Skip to content

Write the data update messages and levels the .NET service writes - #69

Merged
jwrosewell merged 26 commits into
rcfrom
align/data-update-messages
Nov 11, 2026
Merged

jwrosewell merged 26 commits into
rcfrom
align/data-update-messages

Conversation

@jwrosewell

@jwrosewell jwrosewell commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

The problem

Node's data update service wrote its own sentences for the events the
Errors table of
data-updates.md
lists, at its own levels. A connection failure read "An error occurred when
connecting to <url> in order to check for data file updates for
<Engine>. Update will be attempted again later." at warning, where .NET
logs "Error during check for data update" at error, with an exception
attached that reads "Error accessing data update service at '<url>' for
engine '<Engine type name>'". Someone supporting a customer had to know
which language produced a log before they could read it, and a search for
one sentence found only one language's logs.

Node also reported some checks and not others. A check with no update URL
and a second check started while one was still running each ended silently,
so a listener waiting on updateComplete heard nothing at all.

James Rosewell's decision

Decision 7, 10 November 2026: "The data-updates.md Errors table follows
.NET's messages and levels", and the other languages align to them now,
because "This is all about alignment." Java, Node, Python, Go and Rust use
.NET's text and level for each event, in this release.

Decision 2, the same evening: every check is reported, and a skipped or
not-needed check is reported as NotNeeded, as .NET's DataUpdateService.cs
does. Other language changes to how checks are reported come next week.

What the specification says

data-updates.md, Errors, lines 475 to 486, at e89679e9,
the specifications rc head.

A row at Error level is an error that carries the message shown. Where the service logs it, the entry is at Error, reads Error during check for data update, and has the error attached. An error of any other kind is logged at Error with the message in the last row, also with the error attached. An error raised by a callback in onUpdateCallbacks is logged in the same way on update polling and the file watcher, as Error during check for data update when it is of the same kind as the Error rows, and with the last row's message otherwise. On a programmatic check and the update on start-up it passes to the caller. Either way the callbacks after the one that raised the error are not called, and on update polling any reports of the check not yet made are not made. A row at Warning level is logged with the message shown, and the check goes on.

The same section says these "are the messages and levels of the .NET data
update service". In .NET,
LogUpdateException, lines 658 to 689 at a1454289,
logs a DataUpdateException as Messages.ExceptionAutoUpdate, "Error during
check for data update", and any other exception as
Messages.MessageAutoUpdateUnhandledError, "An unhandled error occurred
while checking for automatic updates for engine '{0}'", each with the
exception attached
(Messages.resx at a1454289,
lines 120 to 122 and 171 to 173). The DataUpdateException carries the
row's text and the exception that caused it, for example the connection
failure at
line 1314.
Callbacks are called by CheckForUpdateComplete?.Invoke at
line 458,
so one that throws stops the rest.

How Node writes an attached error

Node's log takes one string, so the error .NET attaches follows the text,
each part after . Error detail: , the way a .NET logger prints the
exception and its inner exception after the message.

  • An error with a row of its own reads Error during check for data update. Error detail: <row text>, followed by . Error detail: <cause> where
    something caused it, such as connect ECONNREFUSED 127.0.0.1:1.
  • Any other error, and an error raised by a callback, reads An unhandled error occurred while checking for automatic updates for engine '<name>'. Error detail: <cause>.
  • A Warning row is logged with the row's text alone, and the check goes on.

Event by event

