@@ -107,6 +107,162 @@ static int do_as_client__status(void)
107107 }
108108}
109109
110+ enum fsmonitor_cookie_item_result {
111+ FCIR_ERROR = -1 , /* could not create cookie file ? */
112+ FCIR_INIT ,
113+ FCIR_SEEN ,
114+ FCIR_ABORT ,
115+ };
116+
117+ struct fsmonitor_cookie_item {
118+ struct hashmap_entry entry ;
119+ char * name ;
120+ enum fsmonitor_cookie_item_result result ;
121+ };
122+
123+ static int cookies_cmp (const void * data , const struct hashmap_entry * he1 ,
124+ const struct hashmap_entry * he2 , const void * keydata )
125+ {
126+ const struct fsmonitor_cookie_item * a =
127+ container_of (he1 , const struct fsmonitor_cookie_item , entry );
128+ const struct fsmonitor_cookie_item * b =
129+ container_of (he2 , const struct fsmonitor_cookie_item , entry );
130+
131+ return strcmp (a -> name , keydata ? keydata : b -> name );
132+ }
133+
134+ static enum fsmonitor_cookie_item_result with_lock__wait_for_cookie (
135+ struct fsmonitor_daemon_state * state )
136+ {
137+ /* assert current thread holding state->main_lock */
138+
139+ int fd ;
140+ struct fsmonitor_cookie_item * cookie ;
141+ struct strbuf cookie_pathname = STRBUF_INIT ;
142+ struct strbuf cookie_filename = STRBUF_INIT ;
143+ enum fsmonitor_cookie_item_result result ;
144+ int my_cookie_seq ;
145+
146+ CALLOC_ARRAY (cookie , 1 );
147+
148+ my_cookie_seq = state -> cookie_seq ++ ;
149+
150+ strbuf_addf (& cookie_filename , "%i-%i" , getpid (), my_cookie_seq );
151+
152+ strbuf_addbuf (& cookie_pathname , & state -> path_cookie_prefix );
153+ strbuf_addbuf (& cookie_pathname , & cookie_filename );
154+
155+ cookie -> name = strbuf_detach (& cookie_filename , NULL );
156+ cookie -> result = FCIR_INIT ;
157+ hashmap_entry_init (& cookie -> entry , strhash (cookie -> name ));
158+
159+ hashmap_add (& state -> cookies , & cookie -> entry );
160+
161+ trace_printf_key (& trace_fsmonitor , "cookie-wait: '%s' '%s'" ,
162+ cookie -> name , cookie_pathname .buf );
163+
164+ /*
165+ * Create the cookie file on disk and then wait for a notification
166+ * that the listener thread has seen it.
167+ */
168+ fd = open (cookie_pathname .buf , O_WRONLY | O_CREAT | O_EXCL , 0600 );
169+ if (fd < 0 ) {
170+ error_errno (_ ("could not create fsmonitor cookie '%s'" ),
171+ cookie -> name );
172+
173+ cookie -> result = FCIR_ERROR ;
174+ goto done ;
175+ }
176+
177+ /*
178+ * Technically, close() and unlink() can fail, but we don't
179+ * care here. We only created the file to trigger a watch
180+ * event from the FS to know that when we're up to date.
181+ */
182+ close (fd );
183+ unlink (cookie_pathname .buf );
184+
185+ /*
186+ * Technically, this is an infinite wait (well, unless another
187+ * thread sends us an abort). I'd like to change this to
188+ * use `pthread_cond_timedwait()` and return an error/timeout
189+ * and let the caller do the trivial response thing, but we
190+ * don't have that routine in our thread-utils.
191+ *
192+ * After extensive beta testing I'm not really worried about
193+ * this. Also note that the above open() and unlink() calls
194+ * will cause at least two FS events on that path, so the odds
195+ * of getting stuck are pretty slim.
196+ */
197+ while (cookie -> result == FCIR_INIT )
198+ pthread_cond_wait (& state -> cookies_cond ,
199+ & state -> main_lock );
200+
201+ done :
202+ hashmap_remove (& state -> cookies , & cookie -> entry , NULL );
203+
204+ result = cookie -> result ;
205+
206+ free (cookie -> name );
207+ free (cookie );
208+ strbuf_release (& cookie_pathname );
209+
210+ return result ;
211+ }
212+
213+ /*
214+ * Mark these cookies as _SEEN and wake up the corresponding client threads.
215+ */
216+ static void with_lock__mark_cookies_seen (struct fsmonitor_daemon_state * state ,
217+ const struct string_list * cookie_names )
218+ {
219+ /* assert current thread holding state->main_lock */
220+
221+ int k ;
222+ int nr_seen = 0 ;
223+
224+ for (k = 0 ; k < cookie_names -> nr ; k ++ ) {
225+ struct fsmonitor_cookie_item key ;
226+ struct fsmonitor_cookie_item * cookie ;
227+
228+ key .name = cookie_names -> items [k ].string ;
229+ hashmap_entry_init (& key .entry , strhash (key .name ));
230+
231+ cookie = hashmap_get_entry (& state -> cookies , & key , entry , NULL );
232+ if (cookie ) {
233+ trace_printf_key (& trace_fsmonitor , "cookie-seen: '%s'" ,
234+ cookie -> name );
235+ cookie -> result = FCIR_SEEN ;
236+ nr_seen ++ ;
237+ }
238+ }
239+
240+ if (nr_seen )
241+ pthread_cond_broadcast (& state -> cookies_cond );
242+ }
243+
244+ /*
245+ * Set _ABORT on all pending cookies and wake up all client threads.
246+ */
247+ static void with_lock__abort_all_cookies (struct fsmonitor_daemon_state * state )
248+ {
249+ /* assert current thread holding state->main_lock */
250+
251+ struct hashmap_iter iter ;
252+ struct fsmonitor_cookie_item * cookie ;
253+ int nr_aborted = 0 ;
254+
255+ hashmap_for_each_entry (& state -> cookies , & iter , cookie , entry ) {
256+ trace_printf_key (& trace_fsmonitor , "cookie-abort: '%s'" ,
257+ cookie -> name );
258+ cookie -> result = FCIR_ABORT ;
259+ nr_aborted ++ ;
260+ }
261+
262+ if (nr_aborted )
263+ pthread_cond_broadcast (& state -> cookies_cond );
264+ }
265+
110266/*
111267 * Requests to and from a FSMonitor Protocol V2 provider use an opaque
112268 * "token" as a virtual timestamp. Clients can request a summary of all
@@ -404,6 +560,9 @@ static void fsmonitor_free_token_data(struct fsmonitor_token_data *token)
404560 * We should create a new token and start fresh (as if we just
405561 * booted up).
406562 *
563+ * [2] Some of those lost events may have been for cookie files. We
564+ * should assume the worst and abort them rather letting them starve.
565+ *
407566 * If there are no concurrent threads reading the current token data
408567 * series, we can free it now. Otherwise, let the last reader free
409568 * it.
@@ -425,6 +584,8 @@ static void with_lock__do_force_resync(struct fsmonitor_daemon_state *state)
425584 state -> current_token_data = new_one ;
426585
427586 fsmonitor_free_token_data (free_me );
587+
588+ with_lock__abort_all_cookies (state );
428589}
429590
430591void fsmonitor_force_resync (struct fsmonitor_daemon_state * state )
@@ -500,6 +661,8 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
500661 int hash_ret ;
501662 int do_trivial = 0 ;
502663 int do_flush = 0 ;
664+ int do_cookie = 0 ;
665+ enum fsmonitor_cookie_item_result cookie_result ;
503666
504667 /*
505668 * We expect `command` to be of the form:
@@ -560,6 +723,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
560723 * We have a V2 valid token:
561724 * "builtin:<token_id>:<seq_nr>"
562725 */
726+ do_cookie = 1 ;
563727 }
564728 }
565729
@@ -568,6 +732,30 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
568732 if (!state -> current_token_data )
569733 BUG ("fsmonitor state does not have a current token" );
570734
735+ /*
736+ * Write a cookie file inside the directory being watched in
737+ * an effort to flush out existing filesystem events that we
738+ * actually care about. Suspend this client thread until we
739+ * see the filesystem events for this cookie file.
740+ *
741+ * Creating the cookie lets us guarantee that our FS listener
742+ * thread has drained the kernel queue and we are caught up
743+ * with the kernel.
744+ *
745+ * If we cannot create the cookie (or otherwise guarantee that
746+ * we are caught up), we send a trivial response. We have to
747+ * assume that there might be some very, very recent activity
748+ * on the FS still in flight.
749+ */
750+ if (do_cookie ) {
751+ cookie_result = with_lock__wait_for_cookie (state );
752+ if (cookie_result != FCIR_SEEN ) {
753+ error (_ ("fsmonitor: cookie_result '%d' != SEEN" ),
754+ cookie_result );
755+ do_trivial = 1 ;
756+ }
757+ }
758+
571759 if (do_flush )
572760 with_lock__do_force_resync (state );
573761
@@ -787,7 +975,9 @@ static int handle_client(void *data,
787975 return result ;
788976}
789977
790- #define FSMONITOR_COOKIE_PREFIX ".fsmonitor-daemon-"
978+ #define FSMONITOR_DIR "fsmonitor--daemon"
979+ #define FSMONITOR_COOKIE_DIR "cookies"
980+ #define FSMONITOR_COOKIE_PREFIX (FSMONITOR_DIR "/" FSMONITOR_COOKIE_DIR "/")
791981
792982enum fsmonitor_path_type fsmonitor_classify_path_workdir_relative (
793983 const char * rel )
@@ -940,6 +1130,9 @@ void fsmonitor_publish(struct fsmonitor_daemon_state *state,
9401130 }
9411131 }
9421132
1133+ if (cookie_names -> nr )
1134+ with_lock__mark_cookies_seen (state , cookie_names );
1135+
9431136 pthread_mutex_unlock (& state -> main_lock );
9441137}
9451138
@@ -1031,7 +1224,9 @@ static int fsmonitor_run_daemon(void)
10311224
10321225 memset (& state , 0 , sizeof (state ));
10331226
1227+ hashmap_init (& state .cookies , cookies_cmp , NULL , 0 );
10341228 pthread_mutex_init (& state .main_lock , NULL );
1229+ pthread_cond_init (& state .cookies_cond , NULL );
10351230 state .error_code = 0 ;
10361231 state .current_token_data = fsmonitor_new_token_data ();
10371232
@@ -1056,6 +1251,44 @@ static int fsmonitor_run_daemon(void)
10561251 state .nr_paths_watching = 2 ;
10571252 }
10581253
1254+ /*
1255+ * We will write filesystem syncing cookie files into
1256+ * <gitdir>/<fsmonitor-dir>/<cookie-dir>/<pid>-<seq>.
1257+ *
1258+ * The extra layers of subdirectories here keep us from
1259+ * changing the mtime on ".git/" or ".git/foo/" when we create
1260+ * or delete cookie files.
1261+ *
1262+ * There have been problems with some IDEs that do a
1263+ * non-recursive watch of the ".git/" directory and run a
1264+ * series of commands any time something happens.
1265+ *
1266+ * For example, if we place our cookie files directly in
1267+ * ".git/" or ".git/foo/" then a `git status` (or similar
1268+ * command) from the IDE will cause a cookie file to be
1269+ * created in one of those dirs. This causes the mtime of
1270+ * those dirs to change. This triggers the IDE's watch
1271+ * notification. This triggers the IDE to run those commands
1272+ * again. And the process repeats and the machine never goes
1273+ * idle.
1274+ *
1275+ * Adding the extra layers of subdirectories prevents the
1276+ * mtime of ".git/" and ".git/foo" from changing when a
1277+ * cookie file is created.
1278+ */
1279+ strbuf_init (& state .path_cookie_prefix , 0 );
1280+ strbuf_addbuf (& state .path_cookie_prefix , & state .path_gitdir_watch );
1281+
1282+ strbuf_addch (& state .path_cookie_prefix , '/' );
1283+ strbuf_addstr (& state .path_cookie_prefix , FSMONITOR_DIR );
1284+ mkdir (state .path_cookie_prefix .buf , 0777 );
1285+
1286+ strbuf_addch (& state .path_cookie_prefix , '/' );
1287+ strbuf_addstr (& state .path_cookie_prefix , FSMONITOR_COOKIE_DIR );
1288+ mkdir (state .path_cookie_prefix .buf , 0777 );
1289+
1290+ strbuf_addch (& state .path_cookie_prefix , '/' );
1291+
10591292 /*
10601293 * Confirm that we can create platform-specific resources for the
10611294 * filesystem listener before we bother starting all the threads.
@@ -1068,13 +1301,15 @@ static int fsmonitor_run_daemon(void)
10681301 err = fsmonitor_run_daemon_1 (& state );
10691302
10701303done :
1304+ pthread_cond_destroy (& state .cookies_cond );
10711305 pthread_mutex_destroy (& state .main_lock );
10721306 fsm_listen__dtor (& state );
10731307
10741308 ipc_server_free (state .ipc_server_data );
10751309
10761310 strbuf_release (& state .path_worktree_watch );
10771311 strbuf_release (& state .path_gitdir_watch );
1312+ strbuf_release (& state .path_cookie_prefix );
10781313
10791314 return err ;
10801315}
0 commit comments