// Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include #include // for size_t #include #include #include #include "init_locks.hpp" #pragma warning(disable : 4074) #pragma init_seg(compiler) static std::_Init_locks initlocks; extern "C" IMAGE_DOS_HEADER __ImageBase; namespace Concurrency { namespace details { namespace { // When the CRT and STL are statically linked into an EXE, their uninitialization will take place // inside of the call to exit(), before ExitProcess() is called. This means that their // uninitialization occurs before other threads in the process are terminated. We block the exit // from proceeding until all outstanding tasks have completed, to ensure that they do not run after // we destroy the STL's internal locks. // When the CRT and STL are hosted in DLLs (either in their own DLLs or statically linked into a // user DLL), they are uninitialized when the DLL is notified for DLL_PROCESS_DETACH before it is // unloaded. Termination unload occurs after all other threads in the process have been terminated, // so there is no risk of other threads touching internal STL state. We prevent non-termination // unload from occurring while there are outstanding tasks, by having each task own a reference to // the DLL in which the callback is located. HMODULE _Call_get_module_handle_ex(DWORD _Flags, LPCWSTR _Addr) { #if defined(_CRT_APP) // We can't call GetModuleHandleExW from an app context, so treat // that as a failure to call. (void) _Flags; (void) _Addr; return nullptr; #else // ^^^ defined(_CRT_APP) / !defined(_CRT_APP) vvv HMODULE _Result; if (!GetModuleHandleExW(_Flags, _Addr, &_Result)) { return nullptr; } return _Result; #endif // defined(_CRT_APP) } enum class _STL_host_status { _Exe, _Dll, _Unknown }; _STL_host_status _Get_STL_host_status() { #ifdef CRTDLL2 return _STL_host_status::_Dll; #else // ^^^ defined(CRTDLL2) / !defined(CRTDLL2) vvv HANDLE _HExe = _Call_get_module_handle_ex(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nullptr); if (_HExe == nullptr) { return _STL_host_status::_Unknown; } else if (_HExe == reinterpret_cast(&__ImageBase)) { return _STL_host_status::_Exe; } else { return _STL_host_status::_Dll; } #endif // ^^^ !defined(CRTDLL2) ^^^ } #ifdef CRTDLL2 // If the STL is a DLL, no reference counting is necessary, because the CRT shutdown is // always through DLL_PROCESS_DETACH, so keeping the owning reference to the callback // code is sufficient. void _Increment_outstanding() {} void _Decrement_outstanding() {} #else // ^^^ defined(CRTDLL2) / !defined(CRTDLL2) vvv size_t _Outstanding_tasks = 0; _STD mutex _Task_cv_mutex; _STD condition_variable _Task_cv; void _Increment_outstanding() { // block shutdown if (_Get_STL_host_status() == _STL_host_status::_Dll) { return; } _STD lock_guard<_STD mutex> _Lg(_Task_cv_mutex); ++_Outstanding_tasks; } void _Decrement_outstanding() { // release shutdown if (_Get_STL_host_status() == _STL_host_status::_Dll) { return; } size_t _Dec_outstanding; { _STD lock_guard<_STD mutex> _Lg(_Task_cv_mutex); _Dec_outstanding = --_Outstanding_tasks; } if (_Dec_outstanding == 0) { _Task_cv.notify_all(); } } struct _Task_scheduler_main_block { _Task_scheduler_main_block() = default; _Task_scheduler_main_block(const _Task_scheduler_main_block&) = delete; _Task_scheduler_main_block& operator=(const _Task_scheduler_main_block&) = delete; ~_Task_scheduler_main_block() noexcept { // block shutdown of the CRT until std::async shutdown has // completed _STD unique_lock<_STD mutex> _Lck(_Task_cv_mutex); _Task_cv.wait(_Lck, [] { return _Outstanding_tasks == 0; }); } }; _Task_scheduler_main_block _Task_scheduler_main_block_instance; #endif // ^^^ !defined(CRTDLL2) ^^^ void CALLBACK _Task_scheduler_callback(PTP_CALLBACK_INSTANCE _Pci, PVOID _Args, PTP_WORK) noexcept { _Increment_outstanding(); const auto _Chore = static_cast<_Threadpool_chore*>(_Args); if (_Get_STL_host_status() != _STL_host_status::_Exe) { // ensure user code held alive by // _Reschedule_chore is freed when we're done const HMODULE _Callback_dll = _Call_get_module_handle_ex( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(_Chore->_M_callback)); if (_Callback_dll != nullptr) { FreeLibraryWhenCallbackReturns(_Pci, _Callback_dll); } } _Chore->_M_callback(_Chore->_M_data); _Decrement_outstanding(); } } // namespace _CRTIMP2 void __cdecl _Release_chore(_Threadpool_chore* _Chore) { if (_Chore->_M_work != nullptr) { CloseThreadpoolWork(static_cast(_Chore->_M_work)); _Chore->_M_work = nullptr; } } _CRTIMP2 int __cdecl _Reschedule_chore(const _Threadpool_chore* _Chore) { _ASSERT(_Chore->_M_work); // Adds a reference to the DLL with the code to execute on async; the callback will // FreeLibraryWhenCallbackReturns this DLL once it starts running. if (_Get_STL_host_status() != _STL_host_status::_Exe) { (void) _Call_get_module_handle_ex( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(_Chore->_M_callback)); } SubmitThreadpoolWork(static_cast(_Chore->_M_work)); return 0; } _CRTIMP2 int __cdecl _Schedule_chore(_Threadpool_chore* _Chore) { _ASSERT(_Chore->_M_work == nullptr); _ASSERT(_Chore->_M_callback != nullptr); _Chore->_M_work = CreateThreadpoolWork(_Task_scheduler_callback, _Chore, nullptr); if (_Chore->_M_work) { return _Reschedule_chore(_Chore); } else { return static_cast(GetLastError()); // LastError won't be 0 when it's in error state } } } // namespace details } // namespace Concurrency