kernel: suppress async IPC wakeups during shutdown

Async IPC continuations can finish while System::Shutdown is tearing down service, kernel, and timing state. The dump showed an HTTP ReceiveData RunAsync task calling Thread::WakeAfterDelay after shutdown had begun, which reached Core::Timing::ScheduleEvent with invalid timing state and crashed.

Mark KernelSystem as shutting down at the start of System::Shutdown and in the kernel destructor. Route RunAsync and RunOnThreadWorker completions through HLERequestContext::WakeAfterAsync, which skips scheduling guest-thread wakes once shutdown starts and balances the pending async counter instead.

Validation: git diff --check
This commit is contained in:
Masamune3210 2026-05-14 19:21:59 -05:00
parent f2250fa485
commit d19e6086fa
5 changed files with 26 additions and 2 deletions

View file

@ -692,6 +692,9 @@ void System::Shutdown(bool is_deserializing) {
// Shutdown emulation session
is_powered_on = false;
if (kernel) {
kernel->BeginShutdown();
}
gpu.reset();
if (!is_deserializing) {

View file

@ -135,6 +135,15 @@ HLERequestContext::HLERequestContext(KernelSystem& kernel, std::shared_ptr<Serve
HLERequestContext::~HLERequestContext() = default;
void HLERequestContext::WakeAfterAsync(s64 sleep_for) {
if (kernel.IsShuttingDown()) {
kernel.ReportAsyncState(false);
return;
}
thread->WakeAfterDelay(sleep_for, true);
}
std::shared_ptr<Object> HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const {
ASSERT(id_from_cmdbuf < request_handles.size());
return request_handles[id_from_cmdbuf];

View file

@ -259,6 +259,8 @@ public:
std::shared_ptr<WakeupCallback> callback);
private:
void WakeAfterAsync(s64 sleep_for);
template <typename ResultFunctor>
class AsyncWakeUpCallback : public WakeupCallback {
public:
@ -311,7 +313,7 @@ public:
kernel, result_function,
std::move(std::async(std::launch::async, [this, async_section] {
s64 sleep_for = async_section(*this);
this->thread->WakeAfterDelay(sleep_for, true);
WakeAfterAsync(sleep_for);
}))));
} else {
@ -351,7 +353,7 @@ public:
// We use packaged_task so we can retrieve a std::future to pass to AsyncWakeUpCallback
auto task = std::make_shared<std::packaged_task<void()>>([this, async_section] {
s64 sleep_for = async_section(*this);
this->thread->WakeAfterDelay(sleep_for, true);
WakeAfterAsync(sleep_for);
});
auto future = task->get_future();

View file

@ -48,6 +48,7 @@ KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
/// Shutdown the kernel
KernelSystem::~KernelSystem() {
BeginShutdown();
ResetThreadIDs();
};

View file

@ -410,6 +410,14 @@ public:
return pending_async_operations != 0;
}
void BeginShutdown() {
shutting_down.store(true, std::memory_order_release);
}
bool IsShuttingDown() const {
return shutting_down.load(std::memory_order_acquire);
}
void UpdateCPUAndMemoryState(u64 title_id, MemoryMode memory_mode,
New3dsHwCapabilities n3ds_hw_cap);
@ -431,6 +439,7 @@ private:
std::atomic<u32> next_object_id{0};
std::atomic<int> pending_async_operations{};
std::atomic<bool> shutting_down{false};
// Note: keep the member order below in order to perform correct destruction.
// Thread manager is destructed before process list in order to Stop threads and clear thread