fix(sync): re-establish file watch after delete and restore#2001
Conversation
✅ Deploy Preview for polite-licorice-3db33c canceled.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe file sync loop retries watcher re-establishment after remove events with bounded attempts, backoff, and context cancellation. Tests cover transient failures, exhausted retries, and cancellation during backoff. ChangesFile watcher recovery
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| // single attempt is not enough to recover the watch. | ||
| reAddMaxAttempts = 5 | ||
| // reAddBackoff is the delay between re-add attempts. | ||
| reAddBackoff = 100 * time.Millisecond |
There was a problem hiding this comment.
Not sure if this is enough time to catch manual restores after accidental deletes?
I wonder if we should make it configurable too, but that's probably overkill for an edge case.
There was a problem hiding this comment.
Ya, I had the same thought. I think we are less concerned with manual delete/add-back, and mostly concerned with automations/scripts and editors, some non-atomic mount/volume update mechanisms (plain hostPath scripts, certain network filesystems) that don't use K8s-style atomic swaps... But for the sake of picking a nice round number, I've made it 1s.
leakonvalinka
left a comment
There was a problem hiding this comment.
Looks good! I left a question but that's not a blocker for me.
| reAddMaxAttempts = 5 | ||
| // reAddBackoff is the delay between re-add attempts. | ||
| reAddBackoff = 100 * time.Millisecond |
There was a problem hiding this comment.
| reAddMaxAttempts = 5 | |
| // reAddBackoff is the delay between re-add attempts. | |
| reAddBackoff = 100 * time.Millisecond | |
| reAddMaxAttempts = 6 | |
| // reAddBackoff is the delay between re-add attempts. | |
| reAddBackoff = 200 * time.Millisecond |
I think we should change this to 200, x 6, which with the logic below equals 1s total, which seems a bit less arbitrary than the 400ms total we get now.
On a remove event the file sync loop re-added the watcher with a single Add call. During a delete quickly followed by a restore the file can be momentarily absent at that instant, so the Add fails, the watch is dropped, and later edits are never picked up. Retry the re-add a bounded number of times with a short backoff so a file that returns within the window is re-watched, aborting early on context cancellation. The retry is watcher-agnostic, covering both the fsnotify and fileinfo paths. Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
a3bb32d to
be2ce02
Compare
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
be2ce02 to
ed4c05d
Compare
|
|
Thanks for landing this, @toddbaert - the file watch surviving delete-and-restore closes a real sync gap. |



Summary
flagd now recovers its file watch when a watched flag-definition file is deleted and quickly restored, instead of silently going stale.
Why this matters
The reporter of #1876 hit this on Windows by deleting the flag file and immediately undoing (ctrl+z): after the restore,
/ofrep/v1/evaluate/flagskept returning stale data and later edits to the file were never picked up. A maintainer confirmed the report, labeled itbug/help wanted, and left it open.The cause is in
core/pkg/sync/file/filepath_sync.go. On aRemoveevent the sync loop re-added the watcher with a singlefs.watcher.Add(fs.URI)call (originally there to handle Kubernetes ConfigMap symlink swaps). During a delete+restore race the file can be momentarily absent at that instant, soAddfails, the code logs "error restoring watcher" and gives up. The watch is then lost permanently even though the file exists again a moment later.Changes
reAddWatcher(ctx), that retriesAdda few times with a short backoff and aborts early onctxcancellation. A file that returns within the retry window is re-watched. The retry is watcher-agnostic, so it fixes both thefsnotifyandfileinfopaths. The retry bound and backoff are package constants (reAddMaxAttempts,reAddBackoff).Testing
go test ./pkg/sync/file/(including-race). Added three unit tests against a mockWatcher:The existing
TestSimpleSyncdelete/update cases (realfsnotifyover a temp dir) stay green, confirming single-delete and ordinary-write behavior is unchanged.Fixes #1876