Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions core/libs/prx/libkernel/Pthread/Posix/Common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#define CORE_LIBS_PRX_LIBKERNEL_PTHREAD_POSIX_COMMON_HPP

#include <cstdint>
#include <chrono>
#include <limits>
#include "SceTypes.hpp"

Expand All @@ -22,10 +21,31 @@ inline bool RelativeMicroseconds(int clockId, const KernelTimespec* abstime, Ker
if (!abstime || abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) return false;
KernelTimespec now{};
clock_gettime_nid_postfix(clockId, &now);
const auto deadline = std::chrono::seconds(abstime->tv_sec) + std::chrono::nanoseconds(abstime->tv_nsec);
const auto current = std::chrono::seconds(now.tv_sec) + std::chrono::nanoseconds(now.tv_nsec);
const auto remaining = std::chrono::duration_cast<std::chrono::microseconds>(deadline - current).count();
*usec = remaining <= 0 ? 0 : remaining >= std::numeric_limits<KernelUseconds>::max() ? std::numeric_limits<KernelUseconds>::max() : static_cast<KernelUseconds>(remaining);
if (abstime->tv_sec < now.tv_sec ||
(abstime->tv_sec == now.tv_sec && abstime->tv_nsec <= now.tv_nsec)) {
*usec = 0;
return true;
}

std::uint64_t seconds = static_cast<std::uint64_t>(abstime->tv_sec) - static_cast<std::uint64_t>(now.tv_sec);
std::uint64_t nanoseconds = 0;
if (abstime->tv_nsec >= now.tv_nsec) {
nanoseconds = static_cast<std::uint64_t>(abstime->tv_nsec - now.tv_nsec);
} else {
--seconds;
nanoseconds = 1000000000ULL - static_cast<std::uint64_t>(now.tv_nsec - abstime->tv_nsec);
}

constexpr std::uint64_t microsecondsPerSecond = 1000000ULL;
constexpr std::uint64_t maxMicroseconds = std::numeric_limits<KernelUseconds>::max();
constexpr std::uint64_t maxWholeSeconds = maxMicroseconds / microsecondsPerSecond;
if (seconds > maxWholeSeconds) {
*usec = static_cast<KernelUseconds>(maxMicroseconds);
return true;
}

const std::uint64_t remaining = seconds * microsecondsPerSecond + nanoseconds / 1000ULL;
*usec = static_cast<KernelUseconds>(remaining > maxMicroseconds ? maxMicroseconds : remaining);
return true;
}

Expand Down
9 changes: 3 additions & 6 deletions core/libs/prx/libkernel/Pthread/Posix/Mutex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@
#include "../include/Mutex.hpp"
#include "Common.hpp"
#include <atomic>
#include <chrono>
#include <limits>
#include <stdexcept>
#include <string>

