Files
AnyPS5/core/libs/tests/GuestPthreadAtfork.cpp
T
David e8fd542f03 feat(libkernel): implement pthread_atfork
pthread_atfork (U9t5kJAWPnA, aerolib) appends the prepare, parent and
child handlers to a process-wide list under a lock and returns 0, or
ENOMEM when the entry cannot be allocated, as FreeBSD 11.4's
_pthread_atfork does (lib/libthr/thread/thr_fork.c:78-100, freebsd-src
8c639945a44588f6cfdddfb057bb5aad88735da0). Null handlers are accepted;
FreeBSD's fork skips them. The handlers never run: fork
(libkernel_sys) throws and vfork is absent, so no child process is
created. The list is kept, in registration order, for a later fork
(PthreadAtforkHandlers_nid_no_patch); recorded in TechnicalDebt.

XPSemu (PPSA97358) calls it from a constructor, before main
(ZiZc3/XPSemu 8eec07b4, util/rcu.c:488-495):

    static void __attribute__((__constructor__)) rcu_init(void)
    {
        smp_mb_global_init();
    #ifdef CONFIG_POSIX
        pthread_atfork(rcu_init_lock, rcu_init_unlock, rcu_init_child);
    #endif
        rcu_init_complete();
    }

so a throwing export would stop the title before main. Predicted stop
removed (registry index 255, not run):
FAIL: unresolved ELF import U9t5kJAWPnA from libkernel.prx

guest_pthread_atfork checks registration order, null handlers,
concurrent registration from four threads and that no handler runs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit f37bccf6efd76edcde5d582800c89b83fdd30472)
2026-10-13 07:38:13 +00:00

49 lines
2.0 KiB
C++

#include "prx/libkernel/Pthread/include/Atfork.hpp"
#include <atomic>
#include <cstddef>
#include <cstdlib>
#include <thread>
#include <vector>
static void Require(bool value) { if (!value) std::abort(); }
static std::atomic<int> calls{0};
static void APS5_VABI Prepare() { ++calls; }
static void APS5_VABI Parent() { ++calls; }
static void APS5_VABI Child() { ++calls; }
static bool Same(const PthreadAtforkHandlers& entry, PthreadAtforkHandler prepare, PthreadAtforkHandler parent, PthreadAtforkHandler child) {
return entry.prepare == prepare && entry.parent == parent && entry.child == child;
}
int main() {
Require(PthreadAtforkHandlers_nid_no_patch().empty());
Require(pthread_atfork_nid_postfix(Prepare, Parent, Child) == 0);
Require(pthread_atfork_nid_postfix(nullptr, Parent, nullptr) == 0);
Require(pthread_atfork_nid_postfix(nullptr, nullptr, nullptr) == 0);
Require(pthread_atfork_nid_postfix(Prepare, Parent, Child) == 0);
auto handlers = PthreadAtforkHandlers_nid_no_patch();
Require(handlers.size() == 4);
Require(Same(handlers[0], Prepare, Parent, Child));
Require(Same(handlers[1], nullptr, Parent, nullptr));
Require(Same(handlers[2], nullptr, nullptr, nullptr));
Require(Same(handlers[3], Prepare, Parent, Child));
constexpr std::size_t threadCount = 4;
constexpr std::size_t perThread = 64;
std::vector<std::thread> threads;
for (std::size_t index = 0; index < threadCount; ++index) {
threads.emplace_back([] {
for (std::size_t count = 0; count < perThread; ++count) Require(pthread_atfork_nid_postfix(nullptr, nullptr, Child) == 0);
});
}
for (auto& thread : threads) thread.join();
handlers = PthreadAtforkHandlers_nid_no_patch();
Require(handlers.size() == 4 + threadCount * perThread);
Require(Same(handlers[3], Prepare, Parent, Child));
for (std::size_t index = 4; index < handlers.size(); ++index) Require(Same(handlers[index], nullptr, nullptr, Child));
Require(calls == 0);
return 0;
}