From d42f34d3de0860c191655de3ce114626bac4831e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 19 Oct 2026 21:41:48 +0200 Subject: [PATCH] Kernel: Keep alarm and vtimer queues in step with their pending interrupts Each due alarm or vtimer queues its id next to a pending interrupt, which pops it when it runs. An alarm cancelled with its interrupt pending was never popped, so every later alarm interrupt found the cancelled one at the front and was dropped: no alarm ran again. Deleting a vtimer erased its id instead, leaving the last pending interrupt with an empty list. Both now pop a timer that's gone when its interrupt comes, as threads/alarm/pending and threads/vtimers/pending show the PSP still runs the rest, in order. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/HLE/sceKernelAlarm.cpp | 5 +++++ Core/HLE/sceKernelVTimer.cpp | 19 +++++++++++-------- pspautotests | 2 +- test.py | 2 ++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Core/HLE/sceKernelAlarm.cpp b/Core/HLE/sceKernelAlarm.cpp index ac66523265..da59a6d8f9 100644 --- a/Core/HLE/sceKernelAlarm.cpp +++ b/Core/HLE/sceKernelAlarm.cpp @@ -70,12 +70,17 @@ public: bool run(PendingInterrupt& pend) override { u32 error; + if (triggeredAlarm.empty()) { + return false; + } int alarmID = triggeredAlarm.front(); PSPAlarm *alarm = kernelObjects.Get(alarmID, error); if (error) { + // handleResult won't run, so the alarm is popped here, or every later alarm runs this one's entry. WARN_LOG(Log::sceKernel, "Ignoring deleted alarm %08x", alarmID); + triggeredAlarm.pop_front(); return false; } diff --git a/Core/HLE/sceKernelVTimer.cpp b/Core/HLE/sceKernelVTimer.cpp index 51f1bc9821..2b128ea220 100644 --- a/Core/HLE/sceKernelVTimer.cpp +++ b/Core/HLE/sceKernelVTimer.cpp @@ -139,12 +139,18 @@ public: bool run(PendingInterrupt &pend) override { u32 error; + if (vtimers.empty()) { + return false; + } SceUID vtimerID = vtimers.front(); VTimer *vtimer = kernelObjects.Get(vtimerID, error); - - if (error) + if (error) { + // Deleted while its interrupt was pending. handleResult won't run, so it's popped here. + WARN_LOG(Log::sceKernel, "Ignoring deleted vtimer %08x", vtimerID); + vtimers.pop_front(); return false; + } // Reserve some stack space for arguments. u32 argArea = currentMIPS->r[MIPS_REG_SP]; @@ -246,12 +252,9 @@ u32 sceKernelDeleteVTimer(SceUID uid) { return hleLogError(Log::sceKernel, error, "bad timer ID"); } - for (std::list::iterator it = vtimers.begin(); it != vtimers.end(); ++it) { - if (*it == vt->GetUID()) { - vtimers.erase(it); - break; - } - } + // Its entry in vtimers stays: each one belongs to a pending interrupt, which pops it when it runs + // (or right away, finding the timer gone), so erasing it here would hand that interrupt to the + // next timer's entry, or to an empty list if the handler is deleting its own timer. return hleLogDebugOrError(Log::sceKernel, kernelObjects.Destroy(uid)); } diff --git a/pspautotests b/pspautotests index 2533d2d44c..aa60f4bfec 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 2533d2d44c94de8808024bf079111c44accc9965 +Subproject commit aa60f4bfecacb1abe8fcbe71e6ead1b6a3de5745 diff --git a/test.py b/test.py index 46607b9718..69651d8b51 100755 --- a/test.py +++ b/test.py @@ -371,6 +371,7 @@ tests_good = [ "sysmem/volatile", "threads/alarm/alarm", "threads/alarm/cancel/cancel", + "threads/alarm/pending/pending", "threads/alarm/refer/refer", "threads/alarm/set/set", "threads/callbacks/afterwait", @@ -508,6 +509,7 @@ tests_good = [ "threads/vtimers/getbase", "threads/vtimers/gettime", "threads/vtimers/interrupt", + "threads/vtimers/pending/pending", "threads/vtimers/refer", "threads/vtimers/sethandler", "threads/vtimers/settime",