Repository navigation
fix(libc): run guest exit handlers before the host exit handlers - #1658
Conversation
LibcExit called std::exit, which ran the static destructors of the libraries before the guest exit handlers. A guest handler that freed memory then locked a destroyed std::mutex, which throws, and a noexcept guest destructor ended in std::terminate. The guest handlers now run first, while the library objects are alive. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
Additional result on mugurc's Setup: macOS 27, Apple M2, x86-64 under Rosetta, MoltenVK 1.4.2, one retail title (not named). The title runs for 120 s without a crash. Window close:
This is one run on macOS. SIGTERM, Linux and Windows are not tested on this tree; the unit test ( |
|
Linux and Windows runs, since the description lists both as untested. This commit merged onto Linux (Ubuntu 24.04 container, GCC 14.2, Release, Ryzen 9 5900X host):
Windows (Windows 11 Pro 26200, MinGW-w64 GCC 15.2.0 / WinLibs r7, Release, configured like CI).
So the reorder takes effect on Windows as well. If you want Windows coverage in the PR, this is the test I used (registered under #include "prx/libc/include/Shutdown.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdlib>
#include <windows.h>
extern "C" int APS5_VABI __cxa_atexit_nid_postfix(void (APS5_VABI *func)(void*), void* arg, void* dsoHandle);
namespace {
void Record(const char* value) {
DWORD written = 0;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), value, 1, &written, nullptr);
}
void APS5_VABI GuestHandler(void*) { Record("G"); }
void HostHandler() { Record("H"); }
}
int main() {
__cxa_atexit_nid_postfix(GuestHandler, nullptr, nullptr);
std::atexit(HostHandler);
LibcExit_nid_no_patch(0);
}AI-assisted: yes (Claude Code). |
|
Linux test with a retail title, since the PR hasn't been tested there. Demon's Souls (PPSA01341), Nobara 44 (Linux 7.2), GCC 16.2.1, Release, RTX 5070 Ti (595.91.07). Our stack is
None of the six runs exited cleanly. Before this test, every window close on our stack since #1071 ended in -11 or -6, mostly from level-length runs, with |
|
Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.
|
…dlers-first fix(libc): run guest exit handlers before the host exit handlers
What
LibcExit_nid_no_patchcalledstd::exitafter the shutdown callbacks.std::exitruns the static destructors of the host libraries (singletons that hold astd::mutex) before the guest exit handlers, because the guest handlers are run by anatexitrunner that is registered at the first guest__cxa_atexit. A guest handler that freed memory then locked a destroyedstd::mutex. On macOSstd::mutex::lockthen throwsstd::system_error("Invalid argument"), and thenoexceptdestructor of the guest ended instd::terminate:terminate called without an active exception, exit code 134, on a window close and on SIGTERM.LibcExit_nid_no_patchnow callsCxaFinalize_nid_no_patch(nullptr)beforestd::exit. The guest handlers run first, while the library objects are alive.CxaFinalizeremoves each entry as it runs it, so the lateratexitrunner finds the list empty and nothing runs twice. A guest handler that callsexitagain behaves as before (LibcExitalready guards that).Tested
guest_exit_order(UNIX only): a forked child registers a guest handler with__cxa_atexit, then a hoststd::atexithandler, then callsLibcExit; the parent reads the order through a pipe. It fails without the change (host handler first) and passes with it, 3 of 3 runs.maindoes not build libc on macOS yet.std::exitto just before it; I did not check other titles or platforms.Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt.mdfiles or images added to the repository (attach them to this PR)🤖 Generated with Claude Code