aboutsummaryrefslogtreecommitdiff
path: root/zencore/thread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zencore/thread.cpp')
-rw-r--r--zencore/thread.cpp330
1 files changed, 303 insertions, 27 deletions
diff --git a/zencore/thread.cpp b/zencore/thread.cpp
index da711fe89..7f8042ae0 100644
--- a/zencore/thread.cpp
+++ b/zencore/thread.cpp
@@ -4,10 +4,21 @@
#include <zencore/except.h>
#include <zencore/string.h>
+#include <zencore/testing.h>
#if ZEN_PLATFORM_WINDOWS
# include <zencore/windows.h>
#elif ZEN_PLATFORM_LINUX
+# include <chrono>
+# include <condition_variable>
+# include <mutex>
+
+# include <poll.h>
+# include <pthread.h>
+# include <signal.h>
+# include <sys/socket.h>
+# include <sys/un.h>
+# include <time.h>
# include <unistd.h>
#endif
@@ -69,6 +80,8 @@ SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName)
std::string ThreadNameZ{ThreadName};
SetNameInternal(GetCurrentThreadId(), ThreadNameZ.c_str());
#else
+ std::string ThreadNameZ{ThreadName};
+ pthread_setname_np(pthread_self(), ThreadNameZ.c_str());
#endif
} // namespace zen
@@ -98,40 +111,80 @@ RwLock::ReleaseExclusive()
//////////////////////////////////////////////////////////////////////////
-#if ZEN_PLATFORM_WINDOWS
+#if !ZEN_PLATFORM_WINDOWS
+struct EventInner
+{
+ std::mutex Mutex;
+ std::condition_variable CondVar;
+ bool volatile bSet = false;
+};
+#endif // !ZEN_PLATFORM_WINDOWS
Event::Event()
{
- m_EventHandle = CreateEvent(nullptr, true, false, nullptr);
+ bool bManualReset = true;
+ bool bInitialState = false;
+
+#if ZEN_PLATFORM_WINDOWS
+ m_EventHandle = CreateEvent(nullptr, bManualReset, bInitialState, nullptr);
+#else
+ ZEN_UNUSED(bManualReset);
+ auto* Inner = new EventInner();
+ Inner->bSet = bInitialState;
+ m_EventHandle = Inner;
+#endif
}
Event::~Event()
{
- CloseHandle(m_EventHandle);
+ Close();
}
void
Event::Set()
{
+#if ZEN_PLATFORM_WINDOWS
SetEvent(m_EventHandle);
+#else
+ auto* Inner = (EventInner*)m_EventHandle;
+ {
+ std::unique_lock Lock(Inner->Mutex);
+ Inner->bSet = true;
+ }
+ Inner->CondVar.notify_all();
+#endif
}
void
Event::Reset()
{
+#if ZEN_PLATFORM_WINDOWS
ResetEvent(m_EventHandle);
+#else
+ auto* Inner = (EventInner*)m_EventHandle;
+ {
+ std::unique_lock Lock(Inner->Mutex);
+ Inner->bSet = false;
+ }
+#endif
}
void
Event::Close()
{
+#if ZEN_PLATFORM_WINDOWS
CloseHandle(m_EventHandle);
+#else
+ auto* Inner = (EventInner*)m_EventHandle;
+ delete Inner;
+#endif
m_EventHandle = nullptr;
}
bool
Event::Wait(int TimeoutMs)
{
+#if ZEN_PLATFORM_WINDOWS
using namespace std::literals;
const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;
@@ -144,12 +197,50 @@ Event::Wait(int TimeoutMs)
}
return (Result == WAIT_OBJECT_0);
+#else
+ auto* Inner = (EventInner*)m_EventHandle;
+
+ if (TimeoutMs >= 0)
+ {
+ std::unique_lock Lock(Inner->Mutex);
+
+ if (Inner->bSet)
+ {
+ return true;
+ }
+
+ return Inner->CondVar.wait_for(
+ Lock,
+ std::chrono::milliseconds(TimeoutMs),
+ [&] { return Inner->bSet; }
+ );
+ }
+
+ std::unique_lock Lock(Inner->Mutex);
+
+ if (!Inner->bSet)
+ {
+ Inner->CondVar.wait(Lock, [&] { return Inner->bSet; });
+ }
+
+ return true;
+#endif
}
//////////////////////////////////////////////////////////////////////////
-NamedEvent::NamedEvent(std::u8string_view EventName) : Event(nullptr)
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MACOS
+struct NamedEventPosix
+{
+ int SocketFd = 0;
+ sockaddr_un SocketAddr = {};
+ bool bBound = false;
+};
+#endif
+
+NamedEvent::NamedEvent(std::string_view EventName)
{
+#if ZEN_PLATFORM_WINDOWS
using namespace std::literals;
ExtendableStringBuilder<64> Name;
@@ -157,19 +248,117 @@ NamedEvent::NamedEvent(std::u8string_view EventName) : Event(nullptr)
Name << EventName;
m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str());
+#else
+ int SocketFd = socket(AF_UNIX, SOCK_DGRAM, SOCK_CLOEXEC);
+ if (SocketFd < 0)
+ {
+ ThrowLastError("Failed to create IPC socket");
+ }
+
+ auto* Inner = new NamedEventPosix();
+ Inner->SocketFd = SocketFd;
+
+ char* PathPtr = Inner->SocketAddr.sun_path;
+ size_t PathLen = sizeof(Inner->SocketAddr.sun_path) - 1; // -1 for null-term
+# if ZEN_PLATFORM_LINUX
+ PathPtr[0] = '\0'; // make the domain socket...
+ PathPtr += 1; // ...use the abstract namespace
+ PathLen -= 1;
+# endif
+ EventName.copy(PathPtr, PathLen);
+
+ m_EventHandle = Inner;
+#endif
}
-NamedEvent::NamedEvent(std::string_view EventName) : Event(nullptr)
+NamedEvent::~NamedEvent()
{
- using namespace std::literals;
+ Close();
+}
- ExtendableStringBuilder<64> Name;
- Name << "Local\\"sv;
- Name << EventName;
+void NamedEvent::Close()
+{
+ if (m_EventHandle == nullptr)
+ {
+ return;
+ }
- m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str());
+#if ZEN_PLATFORM_WINDOWS
+ CloseHandle(m_EventHandle);
+#else
+ auto* Inner = (NamedEventPosix*)m_EventHandle;
+ close(Inner->SocketFd);
+ delete Inner;
+#endif
+
+ m_EventHandle = nullptr;
+}
+
+
+void NamedEvent::Set()
+{
+#if ZEN_PLATFORM_WINDOWS
+ SetEvent(m_EventHandle);
+#else
+ auto* Inner = (NamedEventPosix*)m_EventHandle;
+
+ uint8_t OneByte = 0x49;
+ sendto(
+ Inner->SocketFd,
+ &OneByte, sizeof(OneByte),
+ 0, (sockaddr*)&Inner->SocketAddr, sizeof(Inner->SocketAddr)
+ );
+#endif
+}
+
+bool NamedEvent::Wait(int TimeoutMs)
+{
+#if ZEN_PLATFORM_WINDOWS
+ const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;
+
+ DWORD Result = WaitForSingleObject(m_EventHandle, Timeout);
+
+ if (Result == WAIT_FAILED)
+ {
+ using namespace std::literals;
+ zen::ThrowLastError("Event wait failed"sv);
+ }
+
+ return (Result == WAIT_OBJECT_0);
+#else
+ auto* Inner = (NamedEventPosix*)m_EventHandle;
+ int SocketFd = Inner->SocketFd;
+
+ int Result;
+
+ if (!Inner->bBound)
+ {
+ Result = bind(SocketFd, (sockaddr*)&(Inner->SocketAddr), sizeof(Inner->SocketAddr));
+ if (!Result)
+ {
+ zen::ThrowLastError("Bind IPC socket failed");
+ }
+ Inner->bBound = true;
+ }
+
+ pollfd PollFd = { SocketFd, POLLIN };
+ Result = poll(&PollFd, 1, TimeoutMs);
+ if (Result > 0)
+ {
+ uint8_t OneByte;
+ Result = recv(SocketFd, &OneByte, sizeof(OneByte), 0);
+
+ return true;
+ }
+
+ return false;
+#endif
}
+//////////////////////////////////////////////////////////////////////////
+
+#if ZEN_PLATFORM_WINDOWS
+
NamedMutex::~NamedMutex()
{
if (m_MutexHandle)
@@ -217,20 +406,26 @@ NamedMutex::Exists(std::string_view MutexName)
#endif // ZEN_PLATFORM_WINDOWS
-#if ZEN_PLATFORM_WINDOWS
-
//////////////////////////////////////////////////////////////////////////
ProcessHandle::ProcessHandle() = default;
+#if ZEN_PLATFORM_WINDOWS
void
ProcessHandle::Initialize(void* ProcessHandle)
{
ZEN_ASSERT(m_ProcessHandle == nullptr);
+
+ if (ProcessHandle == INVALID_HANDLE_VALUE)
+ {
+ ProcessHandle = nullptr;
+ }
+
// TODO: perform some debug verification here to verify it's a valid handle?
m_ProcessHandle = ProcessHandle;
m_Pid = GetProcessId(m_ProcessHandle);
}
+#endif // ZEN_PLATFORM_WINDOWS
ProcessHandle::~ProcessHandle()
{
@@ -241,12 +436,21 @@ void
ProcessHandle::Initialize(int Pid)
{
ZEN_ASSERT(m_ProcessHandle == nullptr);
- m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid);
- using namespace fmt::literals;
+#if ZEN_PLATFORM_WINDOWS
+ m_ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid);
+#elif ZEN_PLATFORM_LINUX
+ 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));
}
@@ -256,29 +460,51 @@ ProcessHandle::Initialize(int Pid)
bool
ProcessHandle::IsRunning() const
{
+ bool bActive = false;
+
+#if ZEN_PLATFORM_WINDOWS
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
+#endif
- return ExitCode == STILL_ACTIVE;
+ return bActive;
}
bool
ProcessHandle::IsValid() const
{
- return (m_ProcessHandle != nullptr) && (m_ProcessHandle != INVALID_HANDLE_VALUE);
+ return (m_ProcessHandle != nullptr);
}
void
ProcessHandle::Terminate(int ExitCode)
{
- if (IsRunning())
+ if (!IsRunning())
{
- TerminateProcess(m_ProcessHandle, ExitCode);
+ return;
}
+ bool bSuccess = false;
+
+#if ZEN_PLATFORM_WINDOWS
+ TerminateProcess(m_ProcessHandle, ExitCode);
DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, INFINITE);
+ bSuccess = (WaitResult != WAIT_OBJECT_0);
+#elif ZEN_PLATFORM_LINUX
+ ZEN_UNUSED(ExitCode);
+ bSuccess = (kill(m_Pid, SIGKILL) == 0);
+#else
+# error Check kill() on this platform
+#endif
- if (WaitResult != WAIT_OBJECT_0)
+ if (!bSuccess)
{
// What might go wrong here, and what is meaningful to act on?
}
@@ -289,14 +515,20 @@ ProcessHandle::Reset()
{
if (IsValid())
{
+#if ZEN_PLATFORM_WINDOWS
CloseHandle(m_ProcessHandle);
+#endif
m_ProcessHandle = nullptr;
+ m_Pid = 0;
}
}
bool
ProcessHandle::Wait(int TimeoutMs)
{
+ using namespace std::literals;
+
+#if ZEN_PLATFORM_WINDOWS
const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;
const DWORD WaitResult = WaitForSingleObject(m_ProcessHandle, Timeout);
@@ -310,15 +542,36 @@ ProcessHandle::Wait(int TimeoutMs)
return false;
case WAIT_FAILED:
- // What might go wrong here, and what is meaningful to act on?
- using namespace std::literals;
- ThrowLastError("Process::Wait failed"sv);
+ break;
}
+#elif ZEN_PLATFORM_LINUX
+ const int SleepMs = 20;
+ timespec SleepTime = { 0, SleepMs * 1000 * 1000 };
+ for (int i = 0; ; i += SleepMs)
+ {
+ if (i >= TimeoutMs)
+ {
+ return false;
+ }
- return false;
-}
+ if (kill(m_Pid, 0) < 0)
+ {
+ if (zen::GetLastError() == ESRCH)
+ {
+ return true;
+ }
+ break;
+ }
-#endif // ZEN_PLATFORM_WINDOWS
+ nanosleep(&SleepTime, nullptr);
+ }
+#else
+# error Check kill() on this platform
+#endif
+
+ // What might go wrong here, and what is meaningful to act on?
+ ThrowLastError("Process::Wait failed"sv);
+}
//////////////////////////////////////////////////////////////////////////
@@ -418,7 +671,9 @@ IsProcessRunning(int pid)
return true;
#else
- ZEN_NOT_IMPLEMENTED();
+ char Buffer[64];
+ sprintf(Buffer, "/proc/%d", pid);
+ return access(Buffer, F_OK) == 0;
#endif
}
@@ -428,7 +683,17 @@ GetCurrentProcessId()
#if ZEN_PLATFORM_WINDOWS
return ::GetCurrentProcessId();
#else
- return getpid();
+ return int(getpid());
+#endif
+}
+
+int
+GetCurrentThreadId()
+{
+#if ZEN_PLATFORM_WINDOWS
+ return ::GetCurrentThreadId();
+#else
+ return int(gettid());
#endif
}
@@ -447,9 +712,20 @@ Sleep(int ms)
// Testing related code follows...
//
+#if ZEN_WITH_TESTS
+
void
thread_forcelink()
{
}
+TEST_CASE("Thread")
+{
+ int Pid = GetCurrentProcessId();
+ CHECK(Pid > 0);
+ CHECK(IsProcessRunning(Pid));
+}
+
+#endif // ZEN_WITH_TESTS
+
} // namespace zen