fix(agc): drain the registration preparation pool before the device is reset
Background registration preparations (#2583) copy the device's SpirvTarget, whose capability and extension spans point into the device's shader profile. Driver::stop reset the device without waiting for the detached pool, so a preparation still queued or running read freed extension names and agc_driver crashed at exit on RADV. Driver::stop now closes the pool after joining the queue workers: it stops queueing and waits until no task is queued or running. A preparation submitted after that runs on the caller's thread, which holds its own reference to the device, like the existing fallback for a pool without threads. Fixes #2891 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
383851643f
commit
2dd453dae0
|
|
@ -10,6 +10,7 @@
|
|||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
|
@ -92,6 +93,8 @@ ShaderSnapshot PrepareNullPixelProgram(const VulkanDevice& device);
|
|||
std::optional<ShaderRecompiler::ShaderFloatMode> RegisteredFloatMode(const ShaderSnapshot& snapshot);
|
||||
std::shared_ptr<const ShaderSnapshot> RegisteredProgram(const ShaderRegistry& registry, std::uint64_t address, std::initializer_list<std::uint8_t> types);
|
||||
void PublishRegisteredShader(std::shared_ptr<ShaderRegistry>& registry, const std::shared_ptr<const ShaderSnapshot>& snapshot);
|
||||
void SubmitRegistrationPreparation(std::function<void()> task);
|
||||
void CloseRegistrationPreparation();
|
||||
|
||||
void ResolvePreparedGraphics(const ShaderSnapshot& front, const std::shared_ptr<const ShaderSnapshot>& fragment, std::uint32_t primitiveType, const ShaderRecompiler::SpirvTarget& target);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Diagnostics.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/WorkerSampler.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/ProfileOutput.hpp"
|
||||
|
|
@ -44,6 +45,7 @@ void Driver::stop() {
|
|||
if (worker.thread.joinable()) worker.thread.join();
|
||||
}
|
||||
StopWorkerSampler();
|
||||
CloseRegistrationPreparation();
|
||||
std::lock_guard gpuLock(GuestMemory::GpuMutex());
|
||||
device.Reset();
|
||||
replacedDevices.clear();
|
||||
|
|
|
|||
|
|
@ -551,15 +551,22 @@ public:
|
|||
}
|
||||
|
||||
void Submit(std::function<void()> task) {
|
||||
if (threads == 0) {
|
||||
task();
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
tasks.push_back(std::move(task));
|
||||
std::unique_lock lock(mutex);
|
||||
if (threads != 0 && !closed) {
|
||||
tasks.push_back(std::move(task));
|
||||
lock.unlock();
|
||||
ready.notify_one();
|
||||
return;
|
||||
}
|
||||
}
|
||||
ready.notify_one();
|
||||
task();
|
||||
}
|
||||
|
||||
void Close() {
|
||||
std::unique_lock lock(mutex);
|
||||
closed = true;
|
||||
idle.wait(lock, [&] { return tasks.empty() && running == 0; });
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
@ -587,16 +594,26 @@ private:
|
|||
pool.ready.wait(lock, [&] { return !pool.tasks.empty(); });
|
||||
task = std::move(pool.tasks.front());
|
||||
pool.tasks.pop_front();
|
||||
++pool.running;
|
||||
}
|
||||
task();
|
||||
task = nullptr;
|
||||
{
|
||||
std::lock_guard lock(pool.mutex);
|
||||
--pool.running;
|
||||
if (pool.tasks.empty() && pool.running == 0) pool.idle.notify_all();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::mutex mutex;
|
||||
std::condition_variable ready;
|
||||
std::condition_variable idle;
|
||||
std::deque<std::function<void()>> tasks;
|
||||
unsigned threads = 0;
|
||||
unsigned running = 0;
|
||||
bool closed = false;
|
||||
};
|
||||
|
||||
bool AsyncRegistrationPrepare() {
|
||||
|
|
@ -605,7 +622,7 @@ bool AsyncRegistrationPrepare() {
|
|||
}
|
||||
|
||||
void PrepareRegisteredInBackground(std::shared_ptr<const ShaderSnapshot> snapshot, std::shared_ptr<RegisteredPreparation> plan) {
|
||||
RegistrationPreparePool::Instance().Submit([snapshot = std::move(snapshot), plan = std::move(plan)] {
|
||||
SubmitRegistrationPreparation([snapshot = std::move(snapshot), plan = std::move(plan)] {
|
||||
PerformanceContext timingContext(FrameTiming::Preparation());
|
||||
std::vector<PreparedShaders::Entry> entries;
|
||||
std::exception_ptr failure;
|
||||
|
|
@ -631,6 +648,14 @@ void PrepareRegisteredInBackground(std::shared_ptr<const ShaderSnapshot> snapsho
|
|||
|
||||
}
|
||||
|
||||
void SubmitRegistrationPreparation(std::function<void()> task) {
|
||||
RegistrationPreparePool::Instance().Submit(std::move(task));
|
||||
}
|
||||
|
||||
void CloseRegistrationPreparation() {
|
||||
RegistrationPreparePool::Instance().Close();
|
||||
}
|
||||
|
||||
std::vector<PreparedGraphicsStage> PrepareGraphicsStages(const DrawDecode& decoded, const ShaderRecompiler::SpirvTarget& target) {
|
||||
ShaderPreparationTransaction transaction;
|
||||
require(decoded.programs.size() == decoded.roles.size(), "graphics ABI program roles are incomplete");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/Driver/Driver.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/Driver/Shaders/ShaderRegistry.hpp"
|
||||
#include "prx/libc/include/Shutdown.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
|
||||
void Require(bool condition, const std::string& message) {
|
||||
if (!condition) throw std::runtime_error("registration prepare shutdown: " + message);
|
||||
}
|
||||
|
||||
void CheckShutdownWaits() {
|
||||
std::mutex mutex;
|
||||
std::condition_variable changed;
|
||||
bool started = false;
|
||||
bool released = false;
|
||||
std::atomic<bool> finished{false};
|
||||
std::atomic<bool> returned{false};
|
||||
std::atomic<bool> finishedAtReturn{false};
|
||||
AgcDriver::DriverDetail::Driver::Get();
|
||||
AgcDriver::DriverDetail::SubmitRegistrationPreparation([&] {
|
||||
std::unique_lock lock(mutex);
|
||||
started = true;
|
||||
changed.notify_all();
|
||||
changed.wait(lock, [&] { return released; });
|
||||
finished.store(true);
|
||||
});
|
||||
{
|
||||
std::unique_lock lock(mutex);
|
||||
changed.wait(lock, [&] { return started; });
|
||||
}
|
||||
std::thread shutdown([&] {
|
||||
LibcRunShutdown_nid_postfix();
|
||||
finishedAtReturn.store(finished.load());
|
||||
returned.store(true);
|
||||
});
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
const bool returnedEarly = returned.load();
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
released = true;
|
||||
}
|
||||
changed.notify_all();
|
||||
shutdown.join();
|
||||
Require(!returnedEarly, "shutdown returned while a registration preparation was still running");
|
||||
Require(finishedAtReturn.load(), "shutdown returned before the running registration preparation finished");
|
||||
}
|
||||
|
||||
void CheckLateSubmissionRunsInline() {
|
||||
const auto caller = std::this_thread::get_id();
|
||||
std::thread::id ran;
|
||||
AgcDriver::DriverDetail::SubmitRegistrationPreparation([&] { ran = std::this_thread::get_id(); });
|
||||
Require(ran == caller, "a registration preparation submitted after shutdown did not run on the caller's thread");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
CheckShutdownWaits();
|
||||
CheckLateSubmissionRunsInline();
|
||||
std::puts("registration prepare shutdown tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue