Merge pull request #341 from DotDebian/feat/kernel-cancel-sema
feat(libkernel): implement sceKernelCancelSema
This commit is contained in:
commit
a818beedf5
|
|
@ -146,6 +146,16 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_pthread_sem PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
|
||||
endif()
|
||||
add_test_executable(guest_semaphore_tests ${LIBS_INCLUDE_DIR}/tests/GuestSemaphore.cpp)
|
||||
target_include_directories(guest_semaphore_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_semaphore_tests PRIVATE libkernel libc)
|
||||
configure_windows_unwind(guest_semaphore_tests)
|
||||
add_test(NAME guest_semaphore COMMAND guest_semaphore_tests)
|
||||
set_tests_properties(guest_semaphore PROPERTIES TIMEOUT 20)
|
||||
if(WIN32)
|
||||
set_tests_properties(guest_semaphore 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)
|
||||
|
|
|
|||
|
|
@ -20,10 +20,13 @@ struct KernelSemaPrivate {
|
|||
TimedWait::Condition condition;
|
||||
std::string name;
|
||||
std::int32_t tokenCount;
|
||||
std::int32_t initCount;
|
||||
std::int32_t maxCount;
|
||||
bool isFifo;
|
||||
bool deleted = false;
|
||||
std::int32_t waiterCount = 0;
|
||||
std::int32_t cancelableCount = 0;
|
||||
std::uint64_t cancelGeneration = 0;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
|
@ -33,6 +36,7 @@ int APS5_VABI sceKernelDeleteSema(KernelSema sem);
|
|||
int APS5_VABI sceKernelPollSema(KernelSema sem, int need);
|
||||
int APS5_VABI sceKernelSignalSema(KernelSema sem, int count);
|
||||
int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time);
|
||||
int APS5_VABI sceKernelCancelSema(KernelSema sem, int count, int* threads);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
#include <utility>
|
||||
|
||||
KernelSemaPrivate::KernelSemaPrivate(std::int32_t initCount, std::int32_t maxCount, std::string name, bool isFifo)
|
||||
: name(std::move(name)), tokenCount(initCount), maxCount(maxCount), isFifo(isFifo) {
|
||||
: name(std::move(name)), tokenCount(initCount), initCount(initCount), maxCount(maxCount), isFifo(isFifo) {
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
|
@ -55,34 +55,47 @@ int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time)
|
|||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock(sem->mutex);
|
||||
const std::uint64_t generation = sem->cancelGeneration;
|
||||
++sem->waiterCount;
|
||||
++sem->cancelableCount;
|
||||
struct WaiterGuard {
|
||||
KernelSemaPrivate* sem;
|
||||
std::uint64_t generation;
|
||||
~WaiterGuard() {
|
||||
--sem->waiterCount;
|
||||
if (sem->cancelGeneration == generation) {
|
||||
--sem->cancelableCount;
|
||||
}
|
||||
sem->condition.NotifyAll();
|
||||
}
|
||||
} waiterGuard{sem};
|
||||
} waiterGuard{sem, generation};
|
||||
|
||||
const auto waitStart = std::chrono::steady_clock::now();
|
||||
const auto traceWait = [&](bool timedOut) {
|
||||
KernelTraceWait_nid_postfix("sema", __builtin_return_address(0), static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now() - waitStart).count()), timedOut);
|
||||
};
|
||||
const auto released = [&] { return sem->tokenCount >= need || sem->deleted || sem->cancelGeneration != generation; };
|
||||
if (time == nullptr) {
|
||||
sem->condition.Wait(lock, [&] { return sem->tokenCount >= need || sem->deleted; });
|
||||
sem->condition.Wait(lock, released);
|
||||
traceWait(false);
|
||||
if (sem->deleted) {
|
||||
return SCE_KERNEL_ERROR_EACCES;
|
||||
}
|
||||
if (sem->cancelGeneration != generation) {
|
||||
return SCE_KERNEL_ERROR_ECANCELED;
|
||||
}
|
||||
sem->tokenCount -= need;
|
||||
return KERNEL_SEMA_OK;
|
||||
}
|
||||
|
||||
const bool acquired = sem->condition.WaitUntil(lock, TimedWait::DeadlineNanos(*time), [&] { return sem->tokenCount >= need || sem->deleted; });
|
||||
const bool acquired = sem->condition.WaitUntil(lock, TimedWait::DeadlineNanos(*time), released);
|
||||
traceWait(!acquired);
|
||||
if (sem->deleted) {
|
||||
return SCE_KERNEL_ERROR_EACCES;
|
||||
}
|
||||
if (sem->cancelGeneration != generation) {
|
||||
return SCE_KERNEL_ERROR_ECANCELED;
|
||||
}
|
||||
if (!acquired) {
|
||||
return SCE_KERNEL_ERROR_ETIMEDOUT;
|
||||
}
|
||||
|
|
@ -91,11 +104,22 @@ int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time)
|
|||
}
|
||||
|
||||
int APS5_VABI sceKernelCancelSema(KernelSema sem, int count, int* threads) {
|
||||
(void)sem;
|
||||
(void)count;
|
||||
(void)threads;
|
||||
NotImplemented_nid_no_patch(__func__);
|
||||
return 0;
|
||||
if (sem == nullptr) {
|
||||
APS5_INVALID_ARG_EX;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(sem->mutex);
|
||||
if (count > sem->maxCount) {
|
||||
return SCE_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (threads != nullptr) {
|
||||
*threads = sem->cancelableCount;
|
||||
}
|
||||
sem->tokenCount = count < 0 ? sem->initCount : count;
|
||||
sem->cancelableCount = 0;
|
||||
++sem->cancelGeneration;
|
||||
sem->condition.NotifyAll();
|
||||
return KERNEL_SEMA_OK;
|
||||
}
|
||||
|
||||
int APS5_VABI sceKernelDeleteSema(KernelSema sem) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
#include "SceTypes.hpp"
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
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 sceKernelCreateSema(KernelSema* sem, const char* name, uint32_t attr, int init, int max, void* opt);
|
||||
int APS5_VABI sceKernelDeleteSema(KernelSema sem);
|
||||
int APS5_VABI sceKernelPollSema(KernelSema sem, int need);
|
||||
int APS5_VABI sceKernelSignalSema(KernelSema sem, int count);
|
||||
int APS5_VABI sceKernelWaitSema(KernelSema sem, int need, KernelUseconds* time);
|
||||
int APS5_VABI sceKernelCancelSema(KernelSema sem, int count, int* threads);
|
||||
}
|
||||
|
||||
static constexpr int SCE_OK = 0;
|
||||
static constexpr int SCE_KERNEL_ERROR_EBUSY = static_cast<int>(0x80020010);
|
||||
static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast<int>(0x80020016);
|
||||
static constexpr int SCE_KERNEL_ERROR_ECANCELED = static_cast<int>(0x80020055);
|
||||
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
|
||||
struct Waiter {
|
||||
KernelSema sem = nullptr;
|
||||
bool timed = false;
|
||||
int result = SCE_OK;
|
||||
};
|
||||
|
||||
static void* APS5_VABI Wait(void* arg) {
|
||||
auto& waiter = *static_cast<Waiter*>(arg);
|
||||
KernelUseconds timeout = 5000000;
|
||||
waiter.result = sceKernelWaitSema(waiter.sem, 1, waiter.timed ? &timeout : nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int main() {
|
||||
KernelSema sem = nullptr;
|
||||
Require(sceKernelCreateSema(&sem, "cancel", 0, 1, 3, nullptr) == SCE_OK);
|
||||
|
||||
int waiters = -1;
|
||||
Require(sceKernelCancelSema(sem, 4, &waiters) == SCE_KERNEL_ERROR_EINVAL);
|
||||
Require(waiters == -1);
|
||||
Require(sceKernelPollSema(sem, 1) == SCE_OK);
|
||||
Require(sceKernelPollSema(sem, 1) == SCE_KERNEL_ERROR_EBUSY);
|
||||
|
||||
Require(sceKernelCancelSema(sem, 2, &waiters) == SCE_OK && waiters == 0);
|
||||
Require(sceKernelPollSema(sem, 2) == SCE_OK);
|
||||
Require(sceKernelPollSema(sem, 1) == SCE_KERNEL_ERROR_EBUSY);
|
||||
|
||||
Require(sceKernelCancelSema(sem, -1, nullptr) == SCE_OK);
|
||||
Require(sceKernelPollSema(sem, 1) == SCE_OK);
|
||||
Require(sceKernelPollSema(sem, 1) == SCE_KERNEL_ERROR_EBUSY);
|
||||
|
||||
Waiter blocked{sem, false};
|
||||
Waiter timed{sem, true};
|
||||
Pthread blockedThread = nullptr;
|
||||
Pthread timedThread = nullptr;
|
||||
Require(scePthreadCreate(&blockedThread, nullptr, Wait, &blocked, nullptr) == SCE_OK);
|
||||
Require(scePthreadCreate(&timedThread, nullptr, Wait, &timed, nullptr) == SCE_OK);
|
||||
|
||||
int canceled = 0;
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(4);
|
||||
while (canceled < 2 && std::chrono::steady_clock::now() < deadline) {
|
||||
waiters = -1;
|
||||
Require(sceKernelCancelSema(sem, 0, &waiters) == SCE_OK);
|
||||
Require(waiters >= 0);
|
||||
canceled += waiters;
|
||||
}
|
||||
Require(scePthreadJoin(blockedThread, nullptr) == SCE_OK);
|
||||
Require(scePthreadJoin(timedThread, nullptr) == SCE_OK);
|
||||
Require(canceled == 2);
|
||||
Require(blocked.result == SCE_KERNEL_ERROR_ECANCELED);
|
||||
Require(timed.result == SCE_KERNEL_ERROR_ECANCELED);
|
||||
Require(sceKernelCancelSema(sem, 0, &waiters) == SCE_OK && waiters == 0);
|
||||
Require(sceKernelPollSema(sem, 1) == SCE_KERNEL_ERROR_EBUSY);
|
||||
|
||||
Require(sceKernelSignalSema(sem, 1) == SCE_OK);
|
||||
Require(sceKernelWaitSema(sem, 1, nullptr) == SCE_OK);
|
||||
Require(sceKernelDeleteSema(sem) == SCE_OK);
|
||||
}
|
||||
Loading…
Reference in New Issue