From f9625f25812e20e0066e43ffda1dc9edfcd8bdc5 Mon Sep 17 00:00:00 2001 From: DotDebian Date: Fri, 2 Oct 2026 12:17:19 +0200 Subject: [PATCH] feat(libkernel): implement the pthread_rwlock try and timed lock functions The POSIX layer only exported init, destroy, rdlock, wrlock and unlock. pthread_rwlock_tryrdlock, pthread_rwlock_trywrlock, pthread_rwlock_timedrdlock and pthread_rwlock_timedwrlock now wrap the existing scePthreadRwlock functions and accept the static initializer. The timed variants take an absolute CLOCK_REALTIME deadline. Like FreeBSD libthr they try the lock first and only validate abstime when the lock is not free. A null abstime throws, as pthread_mutex_timedlock does. scePthreadRwlockTryrdlock and scePthreadRwlockTrywrlock return EBUSY when the caller already holds the write lock instead of calling into std::shared_timed_mutex, where that is undefined behaviour. Co-Authored-By: Claude Opus 5.5 --- core/libs/prx/libkernel/CMakeLists.txt | 10 ++ .../prx/libkernel/Pthread/Posix/Rwlock.cpp | 31 +++++ .../prx/libkernel/Pthread/include/Rwlock.hpp | 2 + .../libs/prx/libkernel/Pthread/src/Rwlock.cpp | 3 +- core/libs/tests/GuestPthreadRwlock.cpp | 131 ++++++++++++++++++ 5 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 core/libs/tests/GuestPthreadRwlock.cpp diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 4c758836a..ae5cb4714 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -196,6 +196,16 @@ if(BUILD_TESTING) set_tests_properties(guest_semaphore PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_pthread_rwlock_tests ${LIBS_INCLUDE_DIR}/tests/GuestPthreadRwlock.cpp) + target_include_directories(guest_pthread_rwlock_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_pthread_rwlock_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_pthread_rwlock_tests) + add_test(NAME guest_pthread_rwlock COMMAND guest_pthread_rwlock_tests) + set_tests_properties(guest_pthread_rwlock PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_pthread_rwlock PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_signal_tests ${LIBS_INCLUDE_DIR}/tests/GuestSignals.cpp) target_include_directories(guest_signal_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_signal_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp index 0e85208eb..9a79b5ecb 100644 --- a/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/Posix/Rwlock.cpp @@ -4,12 +4,15 @@ #include "prx/libc/include/General.hpp" #include "../include/Pthread.hpp" #include "../include/Rwlock.hpp" +#include "Common.hpp" #include #include #include namespace { +constexpr int GUEST_REALTIME_CLOCK = 0; + int toPosix(int result) { if (result == 0) return 0; @@ -48,6 +51,34 @@ int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock* rwlock) { return toPosix(scePthreadRwlockRdlock(rwlock)); } +int APS5_VABI pthread_rwlock_timedrdlock_nid_postfix(PthreadRwlock* rwlock, const KernelTimespec* abstime) { + if (!abstime) throw std::runtime_error("pthread_rwlock_timedrdlock: null abstime"); + initializeStatic(rwlock, __func__); + if (scePthreadRwlockTryrdlock(rwlock) == 0) return 0; + KernelUseconds usec = 0; + if (!PosixThread::RelativeMicroseconds(GUEST_REALTIME_CLOCK, abstime, &usec)) return PosixThread::GUEST_EINVAL; + return toPosix(scePthreadRwlockTimedrdlock(rwlock, usec)); +} + +int APS5_VABI pthread_rwlock_timedwrlock_nid_postfix(PthreadRwlock* rwlock, const KernelTimespec* abstime) { + if (!abstime) throw std::runtime_error("pthread_rwlock_timedwrlock: null abstime"); + initializeStatic(rwlock, __func__); + if (scePthreadRwlockTrywrlock(rwlock) == 0) return 0; + KernelUseconds usec = 0; + if (!PosixThread::RelativeMicroseconds(GUEST_REALTIME_CLOCK, abstime, &usec)) return PosixThread::GUEST_EINVAL; + return toPosix(scePthreadRwlockTimedwrlock(rwlock, usec)); +} + +int APS5_VABI pthread_rwlock_tryrdlock_nid_postfix(PthreadRwlock* rwlock) { + initializeStatic(rwlock, __func__); + return toPosix(scePthreadRwlockTryrdlock(rwlock)); +} + +int APS5_VABI pthread_rwlock_trywrlock_nid_postfix(PthreadRwlock* rwlock) { + initializeStatic(rwlock, __func__); + return toPosix(scePthreadRwlockTrywrlock(rwlock)); +} + int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock* rwlock) { initializeStatic(rwlock, __func__); return toPosix(scePthreadRwlockUnlock(rwlock)); diff --git a/core/libs/prx/libkernel/Pthread/include/Rwlock.hpp b/core/libs/prx/libkernel/Pthread/include/Rwlock.hpp index 0c9668003..d986eb420 100644 --- a/core/libs/prx/libkernel/Pthread/include/Rwlock.hpp +++ b/core/libs/prx/libkernel/Pthread/include/Rwlock.hpp @@ -12,8 +12,10 @@ int APS5_VABI scePthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockatt int APS5_VABI scePthreadRwlockDestroy(PthreadRwlock* rwlock); int APS5_VABI scePthreadRwlockRdlock(PthreadRwlock* rwlock); int APS5_VABI scePthreadRwlockTryrdlock(PthreadRwlock* rwlock); +int APS5_VABI scePthreadRwlockTimedrdlock(PthreadRwlock* rwlock, KernelUseconds usec); int APS5_VABI scePthreadRwlockWrlock(PthreadRwlock* rwlock); int APS5_VABI scePthreadRwlockTrywrlock(PthreadRwlock* rwlock); +int APS5_VABI scePthreadRwlockTimedwrlock(PthreadRwlock* rwlock, KernelUseconds usec); int APS5_VABI scePthreadRwlockUnlock(PthreadRwlock* rwlock); } diff --git a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp index ff81a2cc9..37a7668a6 100644 --- a/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp +++ b/core/libs/prx/libkernel/Pthread/src/Rwlock.cpp @@ -48,6 +48,7 @@ int APS5_VABI scePthreadRwlockRdlock(PthreadRwlock* rwlock) { int APS5_VABI scePthreadRwlockTryrdlock(PthreadRwlock* rwlock) { auto* lock = RequireRwlock(rwlock, __func__); + if (OwnsWrite(lock)) return SCE_KERNEL_ERROR_EBUSY; return lock->_lock.try_lock_shared() ? SCE_OK : SCE_KERNEL_ERROR_EBUSY; } @@ -68,7 +69,7 @@ int APS5_VABI scePthreadRwlockWrlock(PthreadRwlock* rwlock) { int APS5_VABI scePthreadRwlockTrywrlock(PthreadRwlock* rwlock) { auto* lock = RequireRwlock(rwlock, __func__); - if (!lock->_lock.try_lock()) return SCE_KERNEL_ERROR_EBUSY; + if (OwnsWrite(lock) || !lock->_lock.try_lock()) return SCE_KERNEL_ERROR_EBUSY; lock->_writer.store(std::this_thread::get_id(), std::memory_order_release); return SCE_OK; } diff --git a/core/libs/tests/GuestPthreadRwlock.cpp b/core/libs/tests/GuestPthreadRwlock.cpp new file mode 100644 index 000000000..915d7d1ab --- /dev/null +++ b/core/libs/tests/GuestPthreadRwlock.cpp @@ -0,0 +1,131 @@ +#include "SceTypes.hpp" +#include +#include +#include +#include +#include +#include + +extern "C" { +int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name); +int APS5_VABI scePthreadJoin(Pthread thread, void** retval); +int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp); +int APS5_VABI pthread_rwlock_destroy_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_rdlock_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_wrlock_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_unlock_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_tryrdlock_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_trywrlock_nid_postfix(PthreadRwlock* rwlock); +int APS5_VABI pthread_rwlock_timedrdlock_nid_postfix(PthreadRwlock* rwlock, const KernelTimespec* abstime); +int APS5_VABI pthread_rwlock_timedwrlock_nid_postfix(PthreadRwlock* rwlock, const KernelTimespec* abstime); +} + +using TimedLock = int (APS5_VABI *)(PthreadRwlock*, const KernelTimespec*); + +static constexpr int SCE_OK = 0; +static constexpr int GUEST_EDEADLK = 11; +static constexpr int GUEST_EBUSY = 16; +static constexpr int GUEST_EINVAL = 22; +static constexpr int GUEST_ETIMEDOUT = 60; +static constexpr int GUEST_REALTIME_CLOCK = 0; +static constexpr std::int64_t NANOS_PER_SECOND = 1000000000; + +static void Require(bool value) { if (!value) std::abort(); } + +static KernelTimespec After(std::int64_t millis) { + KernelTimespec now{}; + Require(clock_gettime_nid_postfix(GUEST_REALTIME_CLOCK, &now) == 0); + const std::int64_t nanos = now.tv_sec * NANOS_PER_SECOND + now.tv_nsec + millis * 1000000; + return {nanos / NANOS_PER_SECOND, nanos % NANOS_PER_SECOND}; +} + +struct Holder { + PthreadRwlock* rwlock; + bool write; + std::atomic held{false}; + std::atomic release{false}; + Pthread thread = nullptr; +}; + +static void* APS5_VABI Hold(void* arg) { + auto& holder = *static_cast(arg); + Require((holder.write ? pthread_rwlock_wrlock_nid_postfix(holder.rwlock) : pthread_rwlock_rdlock_nid_postfix(holder.rwlock)) == 0); + holder.held.store(true); + while (!holder.release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + Require(pthread_rwlock_unlock_nid_postfix(holder.rwlock) == 0); + return nullptr; +} + +static void Start(Holder& holder) { + Require(scePthreadCreate(&holder.thread, nullptr, Hold, &holder, nullptr) == SCE_OK); + while (!holder.held.load()) std::this_thread::yield(); +} + +static void ExpectTimeout(TimedLock lock, PthreadRwlock* rwlock) { + const KernelTimespec deadline = After(20); + const auto start = std::chrono::steady_clock::now(); + Require(lock(rwlock, &deadline) == GUEST_ETIMEDOUT); + Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15)); + const KernelTimespec past = After(-1000); + Require(lock(rwlock, &past) == GUEST_ETIMEDOUT); + const KernelTimespec invalid{deadline.tv_sec, NANOS_PER_SECOND}; + Require(lock(rwlock, &invalid) == GUEST_EINVAL); + const KernelTimespec negative{deadline.tv_sec, -1}; + Require(lock(rwlock, &negative) == GUEST_EINVAL); +} + +int main() { + const KernelTimespec invalid{0, NANOS_PER_SECOND}; + PthreadRwlock fresh[4] = {}; + Require(pthread_rwlock_tryrdlock_nid_postfix(&fresh[0]) == 0); + Require(pthread_rwlock_trywrlock_nid_postfix(&fresh[1]) == 0); + Require(pthread_rwlock_timedrdlock_nid_postfix(&fresh[2], &invalid) == 0); + Require(pthread_rwlock_timedwrlock_nid_postfix(&fresh[3], &invalid) == 0); + for (auto& lock : fresh) { + Require(lock != nullptr); + Require(pthread_rwlock_unlock_nid_postfix(&lock) == 0); + Require(pthread_rwlock_destroy_nid_postfix(&lock) == 0); + } + + PthreadRwlock rwlock = nullptr; + Require(pthread_rwlock_trywrlock_nid_postfix(&rwlock) == 0); + Require(pthread_rwlock_tryrdlock_nid_postfix(&rwlock) == GUEST_EBUSY); + Require(pthread_rwlock_trywrlock_nid_postfix(&rwlock) == GUEST_EBUSY); + KernelTimespec deadline = After(5000); + Require(pthread_rwlock_timedrdlock_nid_postfix(&rwlock, &deadline) == GUEST_EDEADLK); + Require(pthread_rwlock_timedwrlock_nid_postfix(&rwlock, &deadline) == GUEST_EDEADLK); + Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + + Holder reader{&rwlock, false}; + Start(reader); + Require(pthread_rwlock_tryrdlock_nid_postfix(&rwlock) == 0); + Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + Require(pthread_rwlock_timedrdlock_nid_postfix(&rwlock, &invalid) == 0); + Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + Require(pthread_rwlock_trywrlock_nid_postfix(&rwlock) == GUEST_EBUSY); + ExpectTimeout(pthread_rwlock_timedwrlock_nid_postfix, &rwlock); + deadline = After(5000); + reader.release.store(true); + Require(pthread_rwlock_timedwrlock_nid_postfix(&rwlock, &deadline) == 0); + Require(scePthreadJoin(reader.thread, nullptr) == SCE_OK); + Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + + Holder writer{&rwlock, true}; + Start(writer); + Require(pthread_rwlock_tryrdlock_nid_postfix(&rwlock) == GUEST_EBUSY); + Require(pthread_rwlock_trywrlock_nid_postfix(&rwlock) == GUEST_EBUSY); + ExpectTimeout(pthread_rwlock_timedrdlock_nid_postfix, &rwlock); + ExpectTimeout(pthread_rwlock_timedwrlock_nid_postfix, &rwlock); + deadline = After(5000); + writer.release.store(true); + Require(pthread_rwlock_timedrdlock_nid_postfix(&rwlock, &deadline) == 0); + Require(scePthreadJoin(writer.thread, nullptr) == SCE_OK); + Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0); + + bool rejected = false; + try { pthread_rwlock_timedrdlock_nid_postfix(&rwlock, nullptr); } + catch (const std::runtime_error&) { rejected = true; } + Require(rejected); + Require(pthread_rwlock_destroy_nid_postfix(&rwlock) == 0); +}