mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
scePthreadKeyDelete frees a key slot and the next scePthreadKeyCreate hands the same index out again, but every thread kept the value it had stored under that index. The new key read the previous key's value instead of NULL, and at thread exit its destructor was called on that stale pointer. Each slot now carries a sequence number, as in FreeBSD libthr. A thread value is visible to scePthreadGetspecific and to the exit destructors only while its sequence matches the slot's, so a new key starts as NULL in every thread, as POSIX requires, and a deleted key reads NULL. scePthreadGetspecific stays lock-free and scePthreadSetspecific no longer takes the key lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
101 lines
4.0 KiB
C++
101 lines
4.0 KiB
C++
#include "SceTypes.hpp"
|
|
#include <atomic>
|
|
#include <cstdlib>
|
|
#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 scePthreadKeyCreate(PthreadKey* key, pthread_key_destructor_func_t destructor);
|
|
int APS5_VABI scePthreadKeyDelete(PthreadKey key);
|
|
void* APS5_VABI scePthreadGetspecific(PthreadKey key);
|
|
int APS5_VABI scePthreadSetspecific(PthreadKey key, void* value);
|
|
}
|
|
|
|
static constexpr int SCE_OK = 0;
|
|
static constexpr int SCE_KERNEL_ERROR_EINVAL = static_cast<int>(0x80020016);
|
|
|
|
static void Require(bool value) { if (!value) std::abort(); }
|
|
|
|
static std::atomic<int> deletedCalls{0}, reusedCalls{0}, liveCalls{0};
|
|
static std::atomic<void*> liveValue{nullptr};
|
|
static std::atomic<int> stage{0};
|
|
static PthreadKey sharedKey = -1;
|
|
static int first = 1, second = 2, third = 3;
|
|
|
|
static void APS5_VABI Deleted(void*) { ++deletedCalls; }
|
|
static void APS5_VABI Reused(void*) { ++reusedCalls; }
|
|
static void APS5_VABI Live(void* value) { ++liveCalls; liveValue = value; }
|
|
|
|
static pthread_key_destructor_func_t Destructor(void (APS5_VABI* function)(void*)) {
|
|
return reinterpret_cast<pthread_key_destructor_func_t>(function);
|
|
}
|
|
|
|
static void WaitFor(int value) {
|
|
while (stage.load() != value) std::this_thread::yield();
|
|
}
|
|
|
|
static void* APS5_VABI ReuseBeforeExit(void*) {
|
|
PthreadKey live = -1;
|
|
Require(scePthreadKeyCreate(&live, Destructor(Live)) == SCE_OK);
|
|
Require(scePthreadSetspecific(live, &third) == SCE_OK);
|
|
PthreadKey deleted = -1;
|
|
Require(scePthreadKeyCreate(&deleted, Destructor(Deleted)) == SCE_OK);
|
|
Require(scePthreadSetspecific(deleted, &first) == SCE_OK);
|
|
Require(scePthreadKeyDelete(deleted) == SCE_OK);
|
|
PthreadKey reused = -1;
|
|
Require(scePthreadKeyCreate(&reused, Destructor(Reused)) == SCE_OK);
|
|
Require(reused == deleted);
|
|
sharedKey = live;
|
|
return nullptr;
|
|
}
|
|
|
|
static void* APS5_VABI HoldAcrossReuse(void*) {
|
|
Require(scePthreadSetspecific(sharedKey, &first) == SCE_OK);
|
|
Require(scePthreadGetspecific(sharedKey) == &first);
|
|
stage = 1;
|
|
WaitFor(2);
|
|
Require(scePthreadGetspecific(sharedKey) == nullptr);
|
|
Require(scePthreadSetspecific(sharedKey, &second) == SCE_OK);
|
|
Require(scePthreadGetspecific(sharedKey) == &second);
|
|
return nullptr;
|
|
}
|
|
|
|
int main() {
|
|
PthreadKey key = -1;
|
|
Require(scePthreadKeyCreate(&key, nullptr) == SCE_OK);
|
|
Require(scePthreadGetspecific(key) == nullptr);
|
|
Require(scePthreadSetspecific(key, &first) == SCE_OK);
|
|
Require(scePthreadGetspecific(key) == &first);
|
|
Require(scePthreadKeyDelete(key) == SCE_OK);
|
|
Require(scePthreadGetspecific(key) == nullptr);
|
|
Require(scePthreadSetspecific(key, &second) == SCE_KERNEL_ERROR_EINVAL);
|
|
Require(scePthreadKeyDelete(key) == SCE_KERNEL_ERROR_EINVAL);
|
|
|
|
PthreadKey reused = -1;
|
|
Require(scePthreadKeyCreate(&reused, nullptr) == SCE_OK);
|
|
Require(reused == key);
|
|
Require(scePthreadGetspecific(reused) == nullptr);
|
|
Require(scePthreadSetspecific(reused, &second) == SCE_OK);
|
|
Require(scePthreadGetspecific(reused) == &second);
|
|
Require(scePthreadKeyDelete(reused) == SCE_OK);
|
|
|
|
Pthread thread = nullptr;
|
|
Require(scePthreadCreate(&thread, nullptr, ReuseBeforeExit, nullptr, nullptr) == SCE_OK);
|
|
Require(scePthreadJoin(thread, nullptr) == SCE_OK);
|
|
Require(liveCalls == 1 && liveValue == &third);
|
|
Require(deletedCalls == 0 && reusedCalls == 0);
|
|
Require(scePthreadKeyDelete(sharedKey) == SCE_OK);
|
|
|
|
Require(scePthreadKeyCreate(&sharedKey, nullptr) == SCE_OK);
|
|
const PthreadKey held = sharedKey;
|
|
Require(scePthreadCreate(&thread, nullptr, HoldAcrossReuse, nullptr, nullptr) == SCE_OK);
|
|
WaitFor(1);
|
|
Require(scePthreadKeyDelete(sharedKey) == SCE_OK);
|
|
Require(scePthreadKeyCreate(&sharedKey, nullptr) == SCE_OK);
|
|
Require(sharedKey == held);
|
|
stage = 2;
|
|
Require(scePthreadJoin(thread, nullptr) == SCE_OK);
|
|
Require(scePthreadGetspecific(sharedKey) == nullptr);
|
|
}
|