@@ -94,6 +94,149 @@ static int do_as_client__status(void)
94
94
}
95
95
}
96
96
97
+ enum fsmonitor_cookie_item_result {
98
+ FCIR_ERROR = -1 , /* could not create cookie file ? */
99
+ FCIR_INIT = 0 ,
100
+ FCIR_SEEN ,
101
+ FCIR_ABORT ,
102
+ };
103
+
104
+ struct fsmonitor_cookie_item {
105
+ struct hashmap_entry entry ;
106
+ const char * name ;
107
+ enum fsmonitor_cookie_item_result result ;
108
+ };
109
+
110
+ static int cookies_cmp (const void * data , const struct hashmap_entry * he1 ,
111
+ const struct hashmap_entry * he2 , const void * keydata )
112
+ {
113
+ const struct fsmonitor_cookie_item * a =
114
+ container_of (he1 , const struct fsmonitor_cookie_item , entry );
115
+ const struct fsmonitor_cookie_item * b =
116
+ container_of (he2 , const struct fsmonitor_cookie_item , entry );
117
+
118
+ return strcmp (a -> name , keydata ? keydata : b -> name );
119
+ }
120
+
121
+ static enum fsmonitor_cookie_item_result with_lock__wait_for_cookie (
122
+ struct fsmonitor_daemon_state * state )
123
+ {
124
+ /* assert current thread holding state->main_lock */
125
+
126
+ int fd ;
127
+ struct fsmonitor_cookie_item * cookie ;
128
+ struct strbuf cookie_pathname = STRBUF_INIT ;
129
+ struct strbuf cookie_filename = STRBUF_INIT ;
130
+ enum fsmonitor_cookie_item_result result ;
131
+ int my_cookie_seq ;
132
+
133
+ CALLOC_ARRAY (cookie , 1 );
134
+
135
+ my_cookie_seq = state -> cookie_seq ++ ;
136
+
137
+ strbuf_addf (& cookie_filename , "%i-%i" , getpid (), my_cookie_seq );
138
+
139
+ strbuf_addbuf (& cookie_pathname , & state -> path_cookie_prefix );
140
+ strbuf_addbuf (& cookie_pathname , & cookie_filename );
141
+
142
+ cookie -> name = strbuf_detach (& cookie_filename , NULL );
143
+ cookie -> result = FCIR_INIT ;
144
+ hashmap_entry_init (& cookie -> entry , strhash (cookie -> name ));
145
+
146
+ hashmap_add (& state -> cookies , & cookie -> entry );
147
+
148
+ trace_printf_key (& trace_fsmonitor , "cookie-wait: '%s' '%s'" ,
149
+ cookie -> name , cookie_pathname .buf );
150
+
151
+ /*
152
+ * Create the cookie file on disk and then wait for a notification
153
+ * that the listener thread has seen it.
154
+ */
155
+ fd = open (cookie_pathname .buf , O_WRONLY | O_CREAT | O_EXCL , 0600 );
156
+ if (fd >= 0 ) {
157
+ close (fd );
158
+ unlink (cookie_pathname .buf );
159
+
160
+ /*
161
+ * NEEDSWORK: This is an infinite wait (well, unless another
162
+ * thread sends us an abort). I'd like to change this to
163
+ * use `pthread_cond_timedwait()` and return an error/timeout
164
+ * and let the caller do the trivial response thing.
165
+ */
166
+ while (cookie -> result == FCIR_INIT )
167
+ pthread_cond_wait (& state -> cookies_cond ,
168
+ & state -> main_lock );
169
+ } else {
170
+ error_errno (_ ("could not create fsmonitor cookie '%s'" ),
171
+ cookie -> name );
172
+
173
+ cookie -> result = FCIR_ERROR ;
174
+ }
175
+
176
+ hashmap_remove (& state -> cookies , & cookie -> entry , NULL );
177
+
178
+ result = cookie -> result ;
179
+
180
+ free ((char * )cookie -> name );
181
+ free (cookie );
182
+ strbuf_release (& cookie_pathname );
183
+
184
+ return result ;
185
+ }
186
+
187
+ /*
188
+ * Mark these cookies as _SEEN and wake up the corresponding client threads.
189
+ */
190
+ static void with_lock__mark_cookies_seen (struct fsmonitor_daemon_state * state ,
191
+ const struct string_list * cookie_names )
192
+ {
193
+ /* assert current thread holding state->main_lock */
194
+
195
+ int k ;
196
+ int nr_seen = 0 ;
197
+
198
+ for (k = 0 ; k < cookie_names -> nr ; k ++ ) {
199
+ struct fsmonitor_cookie_item key ;
200
+ struct fsmonitor_cookie_item * cookie ;
201
+
202
+ key .name = cookie_names -> items [k ].string ;
203
+ hashmap_entry_init (& key .entry , strhash (key .name ));
204
+
205
+ cookie = hashmap_get_entry (& state -> cookies , & key , entry , NULL );
206
+ if (cookie ) {
207
+ trace_printf_key (& trace_fsmonitor , "cookie-seen: '%s'" ,
208
+ cookie -> name );
209
+ cookie -> result = FCIR_SEEN ;
210
+ nr_seen ++ ;
211
+ }
212
+ }
213
+
214
+ if (nr_seen )
215
+ pthread_cond_broadcast (& state -> cookies_cond );
216
+ }
217
+
218
+ /*
219
+ * Set _ABORT on all pending cookies and wake up all client threads.
220
+ */
221
+ static void with_lock__abort_all_cookies (struct fsmonitor_daemon_state * state )
222
+ {
223
+ /* assert current thread holding state->main_lock */
224
+
225
+ struct hashmap_iter iter ;
226
+ struct fsmonitor_cookie_item * cookie ;
227
+ int nr_aborted = 0 ;
228
+
229
+ hashmap_for_each_entry (& state -> cookies , & iter , cookie , entry ) {
230
+ trace_printf_key (& trace_fsmonitor , "cookie-abort: '%s'" ,
231
+ cookie -> name );
232
+ cookie -> result = FCIR_ABORT ;
233
+ nr_aborted ++ ;
234
+ }
235
+
236
+ if (nr_aborted )
237
+ pthread_cond_broadcast (& state -> cookies_cond );
238
+ }
239
+
97
240
/*
98
241
* Requests to and from a FSMonitor Protocol V2 provider use an opaque
99
242
* "token" as a virtual timestamp. Clients can request a summary of all
@@ -409,6 +552,9 @@ static void fsmonitor_free_token_data(struct fsmonitor_token_data *token)
409
552
* We should create a new token and start fresh (as if we just
410
553
* booted up).
411
554
*
555
+ * [2] Some of those lost events may have been for cookie files. We
556
+ * should assume the worst and abort them rather letting them starve.
557
+ *
412
558
* If there are no concurrent threads readering the current token data
413
559
* series, we can free it now. Otherwise, let the last reader free
414
560
* it.
@@ -430,6 +576,8 @@ static void with_lock__do_force_resync(struct fsmonitor_daemon_state *state)
430
576
state -> current_token_data = new_one ;
431
577
432
578
fsmonitor_free_token_data (free_me );
579
+
580
+ with_lock__abort_all_cookies (state );
433
581
}
434
582
435
583
void fsmonitor_force_resync (struct fsmonitor_daemon_state * state )
@@ -505,6 +653,8 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
505
653
int hash_ret ;
506
654
int do_trivial = 0 ;
507
655
int do_flush = 0 ;
656
+ int do_cookie = 0 ;
657
+ enum fsmonitor_cookie_item_result cookie_result ;
508
658
509
659
/*
510
660
* We expect `command` to be of the form:
@@ -565,6 +715,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
565
715
* We have a V2 valid token:
566
716
* "builtin:<token_id>:<seq_nr>"
567
717
*/
718
+ do_cookie = 1 ;
568
719
}
569
720
}
570
721
@@ -573,6 +724,30 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,
573
724
if (!state -> current_token_data )
574
725
BUG ("fsmonitor state does not have a current token" );
575
726
727
+ /*
728
+ * Write a cookie file inside the directory being watched in
729
+ * an effort to flush out existing filesystem events that we
730
+ * actually care about. Suspend this client thread until we
731
+ * see the filesystem events for this cookie file.
732
+ *
733
+ * Creating the cookie lets us guarantee that our FS listener
734
+ * thread has drained the kernel queue and we are caught up
735
+ * with the kernel.
736
+ *
737
+ * If we cannot create the cookie (or otherwise guarantee that
738
+ * we are caught up), we send a trivial response. We have to
739
+ * assume that there might be some very, very recent activity
740
+ * on the FS still in flight.
741
+ */
742
+ if (do_cookie ) {
743
+ cookie_result = with_lock__wait_for_cookie (state );
744
+ if (cookie_result != FCIR_SEEN ) {
745
+ error (_ ("fsmonitor: cookie_result '%d' != SEEN" ),
746
+ cookie_result );
747
+ do_trivial = 1 ;
748
+ }
749
+ }
750
+
576
751
if (do_flush )
577
752
with_lock__do_force_resync (state );
578
753
@@ -786,7 +961,9 @@ static int handle_client(void *data,
786
961
return result ;
787
962
}
788
963
789
- #define FSMONITOR_COOKIE_PREFIX ".fsmonitor-daemon-"
964
+ #define FSMONITOR_DIR "fsmonitor--daemon"
965
+ #define FSMONITOR_COOKIE_DIR "cookies"
966
+ #define FSMONITOR_COOKIE_PREFIX (FSMONITOR_DIR "/" FSMONITOR_COOKIE_DIR "/")
790
967
791
968
enum fsmonitor_path_type fsmonitor_classify_path_workdir_relative (
792
969
const char * rel )
@@ -939,6 +1116,9 @@ void fsmonitor_publish(struct fsmonitor_daemon_state *state,
939
1116
}
940
1117
}
941
1118
1119
+ if (cookie_names -> nr )
1120
+ with_lock__mark_cookies_seen (state , cookie_names );
1121
+
942
1122
pthread_mutex_unlock (& state -> main_lock );
943
1123
}
944
1124
@@ -1028,7 +1208,9 @@ static int fsmonitor_run_daemon(void)
1028
1208
1029
1209
memset (& state , 0 , sizeof (state ));
1030
1210
1211
+ hashmap_init (& state .cookies , cookies_cmp , NULL , 0 );
1031
1212
pthread_mutex_init (& state .main_lock , NULL );
1213
+ pthread_cond_init (& state .cookies_cond , NULL );
1032
1214
state .error_code = 0 ;
1033
1215
state .current_token_data = fsmonitor_new_token_data ();
1034
1216
@@ -1053,6 +1235,44 @@ static int fsmonitor_run_daemon(void)
1053
1235
state .nr_paths_watching = 2 ;
1054
1236
}
1055
1237
1238
+ /*
1239
+ * We will write filesystem syncing cookie files into
1240
+ * <gitdir>/<fsmonitor-dir>/<cookie-dir>/<pid>-<seq>.
1241
+ *
1242
+ * The extra layers of subdirectories here keep us from
1243
+ * changing the mtime on ".git/" or ".git/foo/" when we create
1244
+ * or delete cookie files.
1245
+ *
1246
+ * There have been problems with some IDEs that do a
1247
+ * non-recursive watch of the ".git/" directory and run a
1248
+ * series of commands any time something happens.
1249
+ *
1250
+ * For example, if we place our cookie files directly in
1251
+ * ".git/" or ".git/foo/" then a `git status` (or similar
1252
+ * command) from the IDE will cause a cookie file to be
1253
+ * created in one of those dirs. This causes the mtime of
1254
+ * those dirs to change. This triggers the IDE's watch
1255
+ * notification. This triggers the IDE to run those commands
1256
+ * again. And the process repeats and the machine never goes
1257
+ * idle.
1258
+ *
1259
+ * Adding the extra layers of subdirectories prevents the
1260
+ * mtime of ".git/" and ".git/foo" from changing when a
1261
+ * cookie file is created.
1262
+ */
1263
+ strbuf_init (& state .path_cookie_prefix , 0 );
1264
+ strbuf_addbuf (& state .path_cookie_prefix , & state .path_gitdir_watch );
1265
+
1266
+ strbuf_addch (& state .path_cookie_prefix , '/' );
1267
+ strbuf_addstr (& state .path_cookie_prefix , FSMONITOR_DIR );
1268
+ mkdir (state .path_cookie_prefix .buf , 0777 );
1269
+
1270
+ strbuf_addch (& state .path_cookie_prefix , '/' );
1271
+ strbuf_addstr (& state .path_cookie_prefix , FSMONITOR_COOKIE_DIR );
1272
+ mkdir (state .path_cookie_prefix .buf , 0777 );
1273
+
1274
+ strbuf_addch (& state .path_cookie_prefix , '/' );
1275
+
1056
1276
/*
1057
1277
* Confirm that we can create platform-specific resources for the
1058
1278
* filesystem listener before we bother starting all the threads.
@@ -1065,13 +1285,19 @@ static int fsmonitor_run_daemon(void)
1065
1285
err = fsmonitor_run_daemon_1 (& state );
1066
1286
1067
1287
done :
1288
+ pthread_cond_destroy (& state .cookies_cond );
1068
1289
pthread_mutex_destroy (& state .main_lock );
1069
1290
fsm_listen__dtor (& state );
1070
1291
1071
1292
ipc_server_free (state .ipc_server_data );
1072
1293
1073
1294
strbuf_release (& state .path_worktree_watch );
1074
1295
strbuf_release (& state .path_gitdir_watch );
1296
+ strbuf_release (& state .path_cookie_prefix );
1297
+
1298
+ /*
1299
+ * NEEDSWORK: Consider "rm -rf <gitdir>/<fsmonitor-dir>"
1300
+ */
1075
1301
1076
1302
return err ;
1077
1303
}
0 commit comments