fix: recover polling file watcher from transient file absence#8860
fix: recover polling file watcher from transient file absence#8860Krishnachaitanyakc wants to merge 2 commits intomarimo-team:mainfrom
Conversation
The PollingFileWatcher crashed with an unhandled FileNotFoundError when the watched file was temporarily missing. This commonly occurs with editors like vim that save by writing to a temp file, deleting the original, and renaming -- causing a brief window where the file does not exist. Instead of raising immediately, the watcher now tolerates up to MAX_MISSING_POLLS (5) consecutive absences before giving up. If the file reappears within that window the watcher resumes normally and fires the change callback. Closes marimo-team#8624
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
tests/_utils/test_file_watcher.py
Outdated
| os.remove(tmp_path2) | ||
|
|
||
|
|
||
| async def test_polling_file_watcher_transient_missing() -> None: |
There was a problem hiding this comment.
for a slightly more concise setup, you can use tmp_path:
async def test_polling_file_watcher_transient_missing(tmp_path: Path) -> None:
tmp_file = tmp_path / "nb.py"
tmp_file.write(b"original")
this will also handle cleanup
tests/_utils/test_file_watcher.py
Outdated
| async def test_callback(path: Path) -> None: | ||
| callback_calls.append(path) | ||
|
|
||
| PollingFileWatcher.POLL_SECONDS = 0.05 |
There was a problem hiding this comment.
also for PollingFileWatcher.MAX_MISSING_POLLS, you should be able to use a patch decorator, that will handle cleanup
@patch("marimo._utils.file_watcher.PollingFileWatcher.MAX_MISSING_POLLS", 0.05)
async def test_polling_file_watcher_transient_missing()
…ests Address review feedback: use pytest's tmp_path fixture for automatic cleanup and @patch decorators for class attribute overrides instead of manual setup/teardown.
|
I have read the CLA Document and I hereby sign the CLA You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot. |
Bundle ReportChanges will increase total bundle size by 52 bytes (0.0%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: marimo-esmAssets Changed:
|
|
I have read the CLA Document and I hereby sign the CLA |
There was a problem hiding this comment.
Pull request overview
Improves the resilience of the polling-based file watcher so it can survive transient file absence (e.g., editors that save via delete-and-rename), preventing the watcher task from dying permanently.
Changes:
- Add missing-file tolerance to
PollingFileWatcherviaMAX_MISSING_POLLSand_missing_count. - Stop the polling watcher gracefully after a configurable number of consecutive missing polls.
- Add tests covering transient and permanent missing-file scenarios.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
marimo/_utils/file_watcher.py |
Adds missing-file retry/stop behavior to prevent unhandled FileNotFoundError crashes in the polling watcher. |
tests/_utils/test_file_watcher.py |
Adds async tests validating recovery from transient deletion and stopping after repeated absence. |
Comments suppressed due to low confidence (1)
marimo/_utils/file_watcher.py:116
- There’s a race between the async
exists()check andos.path.getmtime(): the file can disappear afterexists()returns true, causing_get_modified()to returnNone. In that case the current logic treatsmodified=Noneas a change (modified != self.last_modified) and triggerson_file_changed()even though the file is missing. Consider treatingmodified is Noneas a “missing” poll (increment_missing_count/ retry) or re-checkingexists()before firing the callback to avoid spurious change events during delete+rename saves.
# Check for file changes
modified = self._get_modified()
if self.last_modified is None:
self.last_modified = modified
elif modified != self.last_modified:
self.last_modified = modified
await self.on_file_changed()
Summary
Fixes #8624
The
PollingFileWatcher._poll()method crashed with an unhandledFileNotFoundErrorwhen the watched file was temporarily missing. This commonly occurs with editors like vim that save via a delete-and-rename cycle, causing a brief window where the file does not exist. Once the exception propagated, the polling task died permanently and the file watcher never recovered -- forcing users to restart marimo.Changes:
FileNotFoundErrorimmediately when the file is absent, the watcher now tolerates up toMAX_MISSING_POLLS(5) consecutive absences before gracefully stopping_missing_countinstance variable andMAX_MISSING_POLLSclass constant toPollingFileWatcherTest plan
test_polling_file_watcher_transient_missing-- verifies the watcher survives a brief file deletion (simulating vim save) and correctly detects the file change when it reappearstest_polling_file_watcher_permanently_missing-- verifies the watcher gracefully stops afterMAX_MISSING_POLLSconsecutive checks when the file is truly goneruff checkandruff formatpass on changed files