Event .NET logs Node wrote Node now logs
Connecting to the update URL failed Error, "Error during check for data update", with "Error accessing data update service at '<url>' for engine '<name>'" and the cause attached warn, "An error occurred when connecting to ... Update will be attempted again later. Error detail: ..." Error, the .NET text, the row's text and the cause
A URL that will not parse the same row warn, the connecting sentence Error, the same, with the cause "Invalid URL"
The update URL gave no response Error, the same text, with "No response from data update service at '<url>' for engine '<name>'" warn, "An error occurred while downloading ... The connection closed before the whole file was received." Error, the .NET text and the row's text
A 429 response Error, the same text, with "Too many requests to '<url>' for engine '<name>'" warn, the downloading sentence with the status code Error, the .NET text and the row's text
A 403 response Error, the same text, with "Access denied to data update service at '<url>' for engine '<name>'" warn, the downloading sentence Error, the .NET text and the row's text
Any other response that is not a success or a 304 Error, the same text, with "HTTP status code '<code>' from data update service at '<url>' for engine '<name>'" warn, the downloading sentence Error, the .NET text and the row's text
A 304 response info, no data newer than the date, status NotNeeded the same unchanged
The response carries no Content-MD5 header Warning, "No MD5 hash included in data update response for '<url>'. Unable to verify data integrity", and the check goes on nothing, the absent header was read as a mismatch and the update stopped the .NET warning, and the update goes on
The integrity check failed Error, the same text, with "Integrity check failed. MD5 hash in HTTP response '<server hash>' for '<name>'data update does not match calculated hash for the downloaded file '<calculated>'." warn, "An error occurred during the integrity check of new data file for <Engine>. Error detail: ..." Error, the .NET text and the row's text, naming both hashes
The data file has no temporary path Error, the same text, with the row's text, status TempPathNotSet the row's text at error, status ErrReadingStream Error, the .NET text and the row's text, status TempPathNotSet
Copying the new file over the existing one failed Error, the same text, with "An error occurred when copying a data file to replace the existing one at '<path>'." and the cause the row's text, except a locked file which had its own "use temp file" sentence Error, the .NET text, the row's text and the cause, for both
Refreshing the engine from a file failed Error, the same text, with "An error occurred when applying a data update to engine '<name>' after <tries> tries." and the cause error, "An error occurred while applying a data file update to <Engine>. Error detail: ..." Error, the .NET text, the row's text and the cause
Refreshing the engine from memory failed Error, the same text, with "An error occurred when applying a data update to engine '<name>'." and the cause the same sentence as above Error, the .NET text, the row's text and the cause
An update complete callback raised an error Error, "An unhandled error occurred ...", with the error, and the later callbacks are not called each callback's error caught, and the later callbacks still called Error, the unhandled text and the cause, and the later callbacks are not called
Any other error Error, "An unhandled error occurred while checking for automatic updates for engine '<name>'", with the error, status UnknownError several different sentences naming a file and an error detail, or the error object alone Error, the unhandled text and the cause, status UnknownError
A check with nothing to do status NotNeeded reported nothing reported NotNeeded reported
A check started while one runs status InProgress reported nothing reported InProgress reported

What changed

In pipeline/fiftyone.pipeline.engines/dataFileUpdateService.js, at the
head f07059a:

  • checkErrorText writes an error with a row of its own as Error during check for data update, then the row's text, then the cause where there is
    one. It is used for every Error row in the table.
  • unhandledErrorText writes the unhandled error text with its cause. It is
    used for an error with no row of its own, an error raised by a callback,
    and an error while the next check is scheduled, which until now logged the
    error object alone.
  • Each of the thirteen places that handle a failure from the network or the
    file system passes the error on, so the cause is in the log. The three
    stream error handlers read their err again, which is also what the
    n/handle-callback-err lint rule asked for.
  • complete stops at the first callback that raises an error, logs it, and
    does not call the callbacks after it, as the specification and .NET do.
    Before, each callback's error was caught and the next callback was called.
  • refreshDataFile writes .NET's text, naming the try count when the data
    came from a file, and ending with a full stop when it came from memory,
    as the row has it.
  • The response handler names each status code in .NET's words rather than
    one downloading sentence for all of them.
  • A response with no Content-MD5 header is a warning and the update
    carries on, where the absent header used to be read as a mismatch.
  • integrityFailed names the hash in the response and the hash of the data
    that arrived.
  • writeFailed goes, because the only .NET row it matched is the memory
    write, and Node's updateFromMemory refuses a data file with a path, so
    it writes no file.
  • The locked file branch goes, because .NET has one row for a failed copy.
  • A check with no update URL reports NotNeeded, and a check started while
    one is running reports InProgress.
  • A failure building the request is logged and reported as the unhandled
    error rather than being lost.

