Repository navigation
Report a download cut short as the access error, and remove its part file before reporting it - #94
Merged
Merged
Conversation
The data update specification's Errors table gives the messages of the .NET data update service. .NET reads the whole response before its request returns, so a download cut short fails that request, and .NET reports it as "Error accessing data update service at '<url>' for engine '<name>'" with the cause attached. The test of a download cut short closed the connections 10 milliseconds after the check started, whether or not the response had arrived, so it passed or failed by chance. On a slow runner the connection closed before the response, and the message was the one for a failed connection. The test now cuts the connection only once the service's request has its response, from a server that never sends the whole file, and a connection closed before any response is tested on its own. The test of a reset part way through the download expects the same error, which a reset gives whether it comes before or after the response.
A download that ended before the whole file arrived was logged as "No response from data update service", a message .NET writes only when its request returns no response at all. .NET reads the whole response before its request returns, so it reports a download cut short as "Error accessing data update service at '<url>' for engine '<name>'" with the cause attached, which is what Node already wrote for a connection that failed before any response. The service now writes that message for a download cut short too, with the first error the request or the response raised after the response arrived as the cause. The status is unchanged.
The test of a reset part way through the download reset the connection 50 milliseconds after the server wrote, and waited 500 milliseconds before it looked, so its check that the part file had gone held only because of those two times. It now resets once the service's request has its response, waits for the update to be reported, and then checks that there is one report, one scheduled check and no part file. The test of a download cut short checks the folder too. In both tests the cut comes before the service has opened its part file.
When a download was cut short the service destroyed the write stream for the part file, asked for the file to be removed straight away, and reported the update. A stream still opening its file has not created it yet, so the removal could find nothing and the file the stream then created would stay in the temporary folder. In every order the file could still be there when the update was reported. The service now removes the part file once the stream has closed, which is after the file is open, and reports the update once the file has gone.
The folder checks in the two cut short tests passed or failed by chance on the service before the fix, because fs.unlink removes the part file on another thread while the test goes on. A spy on fs.unlink now records each removal as it finishes, and both tests check that the part file's removal had finished when the update was reported. Before the fix the service reports the update first, so the check fails every time.
This was referenced Nov 13, 2026
jwrosewell
marked this pull request as ready for review
November 13, 2026 19:07
This was referenced Nov 13, 2026
Draft
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
The nightly build of
rcat 8f14aa0 failed on Windows_Node_24 inrun 43356655077
(the job "Nightly PR - 0", which builds
rc). One test failed, beinglog messages › a download cut short is the no response errorinpipeline/fiftyone.pipeline.engines/tests/dataUpdateSpecification.test.js.The test
(lines 526 to 546 at 8f14aa04)
starts a check and closes every connection 10 milliseconds later, with
setTimeout(() => remote.server.closeAllConnections(), 10)at line 537,whether or not the response has arrived. The service writes a different
message for each order.
response from data update service"
(
dataFileUpdateService.jslines 525 to 538)."Error accessing data update service" with the cause
(lines 432 to 443).
So the test passed or failed by chance. Of the 68 Windows build and test
jobs that ran a tree holding this test, from when it was added on
11 November to the morning of 13 November, it failed in that one nightly
job. Another job, run 43036532489,
failed for a different reason. Run on its own 60 times on
windows-latestit passed every time
(run 43412245194,
job "rc as it is windows-latest"), so the failure is rare, and the one
failure seen came in a full test run.
A second fault showed once the reset test no longer waited a fixed time.
The close handler destroyed the write stream for the part file, asked for
the file to be removed, and reported the update straight away. The removal
runs on another thread, so the part file could still be in the temporary
folder when the update was reported, and the old reset test's check of the
folder held only because it waited 500 milliseconds first.
What the specification says
The Errors section of data-updates.md, lines 461 to 505 at 6570727d
says which message each failure carries.
AUTO_UPDATE_HTTPS_ERRAUTO_UPDATE_HTTPS_ERRNeither row names a download cut short, so the sentence that decides is the
one saying the messages are those of the .NET service. In .NET,
DataUpdateService.cslines 1305 to 1326sends the request with
_httpClient.SendAsync(message)inside atry, andthe
catchthrows "Error accessing data update service at '{shownUrl}' forengine '...'" with the exception attached. That form of
SendAsync, asMicrosoft's documentation
says, "will complete once the entire response including content is read".
So a download cut short fails inside that call, and .NET reports it as
"Error accessing data update service", with the cause attached. .NET writes
"No response from data update service" only when
SendAsyncreturns noresponse object.
Node's message for a download cut short was therefore not the one the
specification gives, and the test expected that message as well as
depending on the timer.
What changed
Five commits, each fix after the tests that show it.
a download cut short is the error for a service that could not be reachedreplaces the failing test. A server sends the headers withcontent-length: 1000and 7 bytes, and never sends the rest. A spy onhttp.getcloses the connections when the service's request has itsresponse, so the cut always comes part way through the download.
a connection closed before any response is the same erroris new,and covers the other order. The server closes the connection as soon
as the request arrives.
a connection reset part way through the download is reported once and schedules one checkexpects the same error.is accepted, because its wording differs between platforms (
aborted,read ECONNRESET,socket hang up).dataFileUpdateService.js. The response's close handler for adownload cut short logs the same "Error accessing data update service"
text the request's error handler uses (
accessError), with the causeattached. The cause is the first error the request or the response raised
after the response arrived, which the two error handlers now keep where
they used to drop it. The status,
AUTO_UPDATE_HTTPS_ERR, is unchanged.Node no longer writes "No response from data update service". .NET writes
it only when
SendAsyncreturns no response object, and Node'shttp.gethas no outcome of that kind.server writes or waits 500 milliseconds. It resets once the service's
request has its response, waits for the update to be reported, and then
checks there is one report, one scheduled check and no part file. The cut
short test checks the folder too. In both the cut comes before the
service opens its part file.
dataFileUpdateService.js. The close handler removes the partfile once the write stream has closed, which is after the file is open,
and reports the update once the file has gone.
on the service before commit 4, so a spy on
fs.unlinknow records eachremoval as it finishes, and both tests check that the part file's removal
had finished when the update was reported.
The three tests this draft changes or adds
(lines 581, 617 and 968 at 1c1da8e)
use no timer. Other tests in the file still wait fixed times, at lines
811,
884, 890, 931, 942 and 954, 1033, 1078, 1128, 1159, 1185 and 1212, and the
servehelper ends its response after 50 milliseconds at line 86. They arenot changed here.
How it was verified
Each test job runs the test file 10 or 60 times, with the flags of the
repository's
unit-testscript (--ci --runInBand --forceExit --coverage).rcserviceeslintis cleanExpected: 1, Received: 0removals when the update was reportedeslintis cleanRun 43430331608
also cut 200 downloads short in a script outside the tests and counted part
files. On the service of 1e37ee6 the part file was still there when the
update was reported in 6 of 200 on Windows and 29 of 200 on Ubuntu, and gone
200 milliseconds later in all 400. So the file was removed late, but no file
was left behind for good, which is the fault read from the code before. On
104bbb9 the count was 0 at the report each time. In the same run the tests
of 035b82b on the older service failed in 1 of 10 runs on Windows and 0 of
10 on Ubuntu, which is why commit 5 checks the order of removal and report
rather than the folder alone.
Full builds, with every
Setting 'PullRequestSha'line on the commit named.at 1e37ee6 passed. All 8 Ubuntu, Ubuntu ARM, Windows and macOS jobs on
Node.js 22 and 24 succeeded, each with 1,356 unit tests passed and 132
integration tests passed.
at 1c1da8e. The 6 Ubuntu, Ubuntu ARM and Windows jobs passed, each with
1,356 unit tests and 132 integration tests passed. macOS_Node_24 failed
one test this draft does not touch,
the time of a read with the native engine › a read by name is no slower than it wasindevice-detection/fiftyone.devicedetection.onpremise/tests/propertyNameCaseNative.test.jsline 164,which measured 1,460.94 nanoseconds against a limit of 1,385.96, being a
tenth above the 1,260 it measured for the read before. macOS_Node_22 was
cancelled. That failure is a timing test of its own, fixed in
#96.
Effect on applications
A log search or an alert keyed on "No response from data update service" no
longer matches anything Node writes. A download cut short now reads "Error
during check for data update. Error detail: Error accessing data update
service at '<url>' for engine '<name>'. Error detail: <cause>", as .NET
writes it, and is reported once its part file has been removed. The status
reported to
updateCompletelisteners is unchanged.What remains
timing" in
AGENTS.mdit is fixed rather than run again, in its owndraft, #96. This
draft's full build can pass on macOS once Settle the native read timing test on paired rounds, and count the case work #96 is in
rc.update URL failed", is narrower than what .NET reports under it, because a
failure while the body is read is reported the same way. That is
specifications#144.
neither row, as read from each one's
rccode.51degrees-java#125.
51degrees-go#123.
StreamErrorwith its own message,51degrees-rust#403.
requests.getwithout streaming reads the whole body inside that call
(
datafile_update_service.pylines 152 to 161 at df0949d7),so it needs no issue.