namespace {

constexpr int POSIX_EINVAL = 22;
constexpr int GUEST_REALTIME_CLOCK = 0;
constexpr int POSIX_PRIO_PROTECT = 2;
constexpr std::uintptr_t POSIX_ADAPTIVE_MUTEX_INITIALIZER = 1;

Expand Down Expand Up @@ -61,10 +60,8 @@ int APS5_VABI pthread_mutex_timedlock_nid_postfix(PthreadMutex* mutex, const Ker
if (!abstime) throw std::runtime_error("pthread_mutex_timedlock: null abstime");
if (abstime->tv_nsec < 0 || abstime->tv_nsec >= 1000000000) return POSIX_EINVAL;
_initializeStatic(mutex, __func__);
const auto deadline = std::chrono::seconds(abstime->tv_sec) + std::chrono::nanoseconds(abstime->tv_nsec);
const auto now = std::chrono::system_clock::now().time_since_epoch();
const auto remaining = std::chrono::duration_cast<std::chrono::microseconds>(deadline - now).count();
const auto usec = remaining <= 0 ? 0 : remaining >= std::numeric_limits<KernelUseconds>::max() ? std::numeric_limits<KernelUseconds>::max() : static_cast<KernelUseconds>(remaining);
KernelUseconds usec = 0;
if (!PosixThread::RelativeMicroseconds(GUEST_REALTIME_CLOCK, abstime, &usec)) return POSIX_EINVAL;
return PosixThread::ToErrno(scePthreadMutexTimedlock(mutex, usec));
}

Expand Down
74 changes: 74 additions & 0 deletions core/libs/tests/GuestPthreadRwlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdio>
#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 scePthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr, const char* name);
int APS5_VABI scePthreadMutexDestroy(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexLock(PthreadMutex* mutex);
int APS5_VABI scePthreadMutexUnlock(PthreadMutex* mutex);
int APS5_VABI clock_gettime_nid_postfix(int clockId, KernelTimespec* tp);
int APS5_VABI pthread_mutex_timedlock_nid_postfix(PthreadMutex* mutex, const KernelTimespec* abstime);
int APS5_VABI pthread_mutex_unlock_nid_postfix(PthreadMutex* mutex);
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);
Expand Down Expand Up @@ -47,6 +54,22 @@ struct Holder {
Pthread thread = nullptr;
};

struct MutexHolder {
PthreadMutex* mutex;
std::atomic<bool> held{false};
std::atomic<bool> release{false};
Pthread thread = nullptr;
};

static void* APS5_VABI HoldMutex(void* arg) {
auto& holder = *static_cast<MutexHolder*>(arg);
Require(scePthreadMutexLock(holder.mutex) == SCE_OK);
holder.held.store(true);
while (!holder.release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1));
Require(scePthreadMutexUnlock(holder.mutex) == SCE_OK);
return 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);
Expand All @@ -69,6 +92,8 @@ static void ExpectTimeout(TimedLock lock, PthreadRwlock* rwlock) {
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(15));
const KernelTimespec past = After(-1000);
Require(lock(rwlock, &past) == GUEST_ETIMEDOUT);
const KernelTimespec subsecond = After(1);
Require(lock(rwlock, &subsecond) == GUEST_ETIMEDOUT);
const KernelTimespec invalid{deadline.tv_sec, NANOS_PER_SECOND};
Require(lock(rwlock, &invalid) == GUEST_EINVAL);
const KernelTimespec negative{deadline.tv_sec, -1};
Expand Down Expand Up @@ -123,6 +148,55 @@ int main() {
Require(scePthreadJoin(writer.thread, nullptr) == SCE_OK);
Require(pthread_rwlock_unlock_nid_postfix(&rwlock) == 0);

Holder farFutureWriter{&rwlock, true};
Start(farFutureWriter);
std::thread releaseFarFutureWriter([&] {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
farFutureWriter.release.store(true);
});
const KernelTimespec farFuture{INT64_MAX, 0};
const int farFutureResult = pthread_rwlock_timedrdlock_nid_postfix(&rwlock, &farFuture);
const int farFutureUnlockResult = farFutureResult == 0 ? pthread_rwlock_unlock_nid_postfix(&rwlock) : 0;
releaseFarFutureWriter.join();
Require(scePthreadJoin(farFutureWriter.thread, nullptr) == SCE_OK);
if (farFutureResult != 0) {
std::fprintf(stderr, "far-future rwlock timed lock returned %d\n", farFutureResult);
std::abort();
}
if (farFutureUnlockResult != 0) {
std::fprintf(stderr, "far-future rwlock unlock returned %d\n", farFutureUnlockResult);
std::abort();
}

PthreadMutex mutex = nullptr;
Require(scePthreadMutexInit(&mutex, nullptr, nullptr) == SCE_OK);
MutexHolder farFutureMutex{&mutex};
Require(scePthreadCreate(&farFutureMutex.thread, nullptr, HoldMutex, &farFutureMutex, nullptr) == SCE_OK);
while (!farFutureMutex.held.load()) std::this_thread::yield();
const auto mutexWaitStart = std::chrono::steady_clock::now();
std::thread releaseFarFutureMutex([&] {
std::this_thread::sleep_for(std::chrono::milliseconds(20));
farFutureMutex.release.store(true);
});
const int farFutureMutexResult = pthread_mutex_timedlock_nid_postfix(&mutex, &farFuture);
const auto mutexWaitElapsed = std::chrono::steady_clock::now() - mutexWaitStart;
const int farFutureMutexUnlockResult = farFutureMutexResult == 0 ? pthread_mutex_unlock_nid_postfix(&mutex) : 0;
releaseFarFutureMutex.join();
Require(scePthreadJoin(farFutureMutex.thread, nullptr) == SCE_OK);
Require(scePthreadMutexDestroy(&mutex) == SCE_OK);
if (farFutureMutexResult != 0) {
std::fprintf(stderr, "far-future mutex timed lock returned %d\n", farFutureMutexResult);
std::abort();
}
if (farFutureMutexUnlockResult != 0) {
std::fprintf(stderr, "far-future mutex unlock returned %d\n", farFutureMutexUnlockResult);
std::abort();
}
if (mutexWaitElapsed < std::chrono::milliseconds(15)) {
std::fprintf(stderr, "far-future mutex timed lock returned too early\n");
std::abort();
}

bool rejected = false;
try { pthread_rwlock_timedrdlock_nid_postfix(&rwlock, nullptr); }
catch (const std::runtime_error&) { rejected = true; }
Expand Down
Loading