Commit dac9bd0
fix(watcher): flush debounce on real-event deadline so a hidden-dir stream cannot starve index updates
Context:
The native vault watcher (`run_debounce_loop`) accumulates real file
changes into a buffer and emits a single `vault-files-changed` event per
debounced burst. The notify callback forwards EVERY raw path into the
channel; the hidden-dir filter (`is_inside_hidden_dir`) is applied inside
the loop. The 500 ms debounce was implemented as `recv_timeout(debounce)`
with the buffer flush living only in the `Timeout` arm.
Problem:
`recv_timeout` restarts a fresh window on every call and returns `Ok` the
instant ANY event arrives, including hidden-dir events that are dropped
without touching the buffer or `last_event`. So a hidden-dir stream
arriving faster than 500 ms kept the loop in the `Ok` arm indefinitely,
the `Timeout` arm never ran, and a buffered real edit was never emitted
until the stream paused for a full window (or the sender disconnected on
vault switch/teardown). Realistic triggers, some self-inflicted: a nested
`.git` working copy doing background churn (documented in this file's own
audit comments), a sync client touching `.dropbox.cache`/`.stfolder`, or
our own `{vault}/.kokobrain/kokobrain.db` WAL writes during a semantic
indexing run. Effect: user saves a note but backlinks/tags/tasks/
properties indexes (all driven off `vault-files-changed`) stay stale
until the noise quiets. No panic, no data loss, self-heals.
The existing test `debounce_hidden_events_do_not_delay_real_files` only
sent a bounded 20-event burst then dropped the sender, so it passed via
the `Disconnected` final flush and never modeled a sustained stream that
outlives the assertion. False confidence.
Solution:
Shrink the recv timeout to the remaining deadline measured from the LAST
REAL event (`debounce.saturating_sub(last_event.elapsed())`), and run the
flush check after EVERY wakeup instead of only in the `Timeout` arm. With
an empty buffer the loop blocks a full window (nothing pending, and this
paces the stop check) which also guards against a busy-spin: flushing
empties the buffer, so the next iteration blocks again. A real event
resets `last_event` and so extends the window, matching the prior TS
debounce shape. The `Timeout` arm is now a no-op; `Disconnected` keeps
its final flush.
Behavior:
A buffered real change now emits ~500 ms after the last real edit
regardless of how many hidden-dir events keep waking the loop. Hidden-dir
events never extend the deadline and never delay the emit. Index updates
no longer stall behind background hidden-dir churn.
Files:
- src-tauri/src/vault/watcher.rs:138-207 — rewrote `run_debounce_loop`:
per-iteration `wait` deadline, flush check moved out of the `Timeout`
arm to after the match, empty-buffer full-window block (busy-spin
guard), `Timeout` arm now empty.
- src-tauri/src/vault/watcher.rs:611-680 — added regression test
`debounce_sustained_hidden_stream_does_not_starve_real_file`: a pump
thread streams hidden-dir events every 100 ms and keeps the sender
alive past the assertion, so the emit cannot come from `Disconnected`.
Fails against the pre-fix loop (times out), passes after.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent de53fe9 commit dac9bd0
1 file changed
Lines changed: 102 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
149 | | - | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
150 | 172 | | |
151 | 173 | | |
152 | 174 | | |
153 | 175 | | |
154 | 176 | | |
155 | 177 | | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
163 | | - | |
164 | | - | |
165 | | - | |
166 | | - | |
167 | | - | |
168 | | - | |
| 178 | + | |
169 | 179 | | |
170 | 180 | | |
171 | 181 | | |
| |||
178 | 188 | | |
179 | 189 | | |
180 | 190 | | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
181 | 207 | | |
182 | 208 | | |
183 | 209 | | |
| |||
639 | 665 | | |
640 | 666 | | |
641 | 667 | | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
642 | 730 | | |
643 | 731 | | |
644 | 732 | | |
| |||
0 commit comments