In pipeline/fiftyone.pipeline.engines/autoUpdateStatus.js and its types,
the two names .NET reports that this service now sets are added, being
AUTO_UPDATE_TEMP_PATH_NOT_SET and AUTO_UPDATE_UNKNOWN_ERROR. .NET's
AUTO_UPDATE_TIMEOUT and AUTO_UPDATE_NO_CONFIGURATION are left out,
because nothing in this service sets either, and a status nothing can
produce reads as a working feature. They are under What remains.

node#65, which writes
a callback's error in .NET's unhandled error text and writes an error to the
console where the service has no pipeline, is merged into rc, and rc is
merged in here, so the diff is this draft's own change.

Effect on applications

  • Every Error row now starts with Error during check for data update. A
    log search or an alert keyed on Node's old sentences, or on the row's text
    at the start of the message, needs to change.
  • A listener or onUpdateComplete function that throws now stops the
    callbacks after it for that check. Before, the rest were still called.

Tests

  • Red, run 42980226619, on the tests-only 7032b76. 19 of 58 tests fail, each on the old sentence or level, the missing NotNeeded and InProgress statuses, the missing temporary path status, or the absent Content-MD5 header stopping the update.
  • The first full build, run 42985089997, on 8bff4f1, failed lint with n/handle-callback-err. f8a6520 removed the three err parameters, and its build, run 42986886077, failed 7 tests, because the cause was no longer in the log and the test for a download the MD5 check cannot read sent no hash.
  • The cause after the text. Run 42988707836, job "red, tests on 70b4c5a", fails 14 of 58. b550339 gives the MD5 read test a response with a hash, because the check is made only where a response carries one.
  • Stopping at the first callback. Run 42991773496, job "red, tests on 547418e", fails a listener that throws is logged and the callbacks after it are not called, and the job "green, head e9f1aa7" passes 60 of 60.
  • Error rows and the scheduling error. Run 42992805527, job "red, tests of f471d65 over e9f1aa7 code", fails 15 of 61, being the Error rows, the refresh from memory and the scheduling error. The job "green, head f471d65" passes 61 of 61, with lint clean on pipeline/fiftyone.pipeline.engines.
  • Full build on f471d65, run 42992895964. Passed. All 6 Ubuntu, Ubuntu ARM and Windows jobs succeeded, each with 1,011 tests passed and 2 skipped, every one of the 7 Setting 'PullRequestSha' lines is f471d65d, and Configure printed "Left out 2 macOS entries". It is superseded by the build below.
  • The two statuses nothing sets. Run 43006227088, job "red, tests on 16d149c", fails leave out the statuses nothing here reports, and the job "green, head f07059a" passes 62 of 62, with lint clean.
  • Full build on the head f07059a, which merges rc at b22255e, run 43006243335. Passed. All 6 Ubuntu, Ubuntu ARM and Windows jobs succeeded, each with 1,064 tests passed and 2 skipped, every one of the 7 Setting 'PullRequestSha' lines is f07059af, and Configure printed "Left out 2 macOS entries".

What remains

Three parts of the same specification change how a failure reaches the
caller, and go in a separate draft next week under decision 2. A Promise
from updateDataFile would also change what it returns, which would break
callers in a patch release.

  • A programmatic check passes a failure, and a callback's error, to the
    caller instead of logging it
    (lines 318 to 322,
    lines 339 to 341
    and lines 481 to 483).
    Node logs them, and updateDataFile returns at once rather than giving a
    result the caller can wait for.
  • An update from memory reports nothing to onUpdateCallbacks and fails the
    call with an error that is not logged
    (lines 305 to 307
    and lines 363 to 367).
    Node's updateFromMemory reports to the listeners, logs a failed refresh
    and returns false.
  • The update on start-up passes its error to the caller
    (lines 467 to 469).
    In Node it runs from registerDataFile while the engine is built, which
    has no caller to give it to.

