diff --git a/core/libs/prx/libkernel/System/src/Exception.cpp b/core/libs/prx/libkernel/System/src/Exception.cpp index a1314a8a74..095f8b5ad9 100644 --- a/core/libs/prx/libkernel/System/src/Exception.cpp +++ b/core/libs/prx/libkernel/System/src/Exception.cpp @@ -784,7 +784,7 @@ Attempt TryDeliver(Pthread thread, HANDLE native, GuestExceptionHandler handler, DWORD64 unknown = 0; if (arm != nullptr && running == CodeKind::Host && !restoring) ArmReturnBreak(native, delivery.context, queued, *arm, unknown); ResumeThread(native); - if (postAfterResume) TimedWait::QueueForDeliveryDrain(GetThreadId(native), &DeferredWaitingEntry, queued); + if (postAfterResume) TimedWait::QueueForDeliveryDrain(GetThreadId(native), &thread->_finished, &DeferredWaitingEntry, queued); if (running == CodeKind::Unknown) { static_cast(ClassifyImage(image)); continue; diff --git a/core/libs/prx/libkernel/Time/TimedWait.cpp b/core/libs/prx/libkernel/Time/TimedWait.cpp index 81f6958169..299ba46b1b 100644 --- a/core/libs/prx/libkernel/Time/TimedWait.cpp +++ b/core/libs/prx/libkernel/Time/TimedWait.cpp @@ -170,9 +170,26 @@ struct Waiter { } }; -void QueueForDeliveryDrain(unsigned long thread, void (*run)(void*), void* argument) { - const std::lock_guard lock(drainBoxesLock); - drainBoxes[thread].push_back({run, argument}); +std::atomic& DrainPostHook() { + static std::atomic hook{nullptr}; + return hook; +} + +void SetDeliveryDrainPostHook(void (*hook)()) { + DrainPostHook().store(hook, std::memory_order_release); +} + +void QueueForDeliveryDrain(unsigned long thread, const std::atomic* targetFinished, void (*run)(void*), void* argument) { + if (const auto hook = DrainPostHook().load(std::memory_order_acquire)) hook(); + bool queued = false; + { + const std::lock_guard lock(drainBoxesLock); + if (targetFinished == nullptr || !targetFinished->load(std::memory_order_acquire)) { + drainBoxes[thread].push_back({run, argument}); + queued = true; + } + } + if (!queued) run(argument); } void ReapDeliveryDrain() { diff --git a/core/libs/prx/libkernel/Time/include/TimedWait.hpp b/core/libs/prx/libkernel/Time/include/TimedWait.hpp index f81890fb58..e072e1a860 100644 --- a/core/libs/prx/libkernel/Time/include/TimedWait.hpp +++ b/core/libs/prx/libkernel/Time/include/TimedWait.hpp @@ -39,9 +39,10 @@ public: void DeliverDeferred(); #ifdef _WIN32 -void QueueForDeliveryDrain(unsigned long thread, void (*run)(void*), void* argument); +void QueueForDeliveryDrain(unsigned long thread, const std::atomic* targetFinished, void (*run)(void*), void* argument); void ReapDeliveryDrain(); std::size_t DeliveryDrainPending(); +void SetDeliveryDrainPostHook(void (*hook)()); constexpr unsigned PARK_SPINS = 1000; diff --git a/core/libs/tests/GuestRaiseException.cpp b/core/libs/tests/GuestRaiseException.cpp index 4c7e245c67..5079190b40 100644 --- a/core/libs/tests/GuestRaiseException.cpp +++ b/core/libs/tests/GuestRaiseException.cpp @@ -308,6 +308,14 @@ static void* APS5_VABI ChurnUntil(void* arg) { while (!exitingRelease.load()) MultiByteToWideChar(CP_UTF8, 0, source.data(), static_cast(source.size()), converted.data(), static_cast(converted.size())); return nullptr; } + +static std::atomic postEntered{0}; +static std::atomic postHold{false}; + +static void DeliveryPostHook() { + postEntered.fetch_add(1); + while (postHold.load()) std::this_thread::yield(); +} #endif static std::atomic spinningId; @@ -742,6 +750,8 @@ int main() { Worker churning; Pthread churningThread = nullptr; + TimedWait::SetDeliveryDrainPostHook(&DeliveryPostHook); + const int postsBeforeChurn = postEntered.load(); Require(scePthreadCreate(&churningThread, nullptr, Churning, &churning, "churn then drain") == 0); for (int round = 0; round < DrainRounds; ++round) { while (!churning.started.load()) std::this_thread::yield(); @@ -753,6 +763,7 @@ int main() { ExpectDelivery(before, churning.id); } Require(scePthreadJoin(churningThread, nullptr) == 0); + Require(postEntered.load() > postsBeforeChurn); for (int round = 0; round < ExitRounds; ++round) { exitingRelease.store(false); @@ -767,6 +778,35 @@ int main() { Require(scePthreadJoin(exitingThread, nullptr) == 0); Require(TimedWait::DeliveryDrainPending() == 0); } + + exitingRelease.store(false); + postHold.store(true); + const int postsBeforeHeld = postEntered.load(); + Worker heldExit; + Pthread heldExitThread = nullptr; + Require(scePthreadCreate(&heldExitThread, nullptr, ChurnUntil, &heldExit, "exit while posted") == 0); + while (!heldExit.started.load()) std::this_thread::yield(); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + std::atomic raiseResult{-1}; + bool posted = false; + for (int attempt = 0; attempt < 50 && !posted; ++attempt) { + raiseResult.store(-1); + std::thread raiser([&] { raiseResult.store(sceKernelRaiseException(heldExitThread, GUEST_SIGUSR1)); }); + const auto entered = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (postEntered.load() == postsBeforeHeld && raiseResult.load() == -1 && std::chrono::steady_clock::now() < entered) std::this_thread::yield(); + posted = postEntered.load() > postsBeforeHeld; + if (posted) { + exitingRelease.store(true); + Require(scePthreadJoin(heldExitThread, nullptr) == 0); + postHold.store(false); + } + raiser.join(); + } + Require(posted); + Require(raiseResult.load() == 0 || raiseResult.load() == SCE_KERNEL_ERROR_ESRCH); + const auto drained = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (TimedWait::DeliveryDrainPending() != 0 && std::chrono::steady_clock::now() < drained) std::this_thread::yield(); + Require(TimedWait::DeliveryDrainPending() == 0); std::this_thread::sleep_for(std::chrono::milliseconds(200)); const int settledCalls = calls.load(); drainingRound.store(0);