fix(sync): re-poll host path after own-write suppression clears - #271
Conversation
pollHostDirectoryOnce's own-write suppression (added to stop the poller from misclassifying an in-flight write-through as an external change) skipped a path's poll entirely while hostWriteInFlight held it - including the mtime/size/cache refresh. A genuine external edit landing on that same path during the window was never picked up: hostModTimes never advanced, so FileRegistry.detectChanges saw no change and GraphFileWatcher never notified the UI. No error, no log - the edit was just gone. Track suppressed paths in hostWriteSuppressedDuringFlush and re-run pollHostDirectoryOnce once the owning flush clears hostWriteInFlight, bounding the loss window to one extra poll instead of indefinite silent drops. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adversarial review of the previous fix (ad0dc53) found retryStuckHostWrites cleared hostWriteInFlight in its own finally block without checking hostWriteSuppressedDuringFlush — reopening the same silent-loss window for any write whose in-flight ownership passes through the stuck-write retry path instead of scheduleHostWriteThrough. Factor the repoll-on-clear logic into repollIfSuppressedDuringFlush and call it from both finally blocks.
Closes the coverage gap flagged in review of ad0dc53/7e55c286: no test previously drove pollHostDirectoryOnce's suppression guard concurrently with an in-flight retryStuckHostWrites flush. Adds a hybrid writable + enumerable OPFS root fixture with a gate to pause a flush mid-attempt, proving the automatic repollIfSuppressedDuringFlush mechanism (not a second manual poll) is what brings the cache current. Verified via wasmJsBrowserTest (headless Chrome): 12/12 passing in HostDirectorySyncWriteThroughTest, including the new test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Closed the remaining review gap from the adversarial pass: added a regression test for the suppression/repoll race itself (2666dae).
Verified with Round-2 convergence: re-read the full |
Android Load BenchmarkInstrumented benchmark on an API 30 x86_64 emulator — 500-page synthetic graph. Comparing Graph Load
Interactive Write Latency (during Phase 3)
SAF I/O Overhead (ContentProvider vs direct File read)Measures Binder IPC cost added by ContentResolver per readFile() call.
|
JVM Load Benchmark (Desktop)Synthetic in-memory benchmark measuring load performance for the desktop (JVM) app.
Flamegraphs (this PR)**Allocation** — object allocation pressure (JDBC/SQLite churn)Alloc flamegraph not available CPU — method-level hotspots by on-CPU time CPU flamegraph not available Top allocation hotspots (this PR)`35.3%` byte[]_[k] `8.6%` java.lang.String_[k] `6.4%` java.util.LinkedHashMap$Entry_[k] `6.2%` int[]_[k] `4.9%` java.lang.Object[]_[k]Top CPU hotspots (this PR)`97.5%` /usr/lib/x86_64-linux-gnu/libc.so.6 `0.9%` /tmp/sqlite-3.51.3.0-d8a880eb-07c2-4a8c-b21b-8eb36454515c-libsqlitejdbc.so `0.4%` __libc_pwrite `0.2%` fsync `0.1%` SR_handler |
Remove the dead writableEnumerableRootGetContent accessor (and its orphaned JS-side _getContent), and expand the new test's comment to scope what the final assertion actually proves: the repoll reflects current on-disk content, not survival of a concurrent external edit against the local write. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Summary
pollHostDirectoryOnce's own-write suppression guard skipped a path's poll entirely while an app-initiated write-through was in flight for that path (hostWriteInFlight) — never refreshinghostModTimes/hostFileSizes/cache for it. A genuine external edit landing on the same path during that window was dropped with no error/log, sinceFileRegistry.detectChangesnever saw an advanced mtime andGraphFileWatchernever fired.hostWriteSuppressedDuringFlushset and re-runspollHostDirectoryOnceonce the owning flush clearshostWriteInFlight, bounding the loss window to one extra poll instead of indefinite silent drops.Root cause confirmed by direct code read of
HostDirectorySync.kt'svisit()/scheduleHostWriteThrough(not just inferred from symptoms). Caveat: this requires the external edit to land during the exact window of an app-initiated write-through to the same path — plausible for actively-autosaved journal/page files (matches the reportedjournals/2026_08_13.mdrepro pattern) but not proven to be the exact original incident's timing.Test plan
JAVA_HOME=.../openjdk-21 ./gradlew :kmp:compileKotlinWasmJs -PenableJs=true— BUILD SUCCESSFUL🤖 Generated with Claude Code