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 <noreply@anthropic.com>
This commit is contained in:
DotDebian 2026-10-02 12:17:19 +02:00
parent 2cf901bcff
commit f9625f2581
5 changed files with 176 additions and 1 deletions

View File

@ -196,6 +196,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_semaphore PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
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:$<TARGET_FILE_DIR:libkernel>")
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)

View File

@ -4,12 +4,15 @@
#include "prx/libc/include/General.hpp"
#include "../include/Pthread.hpp"
#include "../include/Rwlock.hpp"
#include "Common.hpp"
#include <atomic>
#include <stdexcept>
#include <string>
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));

View File

@ -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);
}

View File

@ -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;
}

View File

@ -0,0 +1,131 @@
#include "SceTypes.hpp"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <stdexcept>
#include <thread>
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<bool> held{false};
std::atomic<bool> release{false};
Pthread thread = nullptr;
};
static void* APS5_VABI Hold(void* arg) {
auto& holder = *static_cast<Holder*>(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);
}