Repository navigation
Refresh the engine once when a data update is applied in memory - #92
Merged
Merged
Conversation
DataUpdateService_CheckForUpdate_InMemory_RefreshesOnce runs a check for a data file with an update URL and no file path, so the downloaded data is applied in memory. It counts every refreshData call on the engine and expects exactly one, the call with the downloaded data.
When a data file has no file path, checkForUpdateFromUrl applies the downloaded data to the engine in memory, then returned AUTO_UPDATE_IN_PROGRESS. checkForUpdate took that to mean the engine had not been refreshed and called updatedFileAvailable, which refreshed the engine a second time through refreshData(String), the method that reloads from a data file. The in-memory path now returns AUTO_UPDATE_SUCCESS once the engine, or the configuration where there is no engine yet, holds the new data, as .NET's DataUpdateService does. checkForUpdate then clears the timer as updatedFileAvailable did, so the next check is registered as before.
…mer's check With a one second polling interval the timer's check can complete first, finding no update because the watcher is enabled, and release the test before the watcher has refreshed the engine. The failed run took 1.008 seconds. The flag is now released only by a completion whose status is AUTO_UPDATE_SUCCESS, which only the watcher's update gives.
The data updates page of the specification on rc says the automatic update master switch "only governs *automatic* activity - it does not disable *Programmatic update*". A programmatic update processes a newer file "as per newFile above", and the steps of newFileAvailable are: - pause file watcher, if any - pause update polling, if any - load file - resume polling, if any - resume file watcher, if any - trigger onUpdateCallbacks Three tests cover a manual check on a data file registered with the service, for each setting of automatic updates. DataUpdateService_CheckForUpdate_FileUpdated_TimerSetAgain applies an update from the data file, and DataUpdateService_CheckForUpdate_InMemory_TimerSetAgain applies one downloaded in memory. Each expects a new timer where automatic updates are on. Both are written to fail where automatic updates are on, because the manual update clears the timer and only a check the timer started registers the data file again. DataUpdateService_CheckForUpdate_NoUpdate_TimerKept checks that a manual check finding no update leaves the existing timer as it was. It passes before and after the change.
… says The data updates page of the specification on rc says the automatic update master switch "only governs *automatic* activity - it does not disable *Programmatic update*". Under Programmatic update, a newer file is processed "as per newFile above", and the steps of newFileAvailable include "resume polling, if any" and "resume file watcher, if any". checkForUpdate registered the data file again only after a check the timer started. A manual update cleared the timer, through updatedFileAvailable or the in-memory success branch, and set no new one, so polling stopped. checkForUpdate now also registers the data file again after a manual check that applied an update, where the data file is registered with the service, so registerDataFile sets the next timer when automatic updates are on, as it does after an automatic check. A manual check that finds no update still leaves the timer as it was, and a data file that was never registered is not registered by a manual check.
Both TimerSetAgain tests now keep the timer pending before the manual update and check, where automatic updates are on, that it was cancelled and that the data file holds a different one. With the existing check that exactly one more timer was scheduled, this shows a manual update with a timer pending leaves one live timer. The pending timer is cancelled and cleared under futureLock, by updatedFileAvailable for the file path or by the in-memory success branch, before registerDataFile schedules a new one, and registerDataFile schedules only while the future is null under the same lock.
…ng unregisters DataUpdateService_CheckForUpdate_FileRefreshFails_TimerSetAgain makes a manual check whose refresh from the data file fails, on a data file registered for automatic updates, and expects the next update timer to be set where automatic updates are on. The newFileAvailable steps in the data updates page of the specification say "resume polling, if any", with no exception for a failed load. DataUpdateService_Unregister_NoLongerRegistered expects a data file to be no longer registered after unregisterDataFile, so a manual update of it sets no timer. Both are written to fail on the previous commit. The first fails where automatic updates are on.
…ion on unregister updatedFileAvailable clears the update timer before it refreshes the engine, so after a manual check whose refresh failed polling stopped, where an automatic check registers the data file again. checkForUpdate now also registers a registered data file again after a manual check that returned AUTO_UPDATE_REFRESH_FAILED. A manual failure that did not clear the timer, such as one in memory, keeps the timer it had, because registerDataFile schedules only while there is none. So a manual check leaves one timer whether it applied an update, failed or found none. unregisterDataFile now sets the data file's update service to null, so getIsRegistered is false after it and a later manual update does not register the data file again.
This was referenced Nov 10, 2026
A comment-only change. The comment above the re-registration after a manual update said that the refresh clears the timer, which is true only of a refresh from the data file, because a failed refresh in memory leaves the timer as it was. No code changes.
jwrosewell
marked this pull request as ready for review
November 10, 2026 13:57
This was referenced Nov 10, 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
When a data file has an update URL and no file path, the data update service applies a downloaded update in memory. On
rcat b92cfcf,checkForUpdateFromUrlcallsrefreshData(identifier, data)on the engine with the downloaded data, or puts the data in the configuration where there is no engine yet. It then returnsmemoryDownloadResult, which is alwaysAUTO_UPDATE_IN_PROGRESSat that point.checkForUpdatereadsAUTO_UPDATE_IN_PROGRESSas "an update is available but the engine has not been refreshed". It therefore callsupdatedFileAvailable, which callsrefreshData(identifier)on the same engine. That is a second refresh, through the method that reloads from a data file, when the update was applied in memory because there is no data file path. The interface documentation ofrefreshData(String)says it reloads from the file atgetDataFilePath(), and that an engine built from a byte[] should be refreshed through the byte[] overload instead. If the second refresh throws,updatedFileAvailableretries it up to 10 times and the check reportsAUTO_UPDATE_REFRESH_FAILED, although the first refresh had applied the update.The existing test
DataUpdateService_Register_UpdateOnStartup_InMemorychecks only the call with the downloaded data, so it did not catch the second call.What .NET does
On the same path, .NET's
CheckForUpdateFromUrlsets the result toAUTO_UPDATE_SUCCESSonceRefreshData(identifier, stream)succeeds (line 961), or once the stream is put in the configuration where there is no engine (line 978).CheckForUpdatecallsUpdatedDataAvailableonly forAUTO_UPDATE_IN_PROGRESS(line 855). OnAUTO_UPDATE_SUCCESSit clears the timer and registers the data file again (lines 862 and 865). So .NET refreshes the engine once.What changed
checkForUpdateFromUrlnow returnsAUTO_UPDATE_SUCCESSonce the engine, or the configuration, holds the new data, as .NET does.checkForUpdatehas a new branch forAUTO_UPDATE_SUCCESS, which clears the timer the same wayupdatedFileAvailabledid on this path.registerDataFilethen registers the next check.checkForUpdateFromUrlnever returnedAUTO_UPDATE_SUCCESSbefore, so the new branch is only reached from the in-memory path.Polling after a manual update
The data updates page on
rcsays the following in three places.Before,
checkForUpdateregistered the data file again only after a check the timer started. A manual update cleared the timer, throughupdatedFileAvailableon the file path or the in-memory success branch, and set no new one, so polling stopped after it.checkForUpdatenow also registers the data file again after a manual check that applied an update or whose refresh failed, where the data file is registered with the service. SoregisterDataFilesets the next timer when automatic updates are on, as it does after an automatic check. This covers both the file path and the in-memory path.futureLock, byupdatedFileAvailable(line 296) or by the in-memory success branch (line 232), beforeregisterDataFileruns.registerDataFileschedules a timer only while the future is null, under the same lock (lines 450 to 452). A manual check whose refresh failed in memory clears no timer, soregisterDataFilekeeps the timer the data file already had.DataUpdateService_CheckForUpdate_FileUpdated, which makes a manual check on an unregistered data file and expects no timer, as it was.unregisterDataFilenow sets the data file's update service to null, sogetIsRegisteredis false after it, and a later manual update does not register the data file again.CheckForUpdatedoes (DataUpdateService.cs line 356 at 5b4d06e), and that is being changed to match the master switch rule above.Overlap with other drafts
Two other changes also edit
DataUpdateServiceDefault.java. #85 has merged intorcas fc55341, and #79 is an open draft. This change stays off their lines, andgit merge-treeshows that this branch's head, 763f6a4, merges cleanly withrcat 24c6f25, which holds #85, and with #79's head 986b81c. The old line numbers below are those ofrcat b92cfcf.checkForUpdateFromUrlcall incheckForUpdate(old line 218) and the retry loop inupdatedFileAvailable(old lines 295 to 311). IncheckForUpdateFromUrlit changes the method's signature, bothdownloadFilecalls, the log lines in the in-memory path and the start of the file path (old lines 785, 792, 799, 810 to 812, 817 and 824).unregisterDataFileafter old line 567, and edits old line 808. Make the data update follow the specification #79 edits none of those lines. Between them sit unchanged lines (old line 809, and old lines 219 to 228).DataUpdateServiceTests.java, Make the data update follow the specification #79 edits old lines 374, 887 to 890 and 1394 to 1395. This change adds three imports, edits the watcher test at old lines 418 to 425, and adds its new tests after old line 1231.licenseKeysToMaskhelper after old line 603, and editeddownloadFile(old lines 618 to 668). This change edits none of those lines.AUTO_UPDATE_SUCCESSimport on the same line as this branch has it. Wait for the watcher's update in the watcher test, not the polling timer's check #95 has merged intorcas 30b38b9. This branch merges cleanly withrcat 24c6f25, which holds that merge, and the merged test file is the same as this branch's.For the release notes
refreshData(String), and a failure of that second refresh was reported as a failed update.unregisterDataFile, the data file'sgetIsRegisteredis now false, so a later manual update does not register it again.How to verify
rc(b92cfcf)DataUpdateServiceTestsrc's code.DataUpdateService_CheckForUpdate_InMemory_RefreshesOncemakes a manual check for a data file with an update URL and no file path, and counts everyrefreshDatacall on the mocked engine. It is written to fail there in all 4 parameter sets, finding two calls,refreshData(String, byte[])and thenrefreshData(String).DataUpdateService_Register_UpdateOnStartup_InMemoryshould still pass, as it still sees one call with the downloaded data and one timer scheduled.DataUpdateService_CheckForUpdate_FileUpdated_TimerSetAgainapplies an update from the data file.DataUpdateService_CheckForUpdate_InMemory_TimerSetAgainapplies one downloaded in memory.DataUpdateService_CheckForUpdate_NoUpdate_TimerKeptchecks that a manual check finding no update keeps the same timer and schedules none. It passes before and after the change.DataUpdateService_CheckForUpdate_FileRefreshFails_TimerSetAgainmakes a manual check whose refresh from the data file fails. It expects one more timer, and a new future on the data file, where automatic updates are on. It is written to fail there on 4215118's code, where the failed refresh leaves no timer.DataUpdateService_Unregister_NoLongerRegisteredregisters a data file, unregisters it and then makes a manual update. It expectsgetIsRegisteredto be false and no new timer. It is written to fail on 4215118's code, wheregetIsRegisteredstays true.Red runs
Each red run builds one commit of tests on the code before its fix, on Ubuntu with Java 17, and runs only that commit's tests.
DataUpdateService_CheckForUpdate_InMemory_RefreshesOncefailed in all 4 parameter sets with "The engine was not refreshed exactly once", listingrefreshData(java.lang.String,byte[])and thenrefreshData(java.lang.String).futureFactory.schedulewanted 2 times but called 1 time.DataUpdateService_CheckForUpdate_NoUpdate_TimerKeptpassed.DataUpdateService_CheckForUpdate_FileRefreshFails_TimerSetAgainfailed in the 2 parameter sets where automatic updates are on, withfutureFactory.schedulewanted 2 times but called 1 time, andDataUpdateService_Unregister_NoLongerRegisteredfailed in all 4 with anAssertionErrorat itsgetIsRegisteredcheck.Full build
The full build and test matrix passed every job other than macOS on head 763f6a4 in run 42797664071 on 10 November 2026, 11 jobs succeeded, run as a dry run so nothing merged. 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.