fsmonitor: flush pending FSEvents before cookie wait

56cef9cb1a (fsmonitor: use pthread_cond_timedwait for cookie wait, 2026-04-15) limits the cookie wait to one second so that a filesystem which never delivers events cannot hang fsmonitor clients. A client that times out receives a trivial response and scans the entire index. FSEvents can defer delivery while it batches notifications and does not guarantee that its queue is drained in one latency interval. A loaded macOS system can therefore time out even though the event stream is working. On an Apple M4 Max (16 cores, 128 GiB RAM) running macOS 26.5.2, two worktrees with a 1,001,178-entry index timed out 484 of 545 and 297 of 365 fsmonitor requests. One status call performed 934,519 lstat() calls during a 47-second preload and took 52 seconds overall. Ask FSEvents to flush pending notifications after creating the cookie and before starting the timed wait. Use the asynchronous form because the client handler holds main_lock, which the listener callback also acquires. Keep the timeout and the behavior of the other backends unchanged. Signed-off-by: Tamir Duberstein <tamird@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Tamir Duberstein committed Jul 21, 2026 at 17:04 UTC 08b12d90cde7a833221e132b1b2b7a9c724af234
6 files changed +23
builtin/fsmonitor--daemon.c
+3
@@ -206,6 +206,9 @@ static enum fsmonitor_cookie_item_result with_lock__wait_for_cookie(
206 close(fd);
207 unlink(cookie_pathname.buf);
208
209 + /* The listener callback takes main_lock, so this must not block. */
210 + fsm_listen__flush_async(state);
211 +
212 /*
213 * Wait for the listener thread to observe the cookie file.
214 * Time out after a short interval so that the client
compat/fsmonitor/fsm-darwin-gcc.h
+1
@@ -82,6 +82,7 @@ CFRunLoopRef CFRunLoopGetCurrent(void);
82 extern CFStringRef kCFRunLoopDefaultMode;
83 void FSEventStreamSetDispatchQueue(FSEventStreamRef stream, dispatch_queue_t q);
84 unsigned char FSEventStreamStart(FSEventStreamRef stream);
85 +FSEventStreamEventId FSEventStreamFlushAsync(FSEventStreamRef stream);
86 void FSEventStreamStop(FSEventStreamRef stream);
87 void FSEventStreamInvalidate(FSEventStreamRef stream);
88 void FSEventStreamRelease(FSEventStreamRef stream);
compat/fsmonitor/fsm-listen-darwin.c
+5
@@ -496,6 +496,11 @@ void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
496 pthread_mutex_unlock(&data->dq_lock);
497 }
498
499 +void fsm_listen__flush_async(struct fsmonitor_daemon_state *state)
500 +{
501 + FSEventStreamFlushAsync(state->listen_data->stream);
502 +}
503 +
504 void fsm_listen__loop(struct fsmonitor_daemon_state *state)
505 {
506 struct fsm_listen_data *data;
compat/fsmonitor/fsm-listen-linux.c
+4
@@ -493,6 +493,10 @@ void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
493 state->listen_data->shutdown = SHUTDOWN_STOP;
494 }
495
496 +void fsm_listen__flush_async(struct fsmonitor_daemon_state *state UNUSED)
497 +{
498 +}
499 +
500 /*
501 * Process a single inotify event and queue for publication.
502 */
compat/fsmonitor/fsm-listen-win32.c
+4
@@ -290,6 +290,10 @@ void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)
290 SetEvent(state->listen_data->hListener[LISTENER_SHUTDOWN]);
291 }
292
293 +void fsm_listen__flush_async(struct fsmonitor_daemon_state *state UNUSED)
294 +{
295 +}
296 +
297 static struct one_watch *create_watch(const char *path)
298 {
299 struct one_watch *watch = NULL;
compat/fsmonitor/fsm-listen.h
+6
@@ -38,6 +38,12 @@ void fsm_listen__dtor(struct fsmonitor_daemon_state *state);
38 */
39 void fsm_listen__loop(struct fsmonitor_daemon_state *state);
40
41 +/*
42 + * Prompt the listener to deliver queued filesystem events, if supported.
43 + * This does not wait for the events to be processed.
44 + */
45 +void fsm_listen__flush_async(struct fsmonitor_daemon_state *state);
46 +
47 /*
48 * Gently request that the fsmonitor listener thread shutdown.
49 * It does not wait for it to stop. The caller should do a JOIN