/*** Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: IPCMutexFutex.Linux.cpp Date: 2022- Author: Reece Note: ***/ #include #include "IPC.hpp" #include "IPCHandle.hpp" #include "IPCMutexFutex.Linux.hpp" #include #include #include //////////////////////////////////////////////////////////////////////////////////// // SysCalls //////////////////////////////////////////////////////////////////////////////////// #if !defined(FUTEX_OWNER_DIED) #define FUTEX_OWNER_DIED 0x40000000 #endif static int futex(uint32_t *uaddr, int futex_op, uint32_t val, const struct timespec *timeout, /* or: uint32_t val2 */ uint32_t *uaddr2, uint32_t val3) { return syscall(SYS_futex, uaddr, futex_op, val, timeout, uaddr2, val3); } static int futex_wait(uint32_t *addr, uint32_t expected) { return futex(addr, FUTEX_WAIT, expected, 0, 0, 0); } static int futex_wake(uint32_t *addr, uint32_t nthreads) { return futex(addr, FUTEX_WAKE, nthreads, 0, 0, 0); } static long set_robust_list(struct procmon_head *head, size_t len) { return syscall(SYS_set_robust_list, head, len); } static long get_robust_list(int pid, struct robust_list_head **head_ptr, size_t *len_ptr) { return syscall(SYS_get_robust_list, pid, head_ptr, len_ptr); } //////////////////////////////////////////////////////////////////////////////////// // CONSTANTS //////////////////////////////////////////////////////////////////////////////////// static const AuUInt8 kMaxFutexes = 128; static const AuUInt32 kFutexArraySize = AuUInt32(kMaxFutexes) * 4; static const AuUInt32 kFutexValueOwner = 0x80000000; static const AuUInt32 kFutexValueLocked = kFutexValueOwner | 1; static const AuUInt32 kFutexValueUnlocked = kFutexValueOwner; static const AuUInt32 kFutexValueNULL = 0; static const AuUInt32 kFutexValueMask = kFutexValueLocked; static const AuUInt32 kFutexValueMaskOwner = kFutexValueOwner | FUTEX_OWNER_DIED; //////////////////////////////////////////////////////////////////////////////////// // VARIABLES //////////////////////////////////////////////////////////////////////////////////// static AuSPtr gFutexSharedMemory; static AuUInt32 *gFutexArray; static bool gFutexInit {}; static AuIOIPC::IMutexClosedHook * gFutexCallbacks[kMaxFutexes]; static void InitFutexAPI() { static AuThreadPrimitives::SpinLock lock; AU_LOCK_GUARD(lock); if (AuExchange(gFutexInit, true)) { return; } gFutexSharedMemory = AuIOIPC::NewSharedMemory(kFutexArraySize); SysAssert(gFutexSharedMemory); gFutexArray = gFutexSharedMemory->GetMemory().Begin(); } struct FutexContext { bool bInit {}; // https://github.com/bminor/glibc/blob/78fb88827362fbd2cc8aa32892ae5b015106e25c/sysdeps/nptl/dl-tls_init_tp.c#L93 // Every thread should have an arraylist head robust_list_head *head; bool Init(); }; bool FutexContext::Init() { if (AuExchange(this->bInit, true)) { return true; } InitFutexAPI(); return this->bInit; } static thread_local FutexContext tlsFutexContext; static AuUInt8 AllocateFutex() { tlsFutexContext.Init(); if (!gFutexArray) { return 255; } for (AuUInt32 i = 0; i < kMaxFutexes; i++) { if (AuAtomicCompareExchange(&gFutexArray[i], kFutexValueUnlocked, AuUInt32(0)) == 0) { return i; } } return 255; } static bool TryReleaseFutex(AuUInt8 index) { auto &cb = gFutexCallbacks[index]; if (cb) { if (!cb->OnClosed()) { return false; } } cb = nullptr; gFutexArray[index] = 0; gFutexCallbacks[index] = nullptr; return true; } static void FreeFutex(AuUInt8 index) { gFutexArray[index] = 0; gFutexCallbacks[index] = nullptr; } static void LinuxAddRobustFutexSlow(AuUInt32 *futex) { tlsFutexContext.Init(); } static void LinuxRemoveRobustFutexSlow(AuUInt32 *futex) { tlsFutexContext.Init(); } static void LinuxLockFutex(AuUInt32 *futex) { } static void LinuxUnlockFutex(AuUInt32 *futex) { } static void LinuxSuperSecretIOTick() { if (!gFutexInit) { return; } if (!gFutexArray) { return; } for (AuUInt32 i = 0; i < kMaxFutexes; i++) { if (gFutexArray[i] == FUTEX_OWNER_DIED) { TryReleaseFutex(i); } } } #include "IPCPrimitives.Linux.hpp" #include "IPCMemory.Unix.hpp" namespace Aurora::IO::IPC { static AuThreadPrimitives::SpinLock gLock; static AuBST, AuWPtr> gSharedViewCache; ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Mutexes ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// IPCMutexProxy::IPCMutexProxy(AuUInt32 index) : mutex_(), bOwned(true), index_(index) { if (this->mutex_.HasValidHandle()) { if (IO::UNIX::FDServe(this->GetHandle(), this->token_)) { //this->handle_.PushId(EIPCHandleType::eIPCPrimitiveMutex, token); } else { this->mutex_.~LSMutex(); } } this->mem_ = gFutexSharedMemory; } IPCMutexProxy::IPCMutexProxy(int handle, AuSPtr mem, AuUInt32 index) : mutex_(handle), mem_(mem), index_(index) { if (this->mutex_.HasValidHandle()) { if (IO::UNIX::FDServe(this->GetHandle(), this->token_)) { //this->handle_.PushId(EIPCHandleType::eIPCPrimitiveMutex, token); } else { this->mutex_.~LSMutex(); } } } IPCMutexProxy::~IPCMutexProxy() { IO::UNIX::FDServeEnd(this->token_); if (this->bOwned) { FreeFutex(this->index_); } } bool IPCMutexProxy::Unlock() { return this->mutex_.Unlock(); } bool IPCMutexProxy::IsSignaled() { return this->mutex_.IsSignaled(); } bool IPCMutexProxy::WaitOn(AuUInt32 timeout) { return this->mutex_.WaitOn(timeout); } Loop::ELoopSource IPCMutexProxy::GetType() { return this->mutex_.GetType(); } AuString IPCMutexProxy::ExportToString() { IPC::IPCHandle handle; handle.PushId(EIPCHandleType::eIPCPrimitiveMutex, this->token_); SysAssert(this->mem_); handle.PushId(EIPCHandleType::eIPCMemory, AuStaticCast(this->mem_)->handle_.values[0].token); handle.values[0].token.word = this->index_; return handle.ToString(); } AUKN_SYM AuSPtr NewMutex() { auto futex = AllocateFutex(); if (futex == 255) { return {}; } auto object = AuMakeShared(futex); if (!object) { SysPushErrorMem(); return {}; } if (!object->HasValidHandle()) { SysPushErrorIO(); return {}; } return object; } static AuSPtr GetFutexPagesFromCacheOrImport(const IPCToken &mem) { AU_LOCK_GUARD(gLock); auto id = AuMakePair(mem.cookie, mem.pid); auto itr = gSharedViewCache.find(id); if (itr != gSharedViewCache.end()) { auto test = itr->second.lock(); if (test) { return test; } } auto shared = ImportSharedMemoryEx(mem); if (!shared) { SysPushErrorNested(); return {}; } if (!AuTryInsert(gSharedViewCache, id, shared)) { SysPushErrorMem(); // We don't need to fail. We can leak next time. It'll be fine. } return shared; } AuSPtr ImportMutexEx(const IPCToken &handle, const IPCToken &mem, AuUInt32 index) { int fd {-1}; if (!IO::UNIX::FDAccept(handle, fd)) { SysPushErrorNested(); return {}; } auto view = GetFutexPagesFromCacheOrImport(mem); if (!view) { SysPushErrorNested(); return {}; } auto object = AuMakeShared(fd, view, index); if (!object) { SysPushErrorMem(); ::close(fd); return {}; } if (!object->HasValidHandle()) { SysPushErrorIO(); return {}; } return object; } AUKN_SYM AuSPtr ImportMutex(const AuString &handle) { IPC::IPCHandle decodedHandle; if (!decodedHandle.FromString(handle)) { SysPushErrorParseError("Invalid handle: {}", handle); return {}; } auto val = decodedHandle.GetToken(IPC::EIPCHandleType::eIPCPrimitiveMutex, 0); if (!val) { SysPushErrorParseError("Invalid handle: {}", handle); return {}; } auto mem = decodedHandle.GetToken(IPC::EIPCHandleType::eIPCMemory, 0); if (!mem) { SysPushErrorParseError("Invalid handle: {}", handle); return {}; } return ImportMutexEx(val->token, mem->token, val->token.word); } } namespace Aurora::Grug { void LinuxSuperSecretIOTick() { ::LinuxSuperSecretIOTick(); } }