[PATCH] D102684: [LLD] Allow disabling the early exit codepath as a build configuration
Martin Storsjö via Phabricator via llvm-commits
llvm-commits at lists.llvm.org
Sat May 22 13:23:58 PDT 2021
mstorsjo added a comment.
In D102684#2774826 <https://reviews.llvm.org/D102684#2774826>, @amccarth wrote:
> I could see how that might make even ExitProcess a risky proposition if there are any waits on synchronization objects happening during DLL_THREAD_DETACH. Is this a well understood scenario? If the DLL_THREAD_DETACH destructor problems are hard to fix, would it be feasible to have the thread pool thread signal the main thread to have _it_ call ExitProcess?
Having the threads signal the main thread and make the main thread call ExitProcess is pretty much the same as disabling the early exit mechanism, i.e. what this patch does (behind a configure option).
(The early exit flag does two things: First, exiting from any thread when the error limit is exceeded, vs waiting until the main thread checks if it should exit. And secondly, when exiting, both from the main thread and from worker threads, skip destructors or not. If we exit from non-main threads we really must not run destructors. If we exit from the main thread we could run all of them, but bypassing destructors in the same way shaves off some fraction of runtime, and makes the destructor bypass mechanism more tested.)
While exiting from a non-main thread is tricky, things also do hang when exiting from the main thread, when bypassing constructors. The fatal issue there is that we have a number of threads still running, that can keep various locks. When exiting, all other threads are stopped where they are, and we run the destructors for DLLs.
The hangs on exit look like this:
* frame #0: 0x00007ffe068ae087 ntdll.dll`RtlAllocateHeap + 4215
frame #1: 0x00007ffe068ad997 ntdll.dll`RtlAllocateHeap + 2439
frame #2: 0x00007ffe029baeee ucrtbase.dll`_calloc_base + 78
frame #3: 0x00007ffe029f94bc ucrtbase.dll`__intrinsic_setjmpex + 6012
frame #4: 0x00007ffe029d142d ucrtbase.dll`_configthreadlocale + 13
frame #5: 0x00007ffde79be2b7 libc++.dll`std::__1::ios_base::Init::~Init() + 167
frame #6: 0x00007ffde79f8d5a libc++.dll`std::__1::strstreambuf::operator=(std::__1::strstreambuf&&) + 1418
frame #7: 0x00007ffde79c7751 libc++.dll`std::__1::codecvt<wchar_t, char, _Mbstatet>::do_unshift(_Mbstatet&, char*, char*, char*&) const + 81
frame #8: 0x00007ffde79beda9 libc++.dll`std::__1::ios_base::Init::~Init() + 2969
frame #9: 0x00007ffde79b0016 libc++.dll`std::__1::basic_ostream<wchar_t, std::__1::char_traits<wchar_t> >::flush() + 86
frame #10: 0x00007ffde79be1dc libc++.dll`std::__1::ios_base::Init::Init() + 124
frame #11: 0x00007ffe029c3e26 ucrtbase.dll`_execute_onexit_table + 342
frame #12: 0x00007ffe029c3d4b ucrtbase.dll`_execute_onexit_table + 123
frame #13: 0x00007ffe029c3d04 ucrtbase.dll`_execute_onexit_table + 52
frame #14: 0x00007ffde799109e libc++.dll`_CRT_INIT(hDllHandle=<unavailable>, dwReason=<unavailable>, lpreserved=<unavailable>) at crtdll.c:130:11
frame #15: 0x00007ffde7991311 libc++.dll`__DllMainCRTStartup(hDllHandle=0x00007ffde7990000, dwReason=0, lpreserved=0x0000000000000001) at crtdll.c:195:6
frame #16: 0x00007ffe068e8f07 ntdll.dll`RtlAnsiStringToUnicodeString + 663
frame #17: 0x00007ffe068e35bc ntdll.dll`LdrShutdownProcess + 300
frame #18: 0x00007ffe06906dcd ntdll.dll`RtlExitUserProcess + 173
frame #19: 0x00007ffe042fd62a kernel32.dll`ExitProcess + 10
frame #20: 0x00007ffe029d00a4 ucrtbase.dll`exit + 468
frame #21: 0x00007ffe029cff4f ucrtbase.dll`exit + 127
frame #22: 0x00000001400d2679 ld.lld.exe`_Exit + 9
frame #23: 0x00000001400d265f ld.lld.exe`llvm::sys::Process::Exit(int, bool) + 31
frame #24: 0x000000014003beac ld.lld.exe`lld::exitLld(int) + 460
frame #25: 0x000000014004ad55 ld.lld.exe`lld::coff::link(llvm::ArrayRef<char const*>, bool, llvm::raw_ostream&, llvm::raw_ostream&) + 565
frame #26: 0x00000001400ad85e ld.lld.exe`lld::mingw::link(llvm::ArrayRef<char const*>, bool, llvm::raw_ostream&, llvm::raw_ostream&) + 20046
frame #27: 0x0000000140001f08 ld.lld.exe`lldMain(int, char const**, llvm::raw_ostream&, llvm::raw_ostream&, bool) + 1608
(The top two frames, viewed from WinDbg instead of lldb, are `ntdll!RtlpLowFragHeapAllocFromContext` and `ntdll!RtlpAllocateHeapInternal`.)
So while libc++ ideally wouldn't be calling `_configthreadlocale` when cleaning up, we're also pretty much in pretty bad place in any case if RtlAllocateHeap hangs.
> I'd go so far as to say that if you experience a deadlock or other problem with ExitProcess, the code likely has a bug or at least a questionable design choice.
Well the questionable design choice in this case is LLD that wants to exit while there's lots of threads still "running"/not joined. This is designed to be done by not running any destructors at all, but when we can have DLL boundaries at multiple places (around libc++, or around libLLVM) we end up running some (but not all) destructors.
I'll go ahead and push D102944 <https://reviews.llvm.org/D102944> which should make sure that no destructors whatsoever are called - hopefully that will put an end to these issues. If not we might need to just disable the early exit mechanism altogether on windows and avoid the whole questionable design that way.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D102684/new/
https://reviews.llvm.org/D102684
More information about the llvm-commits
mailing list