]>
Commit | Line | Data |
---|---|---|
5ff01b1f | 1 | #ifndef __clang__ |
b0226007 | 2 | #include <dispatch/dispatch.h> |
5ff01b1f JH |
3 | #include "fsm-darwin-gcc.h" |
4 | #else | |
5 | #include <CoreFoundation/CoreFoundation.h> | |
6 | #include <CoreServices/CoreServices.h> | |
7 | ||
8 | #ifndef AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER | |
9 | /* | |
10 | * This enum value was added in 10.13 to: | |
11 | * | |
12 | * /Applications/Xcode.app/Contents/Developer/Platforms/ \ | |
13 | * MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/ \ | |
14 | * Library/Frameworks/CoreServices.framework/Frameworks/ \ | |
15 | * FSEvents.framework/Versions/Current/Headers/FSEvents.h | |
16 | * | |
17 | * If we're compiling against an older SDK, this symbol won't be | |
18 | * present. Silently define it here so that we don't have to ifdef | |
19 | * the logging or masking below. This should be harmless since older | |
20 | * versions of macOS won't ever emit this FS event anyway. | |
21 | */ | |
22 | #define kFSEventStreamEventFlagItemCloned 0x00400000 | |
23 | #endif | |
24 | #endif | |
25 | ||
f67df255 JH |
26 | #include "cache.h" |
27 | #include "fsmonitor.h" | |
28 | #include "fsm-listen.h" | |
65723b30 | 29 | #include "fsmonitor--daemon.h" |
12fd27df | 30 | #include "fsmonitor-path-utils.h" |
f394e093 | 31 | #include "gettext.h" |
65723b30 | 32 | |
207534e4 | 33 | struct fsm_listen_data |
65723b30 JH |
34 | { |
35 | CFStringRef cfsr_worktree_path; | |
36 | CFStringRef cfsr_gitdir_path; | |
37 | ||
38 | CFArrayRef cfar_paths_to_watch; | |
39 | int nr_paths_watching; | |
40 | ||
41 | FSEventStreamRef stream; | |
42 | ||
b0226007 JH |
43 | dispatch_queue_t dq; |
44 | pthread_cond_t dq_finished; | |
45 | pthread_mutex_t dq_lock; | |
65723b30 JH |
46 | |
47 | enum shutdown_style { | |
48 | SHUTDOWN_EVENT = 0, | |
49 | FORCE_SHUTDOWN, | |
50 | FORCE_ERROR_STOP, | |
51 | } shutdown_style; | |
52 | ||
53 | unsigned int stream_scheduled:1; | |
54 | unsigned int stream_started:1; | |
55 | }; | |
56 | ||
57 | static void log_flags_set(const char *path, const FSEventStreamEventFlags flag) | |
58 | { | |
59 | struct strbuf msg = STRBUF_INIT; | |
60 | ||
61 | if (flag & kFSEventStreamEventFlagMustScanSubDirs) | |
62 | strbuf_addstr(&msg, "MustScanSubDirs|"); | |
63 | if (flag & kFSEventStreamEventFlagUserDropped) | |
64 | strbuf_addstr(&msg, "UserDropped|"); | |
65 | if (flag & kFSEventStreamEventFlagKernelDropped) | |
66 | strbuf_addstr(&msg, "KernelDropped|"); | |
67 | if (flag & kFSEventStreamEventFlagEventIdsWrapped) | |
68 | strbuf_addstr(&msg, "EventIdsWrapped|"); | |
69 | if (flag & kFSEventStreamEventFlagHistoryDone) | |
70 | strbuf_addstr(&msg, "HistoryDone|"); | |
71 | if (flag & kFSEventStreamEventFlagRootChanged) | |
72 | strbuf_addstr(&msg, "RootChanged|"); | |
73 | if (flag & kFSEventStreamEventFlagMount) | |
74 | strbuf_addstr(&msg, "Mount|"); | |
75 | if (flag & kFSEventStreamEventFlagUnmount) | |
76 | strbuf_addstr(&msg, "Unmount|"); | |
77 | if (flag & kFSEventStreamEventFlagItemChangeOwner) | |
78 | strbuf_addstr(&msg, "ItemChangeOwner|"); | |
79 | if (flag & kFSEventStreamEventFlagItemCreated) | |
80 | strbuf_addstr(&msg, "ItemCreated|"); | |
81 | if (flag & kFSEventStreamEventFlagItemFinderInfoMod) | |
82 | strbuf_addstr(&msg, "ItemFinderInfoMod|"); | |
83 | if (flag & kFSEventStreamEventFlagItemInodeMetaMod) | |
84 | strbuf_addstr(&msg, "ItemInodeMetaMod|"); | |
85 | if (flag & kFSEventStreamEventFlagItemIsDir) | |
86 | strbuf_addstr(&msg, "ItemIsDir|"); | |
87 | if (flag & kFSEventStreamEventFlagItemIsFile) | |
88 | strbuf_addstr(&msg, "ItemIsFile|"); | |
89 | if (flag & kFSEventStreamEventFlagItemIsHardlink) | |
90 | strbuf_addstr(&msg, "ItemIsHardlink|"); | |
91 | if (flag & kFSEventStreamEventFlagItemIsLastHardlink) | |
92 | strbuf_addstr(&msg, "ItemIsLastHardlink|"); | |
93 | if (flag & kFSEventStreamEventFlagItemIsSymlink) | |
94 | strbuf_addstr(&msg, "ItemIsSymlink|"); | |
95 | if (flag & kFSEventStreamEventFlagItemModified) | |
96 | strbuf_addstr(&msg, "ItemModified|"); | |
97 | if (flag & kFSEventStreamEventFlagItemRemoved) | |
98 | strbuf_addstr(&msg, "ItemRemoved|"); | |
99 | if (flag & kFSEventStreamEventFlagItemRenamed) | |
100 | strbuf_addstr(&msg, "ItemRenamed|"); | |
101 | if (flag & kFSEventStreamEventFlagItemXattrMod) | |
102 | strbuf_addstr(&msg, "ItemXattrMod|"); | |
103 | if (flag & kFSEventStreamEventFlagOwnEvent) | |
104 | strbuf_addstr(&msg, "OwnEvent|"); | |
105 | if (flag & kFSEventStreamEventFlagItemCloned) | |
106 | strbuf_addstr(&msg, "ItemCloned|"); | |
107 | ||
8e8f4b81 | 108 | trace_printf_key(&trace_fsmonitor, "fsevent: '%s', flags=0x%x %s", |
65723b30 JH |
109 | path, flag, msg.buf); |
110 | ||
111 | strbuf_release(&msg); | |
112 | } | |
113 | ||
de7e0b58 JH |
114 | static int ef_is_root_changed(const FSEventStreamEventFlags ef) |
115 | { | |
116 | return (ef & kFSEventStreamEventFlagRootChanged); | |
117 | } | |
118 | ||
65723b30 JH |
119 | static int ef_is_root_delete(const FSEventStreamEventFlags ef) |
120 | { | |
121 | return (ef & kFSEventStreamEventFlagItemIsDir && | |
122 | ef & kFSEventStreamEventFlagItemRemoved); | |
123 | } | |
124 | ||
125 | static int ef_is_root_renamed(const FSEventStreamEventFlags ef) | |
126 | { | |
127 | return (ef & kFSEventStreamEventFlagItemIsDir && | |
128 | ef & kFSEventStreamEventFlagItemRenamed); | |
129 | } | |
130 | ||
131 | static int ef_is_dropped(const FSEventStreamEventFlags ef) | |
132 | { | |
133 | return (ef & kFSEventStreamEventFlagMustScanSubDirs || | |
134 | ef & kFSEventStreamEventFlagKernelDropped || | |
135 | ef & kFSEventStreamEventFlagUserDropped); | |
136 | } | |
137 | ||
8e8f4b81 JH |
138 | /* |
139 | * If an `xattr` change is the only reason we received this event, | |
140 | * then silently ignore it. Git doesn't care about xattr's. We | |
141 | * have to be careful here because the kernel can combine multiple | |
142 | * events for a single path. And because events always have certain | |
143 | * bits set, such as `ItemIsFile` or `ItemIsDir`. | |
144 | * | |
145 | * Return 1 if we should ignore it. | |
146 | */ | |
147 | static int ef_ignore_xattr(const FSEventStreamEventFlags ef) | |
148 | { | |
149 | static const FSEventStreamEventFlags mask = | |
150 | kFSEventStreamEventFlagItemChangeOwner | | |
151 | kFSEventStreamEventFlagItemCreated | | |
152 | kFSEventStreamEventFlagItemFinderInfoMod | | |
153 | kFSEventStreamEventFlagItemInodeMetaMod | | |
154 | kFSEventStreamEventFlagItemModified | | |
155 | kFSEventStreamEventFlagItemRemoved | | |
156 | kFSEventStreamEventFlagItemRenamed | | |
157 | kFSEventStreamEventFlagItemXattrMod | | |
158 | kFSEventStreamEventFlagItemCloned; | |
159 | ||
160 | return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod); | |
161 | } | |
162 | ||
d6d58ff8 JH |
163 | /* |
164 | * On MacOS we have to adjust for Unicode composition insensitivity | |
165 | * (where NFC and NFD spellings are not respected). The different | |
166 | * spellings are essentially aliases regardless of how the path is | |
167 | * actually stored on the disk. | |
168 | * | |
169 | * This is related to "core.precomposeUnicode" (which wants to try | |
170 | * to hide NFD completely and treat everything as NFC). Here, we | |
171 | * don't know what the value the client has (or will have) for this | |
172 | * config setting when they make a query, so assume the worst and | |
173 | * emit both when the OS gives us an NFD path. | |
174 | */ | |
175 | static void my_add_path(struct fsmonitor_batch *batch, const char *path) | |
176 | { | |
177 | char *composed; | |
178 | ||
179 | /* add the NFC or NFD path as received from the OS */ | |
180 | fsmonitor_batch__add_path(batch, path); | |
181 | ||
182 | /* if NFD, also add the corresponding NFC spelling */ | |
183 | composed = (char *)precompose_string_if_needed(path); | |
184 | if (!composed || composed == path) | |
185 | return; | |
186 | ||
187 | fsmonitor_batch__add_path(batch, composed); | |
188 | free(composed); | |
189 | } | |
190 | ||
191 | ||
65723b30 JH |
192 | static void fsevent_callback(ConstFSEventStreamRef streamRef, |
193 | void *ctx, | |
194 | size_t num_of_events, | |
195 | void *event_paths, | |
196 | const FSEventStreamEventFlags event_flags[], | |
197 | const FSEventStreamEventId event_ids[]) | |
198 | { | |
199 | struct fsmonitor_daemon_state *state = ctx; | |
207534e4 | 200 | struct fsm_listen_data *data = state->listen_data; |
65723b30 JH |
201 | char **paths = (char **)event_paths; |
202 | struct fsmonitor_batch *batch = NULL; | |
203 | struct string_list cookie_list = STRING_LIST_INIT_DUP; | |
204 | const char *path_k; | |
205 | const char *slash; | |
12fd27df | 206 | char *resolved = NULL; |
65723b30 | 207 | struct strbuf tmp = STRBUF_INIT; |
12fd27df | 208 | int k; |
65723b30 JH |
209 | |
210 | /* | |
211 | * Build a list of all filesystem changes into a private/local | |
212 | * list and without holding any locks. | |
213 | */ | |
214 | for (k = 0; k < num_of_events; k++) { | |
215 | /* | |
216 | * On Mac, we receive an array of absolute paths. | |
217 | */ | |
12fd27df ED |
218 | free(resolved); |
219 | resolved = fsmonitor__resolve_alias(paths[k], &state->alias); | |
220 | if (resolved) | |
221 | path_k = resolved; | |
222 | else | |
223 | path_k = paths[k]; | |
65723b30 JH |
224 | |
225 | /* | |
226 | * If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR. | |
227 | * Please don't log them to Trace2. | |
228 | * | |
229 | * trace_printf_key(&trace_fsmonitor, "Path: '%s'", path_k); | |
230 | */ | |
231 | ||
232 | /* | |
233 | * If event[k] is marked as dropped, we assume that we have | |
234 | * lost sync with the filesystem and should flush our cached | |
235 | * data. We need to: | |
236 | * | |
237 | * [1] Abort/wake any client threads waiting for a cookie and | |
238 | * flush the cached state data (the current token), and | |
239 | * create a new token. | |
240 | * | |
241 | * [2] Discard the batch that we were locally building (since | |
242 | * they are conceptually relative to the just flushed | |
243 | * token). | |
244 | */ | |
245 | if (ef_is_dropped(event_flags[k])) { | |
246 | if (trace_pass_fl(&trace_fsmonitor)) | |
247 | log_flags_set(path_k, event_flags[k]); | |
248 | ||
249 | fsmonitor_force_resync(state); | |
250 | fsmonitor_batch__free_list(batch); | |
251 | string_list_clear(&cookie_list, 0); | |
12fd27df | 252 | batch = NULL; |
65723b30 JH |
253 | |
254 | /* | |
255 | * We assume that any events that we received | |
256 | * in this callback after this dropped event | |
257 | * may still be valid, so we continue rather | |
258 | * than break. (And just in case there is a | |
259 | * delete of ".git" hiding in there.) | |
260 | */ | |
261 | continue; | |
262 | } | |
263 | ||
de7e0b58 JH |
264 | if (ef_is_root_changed(event_flags[k])) { |
265 | /* | |
266 | * The spelling of the pathname of the root directory | |
267 | * has changed. This includes the name of the root | |
268 | * directory itself or of any parent directory in the | |
269 | * path. | |
270 | * | |
271 | * (There may be other conditions that throw this, | |
272 | * but I couldn't find any information on it.) | |
273 | * | |
274 | * Force a shutdown now and avoid things getting | |
275 | * out of sync. The Unix domain socket is inside | |
276 | * the .git directory and a spelling change will make | |
277 | * it hard for clients to rendezvous with us. | |
278 | */ | |
279 | trace_printf_key(&trace_fsmonitor, | |
280 | "event: root changed"); | |
281 | goto force_shutdown; | |
282 | } | |
283 | ||
8e8f4b81 JH |
284 | if (ef_ignore_xattr(event_flags[k])) { |
285 | trace_printf_key(&trace_fsmonitor, | |
286 | "ignore-xattr: '%s', flags=0x%x", | |
287 | path_k, event_flags[k]); | |
288 | continue; | |
289 | } | |
290 | ||
65723b30 JH |
291 | switch (fsmonitor_classify_path_absolute(state, path_k)) { |
292 | ||
293 | case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX: | |
294 | case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX: | |
295 | /* special case cookie files within .git or gitdir */ | |
296 | ||
297 | /* Use just the filename of the cookie file. */ | |
298 | slash = find_last_dir_sep(path_k); | |
299 | string_list_append(&cookie_list, | |
300 | slash ? slash + 1 : path_k); | |
301 | break; | |
302 | ||
303 | case IS_INSIDE_DOT_GIT: | |
304 | case IS_INSIDE_GITDIR: | |
305 | /* ignore all other paths inside of .git or gitdir */ | |
306 | break; | |
307 | ||
308 | case IS_DOT_GIT: | |
309 | case IS_GITDIR: | |
310 | /* | |
311 | * If .git directory is deleted or renamed away, | |
312 | * we have to quit. | |
313 | */ | |
314 | if (ef_is_root_delete(event_flags[k])) { | |
315 | trace_printf_key(&trace_fsmonitor, | |
316 | "event: gitdir removed"); | |
317 | goto force_shutdown; | |
318 | } | |
319 | if (ef_is_root_renamed(event_flags[k])) { | |
320 | trace_printf_key(&trace_fsmonitor, | |
321 | "event: gitdir renamed"); | |
322 | goto force_shutdown; | |
323 | } | |
324 | break; | |
325 | ||
326 | case IS_WORKDIR_PATH: | |
327 | /* try to queue normal pathnames */ | |
328 | ||
329 | if (trace_pass_fl(&trace_fsmonitor)) | |
330 | log_flags_set(path_k, event_flags[k]); | |
331 | ||
332 | /* | |
333 | * Because of the implicit "binning" (the | |
334 | * kernel calls us at a given frequency) and | |
335 | * de-duping (the kernel is free to combine | |
336 | * multiple events for a given pathname), an | |
337 | * individual fsevent could be marked as both | |
338 | * a file and directory. Add it to the queue | |
339 | * with both spellings so that the client will | |
340 | * know how much to invalidate/refresh. | |
341 | */ | |
342 | ||
ee0e7fc9 | 343 | if (event_flags[k] & (kFSEventStreamEventFlagItemIsFile | kFSEventStreamEventFlagItemIsSymlink)) { |
65723b30 JH |
344 | const char *rel = path_k + |
345 | state->path_worktree_watch.len + 1; | |
346 | ||
347 | if (!batch) | |
348 | batch = fsmonitor_batch__new(); | |
d6d58ff8 | 349 | my_add_path(batch, rel); |
65723b30 JH |
350 | } |
351 | ||
352 | if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) { | |
353 | const char *rel = path_k + | |
354 | state->path_worktree_watch.len + 1; | |
355 | ||
356 | strbuf_reset(&tmp); | |
357 | strbuf_addstr(&tmp, rel); | |
358 | strbuf_addch(&tmp, '/'); | |
359 | ||
360 | if (!batch) | |
361 | batch = fsmonitor_batch__new(); | |
d6d58ff8 | 362 | my_add_path(batch, tmp.buf); |
65723b30 JH |
363 | } |
364 | ||
365 | break; | |
366 | ||
367 | case IS_OUTSIDE_CONE: | |
368 | default: | |
369 | trace_printf_key(&trace_fsmonitor, | |
370 | "ignoring '%s'", path_k); | |
371 | break; | |
372 | } | |
373 | } | |
374 | ||
12fd27df | 375 | free(resolved); |
65723b30 JH |
376 | fsmonitor_publish(state, batch, &cookie_list); |
377 | string_list_clear(&cookie_list, 0); | |
378 | strbuf_release(&tmp); | |
379 | return; | |
380 | ||
381 | force_shutdown: | |
12fd27df | 382 | free(resolved); |
65723b30 JH |
383 | fsmonitor_batch__free_list(batch); |
384 | string_list_clear(&cookie_list, 0); | |
385 | ||
b0226007 | 386 | pthread_mutex_lock(&data->dq_lock); |
65723b30 | 387 | data->shutdown_style = FORCE_SHUTDOWN; |
b0226007 JH |
388 | pthread_cond_broadcast(&data->dq_finished); |
389 | pthread_mutex_unlock(&data->dq_lock); | |
390 | ||
65723b30 JH |
391 | strbuf_release(&tmp); |
392 | return; | |
393 | } | |
394 | ||
395 | /* | |
396 | * In the call to `FSEventStreamCreate()` to setup our watch, the | |
397 | * `latency` argument determines the frequency of calls to our callback | |
398 | * with new FS events. Too slow and events get dropped; too fast and | |
399 | * we burn CPU unnecessarily. Since it is rather obscure, I don't | |
400 | * think this needs to be a config setting. I've done extensive | |
401 | * testing on my systems and chosen the value below. It gives good | |
402 | * results and I've not seen any dropped events. | |
403 | * | |
404 | * With a latency of 0.1, I was seeing lots of dropped events during | |
405 | * the "touch 100000" files test within t/perf/p7519, but with a | |
406 | * latency of 0.001 I did not see any dropped events. So I'm going | |
407 | * to assume that this is the "correct" value. | |
408 | * | |
409 | * https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate | |
410 | */ | |
f67df255 JH |
411 | |
412 | int fsm_listen__ctor(struct fsmonitor_daemon_state *state) | |
413 | { | |
65723b30 JH |
414 | FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagNoDefer | |
415 | kFSEventStreamCreateFlagWatchRoot | | |
416 | kFSEventStreamCreateFlagFileEvents; | |
417 | FSEventStreamContext ctx = { | |
418 | 0, | |
419 | state, | |
420 | NULL, | |
421 | NULL, | |
422 | NULL | |
423 | }; | |
207534e4 | 424 | struct fsm_listen_data *data; |
65723b30 JH |
425 | const void *dir_array[2]; |
426 | ||
427 | CALLOC_ARRAY(data, 1); | |
207534e4 | 428 | state->listen_data = data; |
65723b30 JH |
429 | |
430 | data->cfsr_worktree_path = CFStringCreateWithCString( | |
431 | NULL, state->path_worktree_watch.buf, kCFStringEncodingUTF8); | |
432 | dir_array[data->nr_paths_watching++] = data->cfsr_worktree_path; | |
433 | ||
434 | if (state->nr_paths_watching > 1) { | |
435 | data->cfsr_gitdir_path = CFStringCreateWithCString( | |
436 | NULL, state->path_gitdir_watch.buf, | |
437 | kCFStringEncodingUTF8); | |
438 | dir_array[data->nr_paths_watching++] = data->cfsr_gitdir_path; | |
439 | } | |
440 | ||
441 | data->cfar_paths_to_watch = CFArrayCreate(NULL, dir_array, | |
442 | data->nr_paths_watching, | |
443 | NULL); | |
444 | data->stream = FSEventStreamCreate(NULL, fsevent_callback, &ctx, | |
445 | data->cfar_paths_to_watch, | |
446 | kFSEventStreamEventIdSinceNow, | |
447 | 0.001, flags); | |
e6bf70d1 | 448 | if (!data->stream) |
65723b30 JH |
449 | goto failed; |
450 | ||
65723b30 JH |
451 | return 0; |
452 | ||
453 | failed: | |
454 | error(_("Unable to create FSEventStream.")); | |
455 | ||
207534e4 | 456 | FREE_AND_NULL(state->listen_data); |
f67df255 JH |
457 | return -1; |
458 | } | |
459 | ||
460 | void fsm_listen__dtor(struct fsmonitor_daemon_state *state) | |
461 | { | |
207534e4 | 462 | struct fsm_listen_data *data; |
65723b30 | 463 | |
207534e4 | 464 | if (!state || !state->listen_data) |
65723b30 JH |
465 | return; |
466 | ||
207534e4 | 467 | data = state->listen_data; |
65723b30 JH |
468 | |
469 | if (data->stream) { | |
470 | if (data->stream_started) | |
471 | FSEventStreamStop(data->stream); | |
472 | if (data->stream_scheduled) | |
473 | FSEventStreamInvalidate(data->stream); | |
474 | FSEventStreamRelease(data->stream); | |
475 | } | |
476 | ||
b0226007 JH |
477 | if (data->dq) |
478 | dispatch_release(data->dq); | |
479 | pthread_cond_destroy(&data->dq_finished); | |
480 | pthread_mutex_destroy(&data->dq_lock); | |
481 | ||
207534e4 | 482 | FREE_AND_NULL(state->listen_data); |
f67df255 JH |
483 | } |
484 | ||
485 | void fsm_listen__stop_async(struct fsmonitor_daemon_state *state) | |
486 | { | |
207534e4 | 487 | struct fsm_listen_data *data; |
65723b30 | 488 | |
207534e4 | 489 | data = state->listen_data; |
65723b30 | 490 | |
b0226007 JH |
491 | pthread_mutex_lock(&data->dq_lock); |
492 | data->shutdown_style = SHUTDOWN_EVENT; | |
493 | pthread_cond_broadcast(&data->dq_finished); | |
494 | pthread_mutex_unlock(&data->dq_lock); | |
f67df255 JH |
495 | } |
496 | ||
497 | void fsm_listen__loop(struct fsmonitor_daemon_state *state) | |
498 | { | |
207534e4 | 499 | struct fsm_listen_data *data; |
65723b30 | 500 | |
207534e4 | 501 | data = state->listen_data; |
65723b30 | 502 | |
b0226007 JH |
503 | pthread_mutex_init(&data->dq_lock, NULL); |
504 | pthread_cond_init(&data->dq_finished, NULL); | |
505 | data->dq = dispatch_queue_create("FSMonitor", NULL); | |
65723b30 | 506 | |
b0226007 | 507 | FSEventStreamSetDispatchQueue(data->stream, data->dq); |
65723b30 JH |
508 | data->stream_scheduled = 1; |
509 | ||
510 | if (!FSEventStreamStart(data->stream)) { | |
511 | error(_("Failed to start the FSEventStream")); | |
512 | goto force_error_stop_without_loop; | |
513 | } | |
514 | data->stream_started = 1; | |
515 | ||
b0226007 JH |
516 | pthread_mutex_lock(&data->dq_lock); |
517 | pthread_cond_wait(&data->dq_finished, &data->dq_lock); | |
518 | pthread_mutex_unlock(&data->dq_lock); | |
65723b30 JH |
519 | |
520 | switch (data->shutdown_style) { | |
521 | case FORCE_ERROR_STOP: | |
207534e4 | 522 | state->listen_error_code = -1; |
65723b30 JH |
523 | /* fall thru */ |
524 | case FORCE_SHUTDOWN: | |
525 | ipc_server_stop_async(state->ipc_server_data); | |
526 | /* fall thru */ | |
527 | case SHUTDOWN_EVENT: | |
528 | default: | |
529 | break; | |
530 | } | |
531 | return; | |
532 | ||
533 | force_error_stop_without_loop: | |
207534e4 | 534 | state->listen_error_code = -1; |
65723b30 JH |
535 | ipc_server_stop_async(state->ipc_server_data); |
536 | return; | |
f67df255 | 537 | } |