mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
fix(libkernel): retire delivery-drain posts against a finished target
QueueForDeliveryDrain ran after ResumeThread with no coordination with the target's final ReapDeliveryDrain: a target could exit between resume and enqueue, leaving the post in a box nothing would drain again and retaining the QueuedDelivery reference. Admission is now decided under drainBoxesLock against the target's _finished flag, which is stored before the final reap in program order; denied posts run their callback inline so the entry and the reference are resolved exactly once on either path. The GuestRaiseException regression arms a post hook, holds the producer between resume and enqueue, lets the target finish and reap, then completes the post and requires the drain to stay empty; the churn rounds also assert the post hook observed mailbox posts alongside the delivered raises.
This commit is contained in:
1 parent
7ff3b7091b
commit
2189fb06ea
4 files changed
+63
-5
No files matched your search
@@ -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<void>(ClassifyImage(image));
|
||||
continue;
|
||||
|
||||
@@ -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<void (*)()>& DrainPostHook() {
|
||||
static std::atomic<void (*)()> hook{nullptr};
|
||||
return hook;
|
||||
}
|
||||
|
||||
void SetDeliveryDrainPostHook(void (*hook)()) {
|
||||
DrainPostHook().store(hook, std::memory_order_release);
|
||||
}
|
||||
|
||||
void QueueForDeliveryDrain(unsigned long thread, const std::atomic<bool>* 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() {
|
||||
|
||||
@@ -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<bool>* targetFinished, void (*run)(void*), void* argument);
|
||||
void ReapDeliveryDrain();
|
||||
std::size_t DeliveryDrainPending();
|
||||
void SetDeliveryDrainPostHook(void (*hook)());
|
||||
|
||||
constexpr unsigned PARK_SPINS = 1000;
|
||||
|
||||
|
||||
@@ -308,6 +308,14 @@ static void* APS5_VABI ChurnUntil(void* arg) {
|
||||
while (!exitingRelease.load()) MultiByteToWideChar(CP_UTF8, 0, source.data(), static_cast<int>(source.size()), converted.data(), static_cast<int>(converted.size()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static std::atomic<int> postEntered{0};
|
||||
static std::atomic<bool> postHold{false};
|
||||
|
||||
static void DeliveryPostHook() {
|
||||
postEntered.fetch_add(1);
|
||||
while (postHold.load()) std::this_thread::yield();
|
||||
}
|
||||
#endif
|
||||
|
||||
static std::atomic<std::thread::id> 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<int> 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);
|
||||
|
||||
Reference in new issue
Block a user