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:
parent
2cf901bcff
commit
f9625f2581
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
Loading…
Reference in New Issue