aboutsummaryrefslogtreecommitdiff
path: root/zencore/thread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zencore/thread.cpp')
-rw-r--r--zencore/thread.cpp169
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")