system: keep the process heap lock unowned across close-time guest thread suspension and make the forced-exit watchdog heap-free
This commit is contained in:
parent
4972095c49
commit
f00a126494
2 changed files with 54 additions and 1 deletions
|
|
@ -12,6 +12,11 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <rex/platform.h>
|
||||||
|
#if REX_PLATFORM_WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
#include <rex/assert.h>
|
#include <rex/assert.h>
|
||||||
|
|
@ -863,6 +868,21 @@ void KernelState::TerminateTitle() {
|
||||||
global_lock.lock();
|
global_lock.lock();
|
||||||
#else
|
#else
|
||||||
// Suspend all running guest threads so they stop touching shared state.
|
// Suspend all running guest threads so they stop touching shared state.
|
||||||
|
//
|
||||||
|
// Guest threads allocate on the process heap constantly (kernel calls,
|
||||||
|
// capture hooks), and SuspendThread stops a thread at an arbitrary
|
||||||
|
// instruction: without protection one can be caught inside the allocator
|
||||||
|
// OWNING the heap lock, and the thread-object frees below then deadlock
|
||||||
|
// the close forever (observed: UI thread parked in RtlpFreeHeap while the
|
||||||
|
// suspended guest render thread sat in RtlpAllocateHeap). Holding the heap
|
||||||
|
// lock across the suspension makes that state unreachable: a thread inside
|
||||||
|
// the allocator finishes before HeapLock returns, and later entrants block
|
||||||
|
// without owning the lock. The lock is reentrant for this thread, so the
|
||||||
|
// bookkeeping below may still allocate.
|
||||||
|
#if REX_PLATFORM_WIN32
|
||||||
|
HANDLE process_heap = GetProcessHeap();
|
||||||
|
HeapLock(process_heap);
|
||||||
|
#endif
|
||||||
std::vector<XThread*> suspended_threads;
|
std::vector<XThread*> suspended_threads;
|
||||||
for (auto it = threads_by_id_.begin(); it != threads_by_id_.end(); ++it) {
|
for (auto it = threads_by_id_.begin(); it != threads_by_id_.end(); ++it) {
|
||||||
if (!XThread::IsInThread(it->second) && it->second->is_guest_thread() &&
|
if (!XThread::IsInThread(it->second) && it->second->is_guest_thread() &&
|
||||||
|
|
@ -871,6 +891,9 @@ void KernelState::TerminateTitle() {
|
||||||
suspended_threads.push_back(it->second);
|
suspended_threads.push_back(it->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if REX_PLATFORM_WIN32
|
||||||
|
HeapUnlock(process_heap);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Terminate each suspended thread. Must drop the lock since Terminate waits.
|
// Terminate each suspended thread. Must drop the lock since Terminate waits.
|
||||||
global_lock.unlock();
|
global_lock.unlock();
|
||||||
|
|
|
||||||
|
|
@ -47,13 +47,21 @@
|
||||||
#include <sys/utsname.h>
|
#include <sys/utsname.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if REX_PLATFORM_WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <toml++/toml.hpp>
|
#include <toml++/toml.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
@ -206,8 +214,30 @@ void LogLinuxRuntimeDiagnostics() {
|
||||||
void StartForcedExitWatchdog(const char* reason) {
|
void StartForcedExitWatchdog(const char* reason) {
|
||||||
std::thread([reason]() {
|
std::thread([reason]() {
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(10));
|
std::this_thread::sleep_for(std::chrono::seconds(10));
|
||||||
REXLOG_WARN("{} watchdog exiting process after shutdown timeout", reason);
|
// The teardown this guards can wedge with the process heap lock orphaned
|
||||||
|
// (a guest thread suspended or terminated mid-allocation), so the kill
|
||||||
|
// path must not allocate or run lock-taking shutdown: logging and
|
||||||
|
// std::_Exit are both off-limits on Windows (ExitProcess acquires the
|
||||||
|
// heap lock inside RtlExitUserProcess, deadlocking the watchdog itself).
|
||||||
|
// TerminateProcess skips user-mode cleanup entirely; on POSIX std::_Exit
|
||||||
|
// is a plain exit_group with no lock use. The marker is emitted through
|
||||||
|
// heap-free primitives only.
|
||||||
|
char msg[160];
|
||||||
|
std::snprintf(msg, sizeof(msg), "%s watchdog terminating process after shutdown timeout\n",
|
||||||
|
reason);
|
||||||
|
#if REX_PLATFORM_WIN32
|
||||||
|
OutputDebugStringA(msg);
|
||||||
|
HANDLE log_handle = GetStdHandle(STD_ERROR_HANDLE);
|
||||||
|
if (log_handle != INVALID_HANDLE_VALUE && log_handle != nullptr) {
|
||||||
|
DWORD written = 0;
|
||||||
|
WriteFile(log_handle, msg, DWORD(std::strlen(msg)), &written, nullptr);
|
||||||
|
}
|
||||||
|
TerminateProcess(GetCurrentProcess(), EXIT_SUCCESS);
|
||||||
|
#else
|
||||||
|
ssize_t written = write(STDERR_FILENO, msg, std::strlen(msg));
|
||||||
|
(void)written;
|
||||||
std::_Exit(EXIT_SUCCESS);
|
std::_Exit(EXIT_SUCCESS);
|
||||||
|
#endif
|
||||||
}).detach();
|
}).detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue