#include "prx/libkernel/include/Api.hpp" #include "prx/libkernel/Time/include/Time.hpp" #include "prx/libc/include/Api.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #else #include #endif static_assert(sizeof(KernelTimespec)==16 && alignof(KernelTimespec)==8 && offsetof(KernelTimespec,tv_nsec)==8); using TimedJoin = decltype(&pthread_timedjoin_np_nid_postfix); static TimedJoin timedJoin; void Require(bool value,const char* message) { if (!value) { std::fprintf(stderr,"timed join: %s\n",message); std::abort(); } } KernelTimespec After(int milliseconds) { KernelTimespec result{}; Require(clock_gettime_nid_postfix(0,&result)==0,"read realtime"); result.tv_nsec+=static_cast(milliseconds)*1000000; result.tv_sec+=result.tv_nsec/1000000000; result.tv_nsec%=1000000000; return result; } struct Gate { std::atomic release{false}, entered{false}, leaving{false}; }; void* APS5_VABI Worker(void* argument) { auto& gate=*static_cast(argument); gate.entered.store(true,std::memory_order_release); while (!gate.release.load(std::memory_order_acquire)) std::this_thread::yield(); gate.leaving.store(true,std::memory_order_release); return reinterpret_cast(std::uintptr_t{0x12345678}); } void Wait(const std::atomic& flag) { const auto deadline=std::chrono::steady_clock::now()+std::chrono::seconds(3); while (!flag.load(std::memory_order_acquire)) { Require(std::chrono::steady_clock::now() entered{false}; int result=-1; void* value=reinterpret_cast(std::uintptr_t{0x55}); }; void* APS5_VABI Joiner(void* argument) { auto& task=*static_cast(argument); const auto deadline=After(3000); task.entered.store(true,std::memory_order_release); task.result=timedJoin(task.target,&task.value,&deadline); return nullptr; } void AwaitClaim(Pthread target) { const KernelTimespec past{0,0}; const auto until=std::chrono::steady_clock::now()+std::chrono::seconds(2); for (;;) { const int result=timedJoin(target,nullptr,&past); if (result==45) return; Require(result==60,"claim probe returns timeout or ENOTSUP"); Require(std::chrono::steady_clock::now()(std::uintptr_t{0x55}); std::uint64_t after=0x5a5a5a5a; } result; const KernelTimespec past{0,0}; *__error_nid_postfix()=123; Require(timedJoin(thread,&result.value,&past)==60,"past deadline expires"); Require(result.value==reinterpret_cast(std::uintptr_t{0x55}),"timeout preserves return slot"); Require(*__error_nid_postfix()==123,"POSIX error preserves errno"); const auto deadline=After(50); const auto originalDeadline=deadline; const auto started=std::chrono::steady_clock::now(); Require(timedJoin(thread,&result.value,&deadline)==60,"future deadline expires"); Require(std::chrono::steady_clock::now()-started>=std::chrono::milliseconds(25),"wait did not expire immediately"); Require(deadline.tv_sec==originalDeadline.tv_sec && deadline.tv_nsec==originalDeadline.tv_nsec,"deadline input is unchanged"); gate.release.store(true,std::memory_order_release); const KernelTimespec distant{std::numeric_limits::max(),999999999}; Require(timedJoin(thread,&result.value,&distant)==0,"timeout leaves target joinable; distant deadline does not overflow"); Require(result.value==reinterpret_cast(std::uintptr_t{0x12345678}),"joined return value"); Require(result.before==0xa5a5a5a5 && result.after==0x5a5a5a5a,"return-slot bounds"); Require(timedJoin(thread,nullptr,&valid)==3,"already joined target"); Require(timedJoin(thread,nullptr,nullptr)==22,"deadline validation precedes joined state"); Gate done; Pthread completed=Start(done); done.release.store(true,std::memory_order_release); Wait(done.leaving); const auto until=std::chrono::steady_clock::now()+std::chrono::seconds(2); for (;;) { const int joined=timedJoin(completed,nullptr,&past); if (joined==0) break; Require(joined==60 && std::chrono::steady_clock::now()(std::uintptr_t{1}),"joiner is canceled"); Require(task.result==-1 && task.value==reinterpret_cast(std::uintptr_t{0x55}),"canceled join did not return or write result"); const KernelTimespec past{0,0}; Require(timedJoin(target,nullptr,&past)==60,"cancellation releases join claim"); gate.release.store(true,std::memory_order_release); const auto deadline=After(1000); Require(timedJoin(target,nullptr,&deadline)==0,"cancellation preserves target joinability"); } else { gate.release.store(true,std::memory_order_release); Require(pthread_join_nid_postfix(joiner,nullptr)==0,"join successful joiner"); Require(task.result==0 && task.value==reinterpret_cast(std::uintptr_t{0x12345678}),"competing join succeeds once"); } } int main(int argc,char** argv) { Require(argc==2,"expected kernel path"); #ifdef _WIN32 const auto library=LoadLibraryA(argv[1]); Require(library!=nullptr,"load kernel"); timedJoin=reinterpret_cast(GetProcAddress(library,"pthread_timedjoin_np_nid_postfix")); #else const auto library=dlopen(argv[1],RTLD_NOW); Require(library!=nullptr,"load kernel"); timedJoin=reinterpret_cast(dlsym(library,"pthread_timedjoin_np_nid_postfix")); #endif Require(timedJoin!=nullptr,"pthread_timedjoin_np export is missing"); Basic(); Contention(false); Contention(true); #ifdef _WIN32 Require(FreeLibrary(library)!=0,"release kernel lookup handle"); #else Require(dlclose(library)==0,"release kernel lookup handle"); #endif std::puts("timed join checks passed"); }