Merge pull request #1705 from muhzain05/fix/ult-finalize-busy

fix(ult): reject finalize with outstanding ulthreads
This commit is contained in:
Dean 2026-10-09 21:03:19 +13:00 committed by GitHub
commit cb6d833295
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 110 additions and 1 deletions

View File

@ -15,4 +15,17 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceUlt>")
endif()
endforeach()
add_test_executable(guest_ult_thread_tests ${LIBS_INCLUDE_DIR}/tests/GuestUltThread.cpp)
target_include_directories(guest_ult_thread_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_ult_thread_tests PRIVATE libSceUlt libc)
configure_windows_unwind(guest_ult_thread_tests)
foreach(testCase IN ITEMS outstanding validation)
add_test(NAME guest_ult_thread_${testCase} COMMAND guest_ult_thread_tests ${testCase})
set_tests_properties(guest_ult_thread_${testCase} PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_ult_thread_${testCase} PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceUlt>")
endif()
endforeach()
endif()

View File

@ -123,6 +123,9 @@ int APS5_VABI sceUltFinalize() {
std::vector<std::shared_ptr<UltSemaphoreState>> semaphores;
{
std::lock_guard<std::mutex> lock(gMutex);
if (!gUlthreads.empty()) {
return ULT_ERROR_BUSY;
}
semaphores.reserve(gSemaphores.size());
for (auto& entry : gSemaphores) {
auto& state = entry.second;
@ -143,7 +146,6 @@ int APS5_VABI sceUltFinalize() {
gQueues.clear();
gQueueDataPools.clear();
gRuntimes.clear();
gUlthreads.clear();
}
for (const auto& state : semaphores) {
state->_available.notify_all();

View File

@ -0,0 +1,94 @@
#include "SceTypes.hpp"
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <cstring>
#include <thread>
extern "C" {
int APS5_VABI sceUltInitialize();
int APS5_VABI sceUltFinalize();
std::uint64_t APS5_VABI sceUltUlthreadRuntimeGetWorkAreaSize(std::uint32_t, std::uint32_t);
int APS5_VABI sceUltUlthreadRuntimeCreate(void*, const char*, std::uint32_t, std::uint32_t, void*, const void*, std::uint32_t);
int APS5_VABI sceUltUlthreadCreate(void*, const char*, UltUlthreadEntry, std::uint64_t, void*, std::uint64_t, void*, const void*, std::uint32_t);
int APS5_VABI sceUltUlthreadJoin(void*, std::int32_t*);
}
static constexpr int Ok = 0;
static constexpr int Null = -2139029503;
static constexpr int State = -2139029498;
static constexpr int Busy = -2139029497;
static void Require(bool value, int line) {
if (!value) {
std::fprintf(stderr, "ULT thread check failed at line %d\n", line);
std::abort();
}
}
#define CHECK(value) Require((value), __LINE__)
static std::atomic<bool> gStarted{false};
static std::atomic<bool> gRelease{false};
static std::int32_t APS5_VABI Parked(std::uint64_t arg) {
gStarted.store(true, std::memory_order_release);
while (!gRelease.load(std::memory_order_acquire)) std::this_thread::yield();
return static_cast<std::int32_t>(arg);
}
static std::int32_t APS5_VABI Immediate(std::uint64_t arg) {
return static_cast<std::int32_t>(arg);
}
struct alignas(8) Runtime { std::uint8_t bytes[4096]{}; };
struct alignas(8) Thread { std::uint8_t bytes[512]{}; };
static void Outstanding() {
Runtime runtime;
Thread thread;
CHECK(sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr, 0) == Ok);
CHECK(sceUltUlthreadCreate(&thread, "parked", Parked, 0x5A, nullptr, 0, &runtime, nullptr, 0) == Ok);
while (!gStarted.load(std::memory_order_acquire)) std::this_thread::yield();
const int outstanding = sceUltFinalize();
gRelease.store(true, std::memory_order_release);
std::int32_t status = 0;
const int joined = sceUltUlthreadJoin(&thread, &status);
CHECK(outstanding == Busy);
CHECK(joined == Ok && status == 0x5A);
CHECK(sceUltUlthreadJoin(&thread, &status) == State);
CHECK(sceUltFinalize() == Ok);
}
static void Validation() {
Runtime runtime;
Thread thread;
gStarted.store(false, std::memory_order_release);
gRelease.store(false, std::memory_order_release);
CHECK(sceUltUlthreadRuntimeCreate(nullptr, "runtime", 1, 1, nullptr, nullptr, 0) == Null);
CHECK(sceUltUlthreadRuntimeCreate(&runtime, "runtime", 1, 1, nullptr, nullptr, 0) == Ok);
CHECK(sceUltUlthreadCreate(nullptr, "thread", Immediate, 0, nullptr, 0, &runtime, nullptr, 0) == Null);
CHECK(sceUltUlthreadCreate(&thread, "thread", nullptr, 0, nullptr, 0, &runtime, nullptr, 0) == Null);
CHECK(sceUltUlthreadCreate(&thread, "thread", Immediate, 0, nullptr, 0, nullptr, nullptr, 0) == Null);
CHECK(sceUltUlthreadJoin(nullptr, nullptr) == Null);
CHECK(sceUltUlthreadJoin(&thread, nullptr) == State);
CHECK(sceUltUlthreadCreate(&thread, "thread", Immediate, 0x27, nullptr, 0, &runtime, nullptr, 0) == Ok);
CHECK(sceUltUlthreadCreate(&thread, "thread", Immediate, 0x27, nullptr, 0, &runtime, nullptr, 0) == State);
CHECK(sceUltFinalize() == Busy);
std::int32_t status = 0;
CHECK(sceUltUlthreadJoin(&thread, &status) == Ok && status == 0x27);
CHECK(sceUltFinalize() == Ok);
CHECK(sceUltUlthreadRuntimeGetWorkAreaSize(1, 1) == 256u + 16u * 1024u);
}
int main(int argc, char** argv) {
CHECK(argc == 2);
CHECK(sceUltInitialize() == Ok);
if (std::strcmp(argv[1], "outstanding") == 0) Outstanding();
else if (std::strcmp(argv[1], "validation") == 0) Validation();
else CHECK(false);
}