.NET's AUTO_UPDATE_TIMEOUT, from the wait for a watched file
(line 601 at a1454289),
and AUTO_UPDATE_NO_CONFIGURATION, from a programmatic check for a data file
the service has no configuration for
(line 361),
come with the routes that report them. Node has neither route yet, and the
second is part of the programmatic check above, so both follow next week.

Two statuses Node reports, AUTO_UPDATE_ERR_READING_STREAM and
AUTO_UPDATE_MASTER_FILE_CANT_RENAME, have no .NET equivalent. They are
left where they are, because changing them changes what applications see
rather than what a log reads, and the table is about messages and levels.

macOS was left out of this draft's full build under the 9 November decision.
The rc head is built with macOS before it merges into main.

…rther

The update status is reported from timer, request and file system
watcher callbacks, where an error thrown by an application's
updateComplete listener would be uncaught and end the process. The
error must be logged, and the other listeners must still be called.
…cess

complete called the updateComplete listeners through emit with no catch,
so a listener that threw inside a timer, HTTP or fs.watch callback was an
uncaught exception that ended the Node process. Each listener is now
called in turn and an error it throws is logged, as an error thrown by
onUpdateComplete already was. The raw listeners are called, so a once
listener is still removed after it runs.
The Errors table of the pipeline specification now follows the .NET data
update service, so the tests ask for its text, its levels and its statuses.
A connection failure, a bad response, a failed integrity check and a failed
refresh become errors rather than warnings, a response with no Content-MD5
header becomes a warning that lets the check go on, and the missing
temporary path is reported as its own status. A locked data file is the
same row as any other failure to copy, and a failure with no row of its own
is the unhandled error. Three further groups cover the statuses .NET
reports that Node has no name for, a check that found nothing to do
reporting not needed, and a second check while one runs reporting in
progress.

Tests only, so the run from this commit is red.
Each event the Errors table of the pipeline specification lists now carries
the text, the level and the status the .NET data update service gives it,
so a reader of a Node log and a reader of a .NET log see the same sentence
for the same event.

A connection failure, a URL that will not parse, a download cut short, a
429, a 403, any other response that is not a success or a 304, a failed
integrity check and a failed refresh are logged at error rather than
warning, and each names the update service and the engine as .NET does. The
retry sentence goes, because no .NET message carries one. A response with
no Content-MD5 header is a new warning that lets the update go on, where
Node treated the missing header as a mismatch and stopped. A data file with
no temporary path is reported as the temp path status rather than the
reading stream one, and a locked data file is the same row as any other
failure to copy. An error with no row of its own is logged as the unhandled
one and reported as the unknown error status.

The status list gains the four names .NET reports that Node had no name
for, which are the temp path, timeout, no configuration and unknown error
ones. A check with no update URL reports not needed, and a check started
while one is still running reports in progress, so every check is reported
once.
The comment on callbackErrorText said every language uses this wording. Python does, and .NET has no message naming the callback, so the comment now says Python.
… longer read

The connection, write and integrity check handlers now log the sentence .NET writes, which carries no error detail, so each err parameter went unread and lint failed on n/handle-callback-err.
…or to the console where there is no pipeline
…ck error, into the data update messages change
…eck for data update, with the row's text and its cause
…ate, with the row's text and its cause, as .NET does
@jwrosewell
jwrosewell marked this pull request as ready for review November 11, 2026 11:56
@jwrosewell
jwrosewell merged commit 8b9fc3d into rc Nov 11, 2026
2 checks passed
@jwrosewell
jwrosewell deleted the align/data-update-messages branch November 11, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant