This repository was archived by the owner on Nov 3, 2026. It is now read-only.
Repository navigation
Keep the file system watcher watching after a failed refresh - #118
Closed
jwrosewell wants to merge 2 commits into
Closed
jwrosewell wants to merge 2 commits into
jwrosewell wants to merge 2 commits into
Conversation
The file system watcher in DataFileUpdateService checks the data file every 10 seconds and starts its next check only after a refresh returns. A refresh that raises, which is what a corrupt or truncated data file causes, ends the check, so no later check is scheduled and a later good file is never seen. A file that is missing at a check ends it in the same way, at os.stat. The tests take the place of threading.Timer so that each scheduled check runs in turn, and use a data file that keeps the data it has when a refresh fails. They fail here and pass with the change that follows.
A refresh that raises in track_local_data_file_changes is now logged through the pipeline at error level and reported to the on_complete methods with AUTO_UPDATE_REFRESH_FAILED, the status the update route reports for a failed refresh. The next check is scheduled in a finally block, so it is scheduled whatever the check does, and a later good file is picked up. A data file that is missing at a check is looked for again at the next one rather than ending the watcher at os.stat. The 10 second interval is now FILE_SYSTEM_WATCHER_INTERVAL_SECONDS. With 51degrees-cxx 4.5.100 or later, a failed reload keeps the data set in use, so carrying on is right. With older native code a reload that fails in a way only the exception shows has already put a half built data set in place, and the next lookup crashes, so this must not be released with native code older than 51degrees-cxx 4.5.100.
Contributor
|
Closing: this repository has moved into 51degrees-python, and this change is already on its |
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Part of 51degrees-cxx#61, which covers the same fault in other languages as well. This pull request does not close that issue.
Merge condition. Do not merge this before the native code the Python engines are built from is 51degrees-cxx 4.5.100 or later. pipeline-python has no native code of its own. The Device Detection engine in device-detection-python builds today from device-detection-cxx at 0a2f0f80, with common-cxx at 61b8da9a, which is older than 4.5.100. On that native code a reload that fails has already put a half built data set in place before
refreshDataraises, so the next lookup crashes the process whatever this package does, as the runs below show. In 51degrees-cxx 4.5.100 a failed reload keeps the data set in use (51degrees-cxx#60), and that is what makes catching the failure and carrying on the right response. This stays a draft until then, as the nightly pull request workflow in this repository merges a pull request once it is marked ready and has no review requested.Problem
track_local_data_file_changesis the file system watcher. Every 10 seconds it compares the data file's modified time with the last one it saw, refreshes the engine when the time has moved on, and only then starts the timer for its next check. Nothing catches a refresh that raises, and a corrupt or truncated data file makesrefreshDataraise. The exception ends the timer thread before the next timer starts, so the watcher stops for good and a later good file is never seen. The service keeps running on whatever data it had. The calls toos.statat lines 274 and 277 end a check in the same way when the file is missing at that moment, for example part way through being replaced.The update route catches the same failure (lines 198 to 206) and reports
AUTO_UPDATE_REFRESH_FAILEDto theon_completemethods.The watcher is on by default in
DataFileand off by default in the Device Detection engine (file_system_watcher=False), so the fault reaches an application that turns the watcher on. The engine'srefreshcallsrefreshDataand lets itsRuntimeErrorthrough.What was checked and found
A corrupt data file went through the real Device Detection engine from device-detection-python at d14d0d94, on Ubuntu and Windows with Python 3.12. The engine was built twice, once from device-detection-cxx 0a2f0f80 as it is today, and once from 51degrees-cxx 4.5.100 with only the source paths in
setup.pyand the one include inhash_python_wrap.cxxchanged for the new layout. The corrupt file is the Lite data file with one more component in its header than the file holds, which is the broken data the 51degrees-cxx reload tests use. It passes the checks on the version and the collections and fails while the components are read. The engine kept a temporary copy of the data file, so writing over the original does not touch the data in use, and the name of the temporary copy shows which data set is in use. Each case ran in its own process, so that a crash ends only that case.refreshDatacalled directlyAUTO_UPDATE_REFRESH_FAILEDreported, the watcher's timer still runningrefreshDatacalled directlyAUTO_UPDATE_REFRESH_FAILEDreported, the watcher's timer still runningEvery row is the same on Linux and Windows. The two rows that call
refreshDatadirectly, with no watcher, show that the crash comes from the native code and not from the watcher. This is the output for the last row on Linux.The rows for main ran before the change was written (Linux, Windows). The rows for this change ran with the change (Linux, Windows). Both came from temporary workflows on branches that were deleted afterwards, and the scripts are in the commits those runs name.
Change
track_local_data_file_changescatches a refresh that raises. It logs the failure through the pipeline at error level, as the update route logs its other failures, and reportsAUTO_UPDATE_REFRESH_FAILEDto theon_completemethods, as the update route does for a failed refresh.finallyblock, so it is scheduled whatever the check does.os.statending the watcher. Nothing is logged or reported for it, as no refresh was tried.os.statis called once for each check rather than twice.FILE_SYSTEM_WATCHER_INTERVAL_SECONDS.The time of a file that failed is kept as the last time seen, so a broken file is not tried again every 10 seconds. The next refresh waits for the file to change again.
Exit paths of
track_local_data_file_changesos.stat. No refresh is tried and the next check is scheduled.updatingis set. No refresh is tried and the next check is scheduled.on_completemethod raises. The next check is still scheduled, because it is infinally, and the exception ends only that timer thread, which Python reports throughthreading.excepthook.Tests
fiftyone_pipeline_engines/tests/test_datafile_update_service.pyis new, in the sameunitteststyle astest_engines.py.testFileSystemWatcherKeepsWatchingAfterFailedRefreshdrops an unusable file while the watcher runs. It checks that the failure is logged at error level and reported asAUTO_UPDATE_REFRESH_FAILED, that the data in use is kept and that a next check is scheduled, and then that a later good file is picked up.testFileSystemWatcherKeepsWatchingWhileFileIsMissingremoves the file and checks that a next check is scheduled, and then that a later good file is picked up with no failure reported.The engines live in other repositories, so these tests use a refresh made to fail rather than a real engine. The data file in the tests keeps its data when a refresh fails, as an engine on 51degrees-cxx 4.5.100 does, and the real engine is covered by the runs above. The tests take the place of
threading.Timer, so that each scheduled check runs in turn with no waiting..pylintrc, as CI runs itpytest fiftyone_pipeline_engines/teststox -e pyinfiftyone_pipeline_engines, as CI runs itThese are the same on Ubuntu and Windows (Linux, Windows). Before the change the two new tests fail with the exception raised out of the check.
What remains
AUTO_UPDATE_SUCCESS. That is unchanged here.update_data_filenever setsdata_file.updatingtoTrue, and itsif data_file.updating: passat lines 93 and 94 does nothing, so the watcher's check ofupdatingnever skips a refresh. Found while reading and not changed here.