From 279609adc9ed2af167bb2113c849d5d074a4d8ad Mon Sep 17 00:00:00 2001 From: Aaron Dierking Date: Tue, 26 Jun 2018 16:22:12 -0700 Subject: [PATCH] Ignore Windows FLS destructors during process exit There's a quirk on Windows where exiting the process will still run fiber-local storage destructors for the thread that called ExitProcess() (but only for that thread). pthreads-based platforms don't call any key destructors on exit, and _libdispatch_tsd_cleanup() assumes this. So, on Windows, calling exit() from a queue thread will trigger the fatal error in _dispatch_frame_cleanup(). Implement a reliable method of detecting whether the process is exiting, and if it is, ignore calls to _libdispatch_tsd_cleanup(). --- src/queue.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/queue.c b/src/queue.c index 896ed21bc..f78cb17a5 100644 --- a/src/queue.c +++ b/src/queue.c @@ -961,9 +961,57 @@ _dispatch_install_thread_detach_callback(dispatch_function_t cb) } #endif +#if defined(_WIN32) +static bool +_dispatch_process_is_exiting(void) +{ + // The goal here is to detect if the current thread is executing cleanup + // code (e.g. FLS destructors) as a result of calling ExitProcess(). Windows + // doesn't provide an official method of getting this information, so we + // take advantage of how ExitProcess() works internally. The first thing + // that it does (according to MSDN) is terminate every other thread in the + // process. Logically, it should not be possible to create more threads + // after this point, and Windows indeed enforces this. Try to create a + // lightweight suspended thread, and if access is denied, assume that this + // is because the process is exiting. + // + // We aren't worried about any race conditions here during process exit. + // Cleanup code is only run on the thread that already called ExitProcess(), + // and every other thread will have been forcibly terminated by the time + // that happens. Additionally, while CreateThread() could conceivably fail + // due to resource exhaustion, the process would already be in a bad state + // if that happens. This is only intended to prevent unwanted cleanup code + // from running, so the worst case is that a thread doesn't clean up after + // itself when the process is about to die anyway. + const size_t stack_size = 1; // As small as possible + HANDLE thread = CreateThread(NULL, stack_size, NULL, NULL, + CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); + if (thread) { + // Although Microsoft recommends against using TerminateThread, it's + // safe to use it here because we know that the thread is suspended and + // it has not executed any code due to a NULL lpStartAddress. There was + // a bug in Windows Server 2003 and Windows XP where the initial stack + // would not be freed, but libdispatch does not support them anyway. + TerminateThread(thread, 0); + CloseHandle(thread); + return false; + } + return GetLastError() == ERROR_ACCESS_DENIED; +} +#endif + void DISPATCH_TSD_DTOR_CC _libdispatch_tsd_cleanup(void *ctx) { +#if defined(_WIN32) + // On Windows, exiting a process will still call FLS destructors for the + // thread that called ExitProcess(). pthreads-based platforms don't call key + // destructors on exit, so be consistent. + if (_dispatch_process_is_exiting()) { + return; + } +#endif + struct dispatch_tsd *tsd = (struct dispatch_tsd*) ctx; _tsd_call_cleanup(dispatch_priority_key, NULL);