diff options
Diffstat (limited to 'zencore/thread.cpp')
| -rw-r--r-- | zencore/thread.cpp | 330 |
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 |