Merge pull request #4350 from milescoviello/fix/once-reset-on-exit
fix(libkernel): reset a once control when its initializer exits or is cancelled
This commit is contained in:
commit
2a5eca11cd
|
|
@ -1,5 +1,6 @@
|
|||
#include "prx/libc/include/General.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "prx/libkernel/Pthread/include/Cancel.hpp"
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
|
@ -14,6 +15,14 @@ struct GuestOnce {
|
|||
static_assert(offsetof(GuestOnce, mutex) == 8 && sizeof(GuestOnce) == 16);
|
||||
std::mutex onceMutex;
|
||||
std::condition_variable onceChanged;
|
||||
|
||||
void APS5_VABI ResetOnce(void* control) {
|
||||
{
|
||||
std::lock_guard lock(onceMutex);
|
||||
static_cast<GuestOnce*>(control)->state = 0;
|
||||
}
|
||||
onceChanged.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" int APS5_VABI pthread_once_nid_postfix(
|
||||
|
|
@ -27,15 +36,17 @@ extern "C" int APS5_VABI pthread_once_nid_postfix(
|
|||
lock.unlock();
|
||||
// Initializers may initialize other once controls. Never run guest code
|
||||
// under the shared lock. The lock publishes all initializer writes.
|
||||
CleanupHandler reset{};
|
||||
void* const mark = ThreadCancel::CleanupMark();
|
||||
__pthread_cleanup_push_imp_nid_postfix(ResetOnce, control, &reset);
|
||||
try {
|
||||
initialize();
|
||||
} catch (...) {
|
||||
lock.lock();
|
||||
control->state = 0;
|
||||
lock.unlock();
|
||||
onceChanged.notify_all();
|
||||
ThreadCancel::CleanupRestore(mark);
|
||||
ResetOnce(control);
|
||||
throw;
|
||||
}
|
||||
ThreadCancel::CleanupRestore(mark);
|
||||
lock.lock();
|
||||
control->state = 1;
|
||||
lock.unlock();
|
||||
|
|
|
|||
|
|
@ -12,6 +12,13 @@ inline constexpr int GUEST_CANCEL_DEFERRED = 0;
|
|||
inline constexpr int GUEST_CANCEL_ASYNCHRONOUS = 2;
|
||||
inline void* const GUEST_CANCELED = reinterpret_cast<void*>(std::uintptr_t{1});
|
||||
|
||||
struct CleanupHandler {
|
||||
CleanupHandler* previous;
|
||||
void (APS5_VABI* routine)(void*);
|
||||
void* argument;
|
||||
int onHeap;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
void APS5_VABI __pthread_cleanup_push_imp_nid_postfix(void (APS5_VABI* routine)(void*), void* argument, void* info);
|
||||
void APS5_VABI __pthread_cleanup_pop_imp_nid_postfix(int execute);
|
||||
|
|
@ -21,6 +28,8 @@ namespace ThreadCancel {
|
|||
|
||||
bool Requested();
|
||||
void Check();
|
||||
void* CleanupMark();
|
||||
void CleanupRestore(void* mark);
|
||||
void BeginWait(TimedWait::Condition* condition);
|
||||
void EndWait();
|
||||
|
||||
|
|
|
|||
|
|
@ -53,12 +53,6 @@ struct ThreadArgs {
|
|||
static std::atomic<thread_dtors_func_t> threadDtors{nullptr};
|
||||
static std::atomic<get_thread_atexit_count_func_t> threadAtexitCount{nullptr};
|
||||
static std::atomic<thread_atexit_report_func_t> threadAtexitReport{nullptr};
|
||||
struct CleanupHandler {
|
||||
CleanupHandler* previous;
|
||||
void (APS5_VABI* routine)(void*);
|
||||
void* argument;
|
||||
int onHeap;
|
||||
};
|
||||
|
||||
struct GuestThreadState {
|
||||
PthreadPrivate* current = nullptr;
|
||||
|
|
@ -371,6 +365,14 @@ void ThreadCancel::Check() {
|
|||
if (Requested()) scePthreadExit(GUEST_CANCELED);
|
||||
}
|
||||
|
||||
void* ThreadCancel::CleanupMark() {
|
||||
return CurrentGuestState().cleanupHandlers;
|
||||
}
|
||||
|
||||
void ThreadCancel::CleanupRestore(void* mark) {
|
||||
CurrentGuestState().cleanupHandlers = static_cast<CleanupHandler*>(mark);
|
||||
}
|
||||
|
||||
void ThreadCancel::BeginWait(TimedWait::Condition* condition) {
|
||||
if (!CurrentGuestState().current) return;
|
||||
std::lock_guard lock(CurrentGuestState().current->cancelLock);
|
||||
|
|
@ -819,20 +821,36 @@ int APS5_VABI scePthreadSetprio(Pthread thread, int prio) {
|
|||
return SCE_OK;
|
||||
}
|
||||
|
||||
static void APS5_VABI ResetOnce(void* once) {
|
||||
constexpr int32_t Never = 0;
|
||||
std::atomic_ref<int32_t> state(*static_cast<int32_t*>(once));
|
||||
state.store(Never, std::memory_order_release);
|
||||
state.notify_all();
|
||||
}
|
||||
|
||||
int APS5_VABI scePthreadOnce(int32_t* once, void (APS5_VABI* init)(void)) {
|
||||
if (!once || !init) return SCE_KERNEL_ERROR_EINVAL;
|
||||
constexpr int32_t Never = 0;
|
||||
constexpr int32_t Done = 1;
|
||||
constexpr int32_t Running = 2;
|
||||
std::atomic_ref<int32_t> state(*once);
|
||||
int32_t expected = Never;
|
||||
if (state.compare_exchange_strong(expected, Running, std::memory_order_acq_rel)) {
|
||||
init();
|
||||
state.store(Done, std::memory_order_release);
|
||||
state.notify_all();
|
||||
return SCE_OK;
|
||||
for (int32_t expected = Never; !state.compare_exchange_strong(expected, Running, std::memory_order_acq_rel); expected = Never) {
|
||||
if (expected != Running) return SCE_OK;
|
||||
state.wait(Running, std::memory_order_acquire);
|
||||
}
|
||||
while ((expected = state.load(std::memory_order_acquire)) == Running) state.wait(Running, std::memory_order_acquire);
|
||||
CleanupHandler reset{};
|
||||
void* const mark = ThreadCancel::CleanupMark();
|
||||
__pthread_cleanup_push_imp_nid_postfix(ResetOnce, once, &reset);
|
||||
try {
|
||||
init();
|
||||
} catch (...) {
|
||||
ThreadCancel::CleanupRestore(mark);
|
||||
ResetOnce(once);
|
||||
throw;
|
||||
}
|
||||
ThreadCancel::CleanupRestore(mark);
|
||||
state.store(Done, std::memory_order_release);
|
||||
state.notify_all();
|
||||
return SCE_OK;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include "SceTypes.hpp"
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
|
|
@ -9,7 +9,17 @@
|
|||
struct GuestOnce { std::int32_t state; void* mutex; };
|
||||
static_assert(sizeof(GuestOnce) == 16 && offsetof(GuestOnce, mutex) == 8);
|
||||
using Initializer = void (APS5_VABI *)();
|
||||
extern "C" int APS5_VABI pthread_once_nid_postfix(GuestOnce*, Initializer);
|
||||
extern "C" {
|
||||
int APS5_VABI pthread_once_nid_postfix(GuestOnce*, Initializer);
|
||||
int APS5_VABI scePthreadOnce(std::int32_t* once, Initializer init);
|
||||
int APS5_VABI scePthreadCreate(Pthread* thread, const PthreadAttr* attr, PthreadEntry entry, void* arg, const char* name);
|
||||
int APS5_VABI scePthreadJoin(Pthread thread, void** retval);
|
||||
void APS5_VABI scePthreadExit(void* retval);
|
||||
int APS5_VABI scePthreadCancel(Pthread thread);
|
||||
void APS5_VABI scePthreadTestcancel();
|
||||
Pthread APS5_VABI scePthreadSelf();
|
||||
void APS5_VABI __pthread_cleanup_push_imp_nid_postfix(void (APS5_VABI* routine)(void*), void* argument, void* info);
|
||||
}
|
||||
static void Require(bool value) { if (!value) std::abort(); }
|
||||
static GuestOnce control{}, nested{}, retry{};
|
||||
static std::atomic<int> calls{0}, arrived{0};
|
||||
|
|
@ -24,6 +34,35 @@ static void APS5_VABI Initialize() {
|
|||
static void APS5_VABI Retry() {
|
||||
if (++attempts == 1) throw 7;
|
||||
}
|
||||
static GuestOnce exited{}, cancelled{};
|
||||
static std::int32_t sceExited = 0, sceCancelled = 0;
|
||||
static int retried = 0;
|
||||
static void APS5_VABI ExitInsideInitializer() { scePthreadExit(nullptr); }
|
||||
static void APS5_VABI CancelInsideInitializer() {
|
||||
Require(scePthreadCancel(scePthreadSelf()) == 0);
|
||||
scePthreadTestcancel();
|
||||
}
|
||||
static void APS5_VABI CountRetry() { ++retried; }
|
||||
static void APS5_VABI AbortCleanup(void*) { std::abort(); }
|
||||
static void APS5_VABI PushAndThrow() {
|
||||
std::uintptr_t info[8];
|
||||
__pthread_cleanup_push_imp_nid_postfix(AbortCleanup, nullptr, info);
|
||||
throw 7;
|
||||
}
|
||||
static GuestOnce pushed{};
|
||||
static std::int32_t scePushed = 0;
|
||||
struct OnceCall { GuestOnce* control; std::int32_t* sceControl; Initializer initialize; };
|
||||
static void* APS5_VABI CallOnce(void* argument) {
|
||||
const auto& call = *static_cast<const OnceCall*>(argument);
|
||||
if (call.control) pthread_once_nid_postfix(call.control, call.initialize);
|
||||
else scePthreadOnce(call.sceControl, call.initialize);
|
||||
return nullptr;
|
||||
}
|
||||
static void LeaveInsideInitializer(OnceCall call) {
|
||||
Pthread thread{};
|
||||
Require(scePthreadCreate(&thread, nullptr, CallOnce, &call, nullptr) == 0);
|
||||
Require(scePthreadJoin(thread, nullptr) == 0);
|
||||
}
|
||||
int main() {
|
||||
std::array<std::thread, 16> workers;
|
||||
for (auto& worker : workers) worker = std::thread([] {
|
||||
|
|
@ -41,4 +80,22 @@ int main() {
|
|||
Require(pthread_once_nid_postfix(&control, nullptr) == 22);
|
||||
GuestOnce invalid{9, nullptr};
|
||||
Require(pthread_once_nid_postfix(&invalid, Initialize) == 22);
|
||||
LeaveInsideInitializer({&exited, nullptr, ExitInsideInitializer});
|
||||
Require(exited.state == 0);
|
||||
Require(pthread_once_nid_postfix(&exited, CountRetry) == 0 && exited.state == 1 && retried == 1);
|
||||
LeaveInsideInitializer({&cancelled, nullptr, CancelInsideInitializer});
|
||||
Require(cancelled.state == 0);
|
||||
Require(pthread_once_nid_postfix(&cancelled, CountRetry) == 0 && cancelled.state == 1 && retried == 2);
|
||||
LeaveInsideInitializer({nullptr, &sceExited, ExitInsideInitializer});
|
||||
Require(sceExited == 0);
|
||||
Require(scePthreadOnce(&sceExited, CountRetry) == 0 && sceExited == 1 && retried == 3);
|
||||
LeaveInsideInitializer({nullptr, &sceCancelled, CancelInsideInitializer});
|
||||
Require(sceCancelled == 0);
|
||||
Require(scePthreadOnce(&sceCancelled, CountRetry) == 0 && sceCancelled == 1 && retried == 4);
|
||||
try { pthread_once_nid_postfix(&pushed, PushAndThrow); std::abort(); }
|
||||
catch (int value) { Require(value == 7 && pushed.state == 0); }
|
||||
Require(pthread_once_nid_postfix(&pushed, CountRetry) == 0 && pushed.state == 1 && retried == 5);
|
||||
try { scePthreadOnce(&scePushed, PushAndThrow); std::abort(); }
|
||||
catch (int value) { Require(value == 7 && scePushed == 0); }
|
||||
Require(scePthreadOnce(&scePushed, CountRetry) == 0 && scePushed == 1 && retried == 6);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue