From 529dbb04802814a48c1bf21b9762c37a30449315 Mon Sep 17 00:00:00 2001 From: Evgeniy Kazarin Date: Sun, 18 Oct 2026 04:27:21 -0400 Subject: [PATCH 1/2] fix(agc): preserve pipeline caches on immediate guest exit --- .../Execution/include/VulkanDevice.hpp | 1 + .../Execution/src/Driver/Lifecycle.cpp | 4 ++ .../Execution/src/VulkanDevice.cpp | 4 ++ .../prx/libSceSystemService/CMakeLists.txt | 4 +- core/libs/prx/libc/include/Shutdown.hpp | 1 + core/libs/prx/libc/src/Process.cpp | 28 +++++++++++++ core/libs/tests/GuestSystemExit.cpp | 40 +++++++++++++++++++ 7 files changed, 81 insertions(+), 1 deletion(-) diff --git a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp index cc6d4a0e285..260f7737685 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp +++ b/core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp @@ -45,6 +45,7 @@ class VulkanDevice { // Starts the journal replay if the device has not (a headless device starts it at its first // draw or dispatch) and joins it (tests; the teardown joins it anyway). void WaitForPipelineJournalReplay(); + void SavePipelineCache(); // Compute pipelines this device built, by dispatches and by the journal replay (tests). std::uint64_t ComputePipelinesCreated() const; ~VulkanDevice(); diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp index b5928fd583c..ff17f54fce6 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Lifecycle.cpp @@ -182,6 +182,10 @@ Driver::Driver() { InstallAsyncLog_nid_no_patch(); static const bool driverMemory = (MapDriverMemory(), true); static_cast(driverMemory); + LibcSetExitDrain_nid_no_patch([] { + const auto current = Driver::Get().device.Load(); + if (current) current->SavePipelineCache(); + }); static_cast(GuestMemory::TrackerGeneration()); ShaderMemory::SetWaitedMsProvider(&Graphics::Recorder::ThreadWaitedMs); try { diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp index 4b0505b3e5e..6160bd7754a 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp @@ -2044,6 +2044,10 @@ void VulkanDevice::WaitForPipelineJournalReplay() { if (state->journalReplay != nullptr) state->journalReplay->Wait(); } +void VulkanDevice::SavePipelineCache() { + if (state->pipelineCache) state->pipelineCache->SaveNow(); +} + std::uint64_t VulkanDevice::ComputePipelinesCreated() const { return state->computePipelinesCreated.load(std::memory_order_relaxed); } diff --git a/core/libs/prx/libSceSystemService/CMakeLists.txt b/core/libs/prx/libSceSystemService/CMakeLists.txt index 78aa978dada..f824aa17217 100644 --- a/core/libs/prx/libSceSystemService/CMakeLists.txt +++ b/core/libs/prx/libSceSystemService/CMakeLists.txt @@ -38,6 +38,8 @@ if(BUILD_TESTING) add_test(NAME guest_requested_exit COMMAND guest_system_exit_tests --requested-exit-driver) add_test(NAME guest_quick_exit COMMAND guest_system_exit_tests --quick-exit) add_test(NAME guest_immediate_exit COMMAND guest_system_exit_tests --immediate) + add_test(NAME guest_immediate_exit_drain COMMAND guest_system_exit_tests --exit-drain-driver) + set_tests_properties(guest_immediate_exit_drain PROPERTIES TIMEOUT 20) add_test(NAME guest_return_from_main COMMAND guest_system_exit_tests --return-from-main) add_test(NAME guest_system_relaunch COMMAND guest_system_exit_tests --relaunch-driver) add_test(NAME guest_nested_exit COMMAND guest_system_exit_tests --nested-exit-driver) @@ -62,7 +64,7 @@ if(BUILD_TESTING) set_tests_properties(guest_system_load_exec_standard_error PROPERTIES TIMEOUT 20) endif() if(WIN32) - set_tests_properties(guest_system_exit guest_requested_exit guest_system_exit_parameters guest_immediate_exit guest_return_from_main guest_quick_exit guest_system_relaunch guest_nested_exit guest_exit_finalizers_before_shutdown PROPERTIES ENVIRONMENT_MODIFICATION + set_tests_properties(guest_system_exit guest_requested_exit guest_system_exit_parameters guest_immediate_exit guest_immediate_exit_drain guest_return_from_main guest_quick_exit guest_system_relaunch guest_nested_exit guest_exit_finalizers_before_shutdown PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() add_test_executable(guest_system_service_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemService.cpp) diff --git a/core/libs/prx/libc/include/Shutdown.hpp b/core/libs/prx/libc/include/Shutdown.hpp index 8e0f9ec2093..a59aa05cedb 100644 --- a/core/libs/prx/libc/include/Shutdown.hpp +++ b/core/libs/prx/libc/include/Shutdown.hpp @@ -17,5 +17,6 @@ extern "C" [[noreturn]] void LibcExitWithoutHandlers_nid_no_patch(int code); extern "C" [[noreturn]] void LibcTerminate_nid_no_patch(int code); extern "C" void LibcSetEndFlush_nid_no_patch(void (*flush)()); extern "C" void LibcRunEndFlush_nid_no_patch(); +extern "C" void LibcSetExitDrain_nid_no_patch(void (*drain)()); #endif diff --git a/core/libs/prx/libc/src/Process.cpp b/core/libs/prx/libc/src/Process.cpp index e6d58a919f6..9d2f6909b0b 100644 --- a/core/libs/prx/libc/src/Process.cpp +++ b/core/libs/prx/libc/src/Process.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -100,6 +101,7 @@ extern "C" void LibcRunShutdown_nid_postfix() { namespace { std::atomic endFlush{nullptr}; +std::atomic exitDrain{nullptr}; void (*previousAbortHandler)(int) = SIG_DFL; void FlushOnAbort(int number) { @@ -129,6 +131,12 @@ extern "C" void LibcRunEndFlush_nid_no_patch() { if (const auto flush = endFlush.load(std::memory_order_acquire)) flush(); } +extern "C" void LibcSetExitDrain_nid_no_patch(void (*drain)()) { + if (drain == nullptr) throw std::runtime_error("libc: invalid exit drain registration"); + void (*registered)() = nullptr; + if (!exitDrain.compare_exchange_strong(registered, drain) && registered != drain) throw std::runtime_error("libc: exit drain already registered"); +} + namespace { void StartExit() { @@ -154,6 +162,26 @@ extern "C" { } [[noreturn]] void LibcTerminate_nid_no_patch(int code) { + if (const auto drain = exitDrain.exchange(nullptr)) { + try { + auto completion = std::make_shared>(); + auto finished = completion->get_future(); + std::thread([completion, drain] { + try { + drain(); + completion->set_value(); + } catch (...) { + completion->set_exception(std::current_exception()); + } + }).detach(); + if (finished.wait_for(std::chrono::seconds(8)) == std::future_status::ready) finished.get(); + else std::fputs("[libc] host exit drain exceeded 8 seconds\n", stderr); + } catch (const std::exception& error) { + std::fprintf(stderr, "[libc] host exit drain failed: %s\n", error.what()); + } catch (...) { + std::fputs("[libc] host exit drain failed\n", stderr); + } + } LibcRunEndFlush_nid_no_patch(); #ifdef _WIN32 TerminateProcess(GetCurrentProcess(), static_cast(code)); diff --git a/core/libs/tests/GuestSystemExit.cpp b/core/libs/tests/GuestSystemExit.cpp index 8fa69824854..c4c81c056e1 100644 --- a/core/libs/tests/GuestSystemExit.cpp +++ b/core/libs/tests/GuestSystemExit.cpp @@ -98,6 +98,15 @@ void SecondaryShutdown() { AppendMarker("secondary shutdown"); } void APS5_VABI RemainingHandler(void*) { AppendMarker("remaining handler"); } void APS5_VABI ExitingHandler(void*) { exit_nid_postfix(7); } void GuestExitShutdown() { AppendMarker("shutdown"); } +void DelayedExitDrain() { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + AppendMarker("drain"); +} +void ExitFlushAfterDrain() { AppendMarker("flush"); } +void ThrowingExitDrain() { throw std::runtime_error("exit drain test failure"); } +void BlockedExitDrain() { + for (;;) std::this_thread::sleep_for(std::chrono::seconds(1)); +} void APS5_VABI CheckShutdownStillRunning(void*) { if (LibcShutdownToken_nid_postfix().stop_requested()) std::_Exit(8); AppendMarker("guest finalizer"); @@ -183,9 +192,40 @@ int DriveRelaunch() { std::puts("Guest relaunch completed"); return 0; } + +int DriveExitDrain() { + const auto self = OwnExecutable(); + const auto marker = std::filesystem::temp_directory_path() / ("anyps5-exit-drain-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".txt"); + SetVariable(RelaunchMarker, marker.string().c_str()); + Require(RunChild(self, "--exit-drain") == 7); + std::ifstream file(marker); + const std::string text{std::istreambuf_iterator(file), std::istreambuf_iterator()}; + file.close(); + std::filesystem::remove(marker); + Require(text == "drain\nflush\n"); + Require(RunChild(self, "--exit-drain-throws") == 7); + const auto started = std::chrono::steady_clock::now(); + Require(RunChild(self, "--exit-drain-blocked") == 7); + const auto elapsed = std::chrono::steady_clock::now() - started; + Require(elapsed >= std::chrono::seconds(8) && elapsed < std::chrono::seconds(12)); + std::puts("Host exit drain completed before immediate termination and stayed bounded"); + return 0; +} } int main(int argc, char** argv) { const int firstArgument = argc > 0 && std::strncmp(argv[0], "--", 2) == 0 ? 0 : 1; + if (argc > firstArgument && std::strcmp(argv[firstArgument], "--exit-drain-driver") == 0) return DriveExitDrain(); + if (argc > firstArgument && std::strncmp(argv[firstArgument], "--exit-drain", 12) == 0) { + LibcRegisterShutdown_nid_postfix(UnexpectedCleanup); + Require(std::atexit(UnexpectedCleanup) == 0); + if (std::strcmp(argv[firstArgument], "--exit-drain-throws") == 0) LibcSetExitDrain_nid_no_patch(ThrowingExitDrain); + else if (std::strcmp(argv[firstArgument], "--exit-drain-blocked") == 0) LibcSetExitDrain_nid_no_patch(BlockedExitDrain); + else { + LibcSetExitDrain_nid_no_patch(DelayedExitDrain); + LibcSetEndFlush_nid_no_patch(ExitFlushAfterDrain); + } + _Exit_nid_postfix(7); + } if (argc > firstArgument && std::strcmp(argv[firstArgument], "--requested-exit-driver") == 0) { Require(RunChild(OwnExecutable(), "--requested-exit") == 0); return 0; From 2f5282560358c1485c593bdbde7bc2691ca3dde0 Mon Sep 17 00:00:00 2001 From: Evgeniy Kazarin Date: Sun, 18 Oct 2026 04:53:15 -0400 Subject: [PATCH 2/2] chore: record UI Lab PR metadata Complete the publication checklist and series cross-links for #7474. No source or test changes.