diff options
Diffstat (limited to 'zencore/thread.cpp')
| -rw-r--r-- | zencore/thread.cpp | 169 |
1 files changed, 132 insertions, 37 deletions
diff --git a/zencore/thread.cpp b/zencore/thread.cpp index 596549bb5..97d44e733 100644 --- a/zencore/thread.cpp +++ b/zencore/thread.cpp @@ -8,17 +8,22 @@ #include <zencore/string.h> #include <zencore/testing.h> +#if ZEN_PLATFORM_LINUX +# if !defined(_GNU_SOURCE) +# define _GNU_SOURCE // for semtimedop() +# endif +#endif + #if ZEN_PLATFORM_WINDOWS # include <shellapi.h> # include <Shlobj.h> # include <zencore/windows.h> -#elif ZEN_PLATFORM_LINUX +#else # include <chrono> # include <condition_variable> # include <mutex> # include <fcntl.h> -# include <mqueue.h> # include <pthread.h> # include <signal.h> # include <sys/file.h> @@ -27,6 +32,14 @@ # include <unistd.h> #endif +#if ZEN_PLATFORM_LINUX +# include <mqueue.h> +#endif + +#if ZEN_PLATFORM_MAC +# include <sys/sem.h> +#endif + #include <thread> ZEN_THIRD_PARTY_INCLUDES_START @@ -278,9 +291,9 @@ NamedEvent::NamedEvent(std::string_view EventName) int LockResult = flock(Inner, LOCK_EX | LOCK_NB); if (LockResult == 0) { - // This is really thread safe as the message queue could be set between - // getting the exclusive lock and checking the queue. But for the our - // simple synchronising of process, this should be okay. + // This isn't really thread safe as the message queue could be set + // between getting the exclusive lock and checking the queue. But for + // the our simple synchronising of process, this should be okay. while (!IsMessageQueueEmpty(Inner)) { char Sink; @@ -289,8 +302,51 @@ NamedEvent::NamedEvent(std::string_view EventName) } m_EventHandle = (void*)intptr_t(Inner); -#else -# error Implement NamedEvent for this platform +#elif ZEN_PLATFORM_MAC + // Create a file to back the semaphore + ExtendableStringBuilder<64> EventPath; + EventPath << "/tmp/" << EventName; + + int Fd = open(EventPath.c_str(), O_RDWR|O_CREAT, 0644); + if (Fd < 0) + { + ThrowLastError(fmt::format("Failed to create '{}' for named event", EventPath)); + } + + // Use the file path to generate an IPC key + key_t IpcKey = ftok(EventPath.c_str(), 1); + if (IpcKey < 0) + { + close(Fd); + ThrowLastError("Failed to create an SysV IPC key"); + } + + // Use the key to create/open the semaphore + int Sem = semget(IpcKey, 1, 0600 | IPC_CREAT); + if (Sem < 0) + { + close(Fd); + ThrowLastError("Failed creating an SysV semaphore"); + } + + // Atomically claim ownership of the semaphore's key. The owner initialises + // the semaphore to 1 so we can use the wait-for-zero op as that does not + // modify the semaphore's value on a successful wait. + int LockResult = flock(Fd, LOCK_EX | LOCK_NB); + if (LockResult == 0) + { + // This isn't thread safe really. Another thread could open the same + // semaphore and successfully wait on it in the period of time where + // this comment is but before the semaphore's initialised. + semctl(Sem, 0, SETVAL, 1); + } + + // Pack into the handle + static_assert(sizeof(Sem) + sizeof(Fd) <= sizeof(void*), "Semaphore packing assumptions not met"); + intptr_t Packed; + Packed = intptr_t(Sem) << 32; + Packed |= intptr_t(Fd) & 0xffff'ffff; + m_EventHandle = (void*)Packed; #endif } @@ -310,7 +366,7 @@ NamedEvent::Close() #if ZEN_PLATFORM_WINDOWS CloseHandle(m_EventHandle); #elif ZEN_PLATFORM_LINUX - int Inner = int(intptr_t(m_EventHandle)); + int Inner = int(intptr_t(m_EventHandle)); if (flock(Inner, LOCK_EX | LOCK_NB) == 0) { @@ -320,6 +376,20 @@ NamedEvent::Close() } close(Inner); +#elif ZEN_PLATFORM_MAC + int Fd = int(intptr_t(m_EventHandle) & 0xffff'ffff); + + if (flock(Fd, LOCK_EX | LOCK_NB) == 0) + { + std::filesystem::path Name = PathFromHandle((void*)(intptr_t(Fd))); + unlink(Name.c_str()); + + flock(Fd, LOCK_UN | LOCK_NB); + close(Fd); + + int Sem = int(intptr_t(m_EventHandle) >> 32); + semctl(Sem, 0, IPC_RMID); + } #endif m_EventHandle = nullptr; @@ -343,6 +413,9 @@ NamedEvent::Set() { ThrowLastError("Unable to send set message to queue"); } +#elif ZEN_PLATFORM_MAC + int Sem = int(intptr_t(m_EventHandle) >> 32); + semctl(Sem, 0, SETVAL, 0); #endif } @@ -370,8 +443,8 @@ NamedEvent::Wait(int TimeoutMs) } struct timeval TimeoutValue = { - .tv_sec = 0, - .tv_usec = TimeoutMs << 10, + .tv_sec = (TimeoutMs >> 10), + .tv_usec = (TimeoutMs & 0x3ff) << 10, }; struct timeval* TimeoutPtr = (TimeoutMs < 0) ? nullptr : &TimeoutValue; @@ -379,6 +452,43 @@ NamedEvent::Wait(int TimeoutMs) FD_ZERO(&FdSet); FD_SET(Inner, &FdSet); return select(Inner + 1, &FdSet, nullptr, nullptr, TimeoutPtr) > 0; +#elif ZEN_PLATFORM_MAC + int Sem = int(intptr_t(m_EventHandle) >> 32); + + int Result; + struct sembuf SemOp = {}; + + if (TimeoutMs < 0) + { + Result = semop(Sem, &SemOp, 1); + return Result == 0; + } + +#if defined(_GNU_SOURCE) + struct timespec TimeoutValue = { + .tv_sec = TimeoutMs >> 10, + .tv_nsec = (TimeoutMs & 0x3ff) << 20, + }; + int Result = semtimedop(Sem, &SemOp, 1, &TimeoutValue); + return Result == 0; +#else + const int SleepTimeMs = 10; + SemOp.sem_flg = IPC_NOWAIT; + do + { + Result = semop(Sem, &SemOp, 1); + if (Result == 0 || errno != EAGAIN) + { + break; + } + + Sleep(SleepTimeMs); + TimeoutMs -= SleepTimeMs; + } + while (TimeoutMs > 0); + + return Result == 0; +#endif // _GNU_SOURCE #endif } @@ -511,19 +621,16 @@ ProcessHandle::Initialize(int Pid) #if ZEN_PLATFORM_WINDOWS m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); -#elif ZEN_PLATFORM_LINUX +#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC if (Pid > 0) { m_ProcessHandle = (void*)(intptr_t(Pid)); } -#else -# error Check process control on this platform #endif if (!m_ProcessHandle) { - using namespace fmt::literals; - ThrowLastError("ProcessHandle::Initialize(pid: {}) failed"_format(Pid)); + ThrowLastError(fmt::format("ProcessHandle::Initialize(pid: {}) failed", Pid)); } m_Pid = Pid; @@ -538,12 +645,8 @@ ProcessHandle::IsRunning() const DWORD ExitCode = 0; GetExitCodeProcess(m_ProcessHandle, &ExitCode); bActive = (ExitCode == STILL_ACTIVE); -#elif ZEN_PLATFORM_LINUX - StringBuilder<64> ProcPath; - ProcPath << "/proc/" << m_Pid; - bActive = (access(ProcPath.c_str(), F_OK) != 0); -#else -# error Check process control on this platform +#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC + bActive = (kill(pid_t(m_Pid), 0) == 0); #endif return bActive; @@ -569,11 +672,9 @@ ProcessHandle::Terminate(int ExitCode) TerminateProcess(m_ProcessHandle, ExitCode); DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, INFINITE); bSuccess = (WaitResult != WAIT_OBJECT_0); -#elif ZEN_PLATFORM_LINUX +#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC ZEN_UNUSED(ExitCode); - bSuccess = (kill(m_Pid, SIGKILL) == 0); -#else -# error Check kill() on this platform + bSuccess = (kill(m_Pid, SIGKILL) == 0); #endif if (!bSuccess) @@ -616,7 +717,7 @@ ProcessHandle::Wait(int TimeoutMs) case WAIT_FAILED: break; } -#elif ZEN_PLATFORM_LINUX +#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC const int SleepMs = 20; timespec SleepTime = {0, SleepMs * 1000 * 1000}; for (int i = 0;; i += SleepMs) @@ -640,8 +741,6 @@ ProcessHandle::Wait(int TimeoutMs) nanosleep(&SleepTime, nullptr); } -#else -# error Check kill() on this platform #endif // What might go wrong here, and what is meaningful to act on? @@ -1012,20 +1111,14 @@ IsProcessRunning(int pid) return false; } - using namespace fmt::literals; - ThrowSystemError(Error, "failed to open process with pid {}"_format(pid)); + ThrowSystemError(Error, fmt::format("failed to open process with pid {}", pid)); } CloseHandle(hProc); return true; -#elif ZEN_PLATFORM_LINUX - char Buffer[64]; - sprintf(Buffer, "/proc/%d", pid); - return access(Buffer, F_OK) == 0; -#elif ZEN_PLATFORM_MAC - kill(pid_t(pid), 0); - return errno != ESRCH; +#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC + return (kill(pid_t(pid), 0) == 0); #endif } @@ -1078,6 +1171,8 @@ TEST_CASE("Thread") int Pid = GetCurrentProcessId(); CHECK(Pid > 0); CHECK(IsProcessRunning(Pid)); + + CHECK_FALSE(GetCurrentThreadId() == 0); } TEST_CASE("BuildArgV") |