Skip to content

Commit ba3933d

Browse files
authored
Merge pull request #377 from adierking/fls
Ignore Windows FLS destructors during process exit
2 parents b863538 + 279609a commit ba3933d

File tree

1 file changed

+48
-0
lines changed

1 file changed

+48
-0
lines changed

src/queue.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,9 +961,57 @@ _dispatch_install_thread_detach_callback(dispatch_function_t cb)
961961
}
962962
#endif
963963

964+
#if defined(_WIN32)
965+
static bool
966+
_dispatch_process_is_exiting(void)
967+
{
968+
// The goal here is to detect if the current thread is executing cleanup
969+
// code (e.g. FLS destructors) as a result of calling ExitProcess(). Windows
970+
// doesn't provide an official method of getting this information, so we
971+
// take advantage of how ExitProcess() works internally. The first thing
972+
// that it does (according to MSDN) is terminate every other thread in the
973+
// process. Logically, it should not be possible to create more threads
974+
// after this point, and Windows indeed enforces this. Try to create a
975+
// lightweight suspended thread, and if access is denied, assume that this
976+
// is because the process is exiting.
977+
//
978+
// We aren't worried about any race conditions here during process exit.
979+
// Cleanup code is only run on the thread that already called ExitProcess(),
980+
// and every other thread will have been forcibly terminated by the time
981+
// that happens. Additionally, while CreateThread() could conceivably fail
982+
// due to resource exhaustion, the process would already be in a bad state
983+
// if that happens. This is only intended to prevent unwanted cleanup code
984+
// from running, so the worst case is that a thread doesn't clean up after
985+
// itself when the process is about to die anyway.
986+
const size_t stack_size = 1; // As small as possible
987+
HANDLE thread = CreateThread(NULL, stack_size, NULL, NULL,
988+
CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
989+
if (thread) {
990+
// Although Microsoft recommends against using TerminateThread, it's
991+
// safe to use it here because we know that the thread is suspended and
992+
// it has not executed any code due to a NULL lpStartAddress. There was
993+
// a bug in Windows Server 2003 and Windows XP where the initial stack
994+
// would not be freed, but libdispatch does not support them anyway.
995+
TerminateThread(thread, 0);
996+
CloseHandle(thread);
997+
return false;
998+
}
999+
return GetLastError() == ERROR_ACCESS_DENIED;
1000+
}
1001+
#endif
1002+
9641003
void DISPATCH_TSD_DTOR_CC
9651004
_libdispatch_tsd_cleanup(void *ctx)
9661005
{
1006+
#if defined(_WIN32)
1007+
// On Windows, exiting a process will still call FLS destructors for the
1008+
// thread that called ExitProcess(). pthreads-based platforms don't call key
1009+
// destructors on exit, so be consistent.
1010+
if (_dispatch_process_is_exiting()) {
1011+
return;
1012+
}
1013+
#endif
1014+
9671015
struct dispatch_tsd *tsd = (struct dispatch_tsd*) ctx;
9681016

9691017
_tsd_call_cleanup(dispatch_priority_key, NULL);

0 commit comments

Comments
 (0)