// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #if ZEN_PLATFORM_WINDOWS # include #elif ZEN_PLATFORM_LINUX # include # include # include # include # include # include # include # include # include # include #endif ZEN_THIRD_PARTY_INCLUDES_START #include ZEN_THIRD_PARTY_INCLUDES_END namespace zen { #if ZEN_PLATFORM_WINDOWS // The information on how to set the thread name comes from // a MSDN article: http://msdn2.microsoft.com/en-us/library/xcb2z8hs.aspx const DWORD kVCThreadNameException = 0x406D1388; typedef struct tagTHREADNAME_INFO { DWORD dwType; // Must be 0x1000. LPCSTR szName; // Pointer to name (in user addr space). DWORD dwThreadID; // Thread ID (-1=caller thread). DWORD dwFlags; // Reserved for future use, must be zero. } THREADNAME_INFO; // The SetThreadDescription API was brought in version 1607 of Windows 10. typedef HRESULT(WINAPI* SetThreadDescription)(HANDLE hThread, PCWSTR lpThreadDescription); // This function has try handling, so it is separated out of its caller. void SetNameInternal(DWORD thread_id, const char* name) { THREADNAME_INFO info; info.dwType = 0x1000; info.szName = name; info.dwThreadID = thread_id; info.dwFlags = 0; __try { RaiseException(kVCThreadNameException, 0, sizeof(info) / sizeof(DWORD), reinterpret_cast(&info)); } __except (EXCEPTION_CONTINUE_EXECUTION) { } } #endif void SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName) { #if ZEN_PLATFORM_WINDOWS // The SetThreadDescription API works even if no debugger is attached. static auto SetThreadDescriptionFunc = reinterpret_cast(::GetProcAddress(::GetModuleHandle(L"Kernel32.dll"), "SetThreadDescription")); if (SetThreadDescriptionFunc) { SetThreadDescriptionFunc(::GetCurrentThread(), Utf8ToWide(ThreadName).c_str()); } // The debugger needs to be around to catch the name in the exception. If // there isn't a debugger, we are just needlessly throwing an exception. if (!::IsDebuggerPresent()) return; 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 void RwLock::AcquireShared() { m_Mutex.lock_shared(); } void RwLock::ReleaseShared() { m_Mutex.unlock_shared(); } void RwLock::AcquireExclusive() { m_Mutex.lock(); } void RwLock::ReleaseExclusive() { m_Mutex.unlock(); } ////////////////////////////////////////////////////////////////////////// #if !ZEN_PLATFORM_WINDOWS struct EventInner { std::mutex Mutex; std::condition_variable CondVar; bool volatile bSet = false; }; #endif // !ZEN_PLATFORM_WINDOWS Event::Event() { 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() { 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; DWORD Result = WaitForSingleObject(m_EventHandle, Timeout); if (Result == WAIT_FAILED) { zen::ThrowLastError("Event wait failed"sv); } 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 } ////////////////////////////////////////////////////////////////////////// #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; Name << "Local\\"sv; 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() { Close(); } void NamedEvent::Close() { if (m_EventHandle == nullptr) { return; } #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) { CloseHandle(m_MutexHandle); } } bool NamedMutex::Create(std::string_view MutexName) { ZEN_ASSERT(m_MutexHandle == nullptr); using namespace std::literals; ExtendableStringBuilder<64> Name; Name << "Global\\"sv; Name << MutexName; m_MutexHandle = CreateMutexA(nullptr, /* InitialOwner */ TRUE, Name.c_str()); return !!m_MutexHandle; } bool NamedMutex::Exists(std::string_view MutexName) { using namespace std::literals; ExtendableStringBuilder<64> Name; Name << "Global\\"sv; Name << MutexName; void* MutexHandle = OpenMutexA(SYNCHRONIZE, /* InheritHandle */ FALSE, Name.c_str()); if (MutexHandle == nullptr) { return false; } CloseHandle(MutexHandle); return true; } #endif // 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() { Reset(); } void ProcessHandle::Initialize(int Pid) { ZEN_ASSERT(m_ProcessHandle == nullptr); #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)); } m_Pid = 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 bActive; } bool ProcessHandle::IsValid() const { return (m_ProcessHandle != nullptr); } void ProcessHandle::Terminate(int ExitCode) { if (!IsRunning()) { 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 (!bSuccess) { // What might go wrong here, and what is meaningful to act on? } } void 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); switch (WaitResult) { case WAIT_OBJECT_0: return true; case WAIT_TIMEOUT: return false; case WAIT_FAILED: 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; } if (kill(m_Pid, 0) < 0) { if (zen::GetLastError() == ESRCH) { return true; } break; } 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); } ////////////////////////////////////////////////////////////////////////// #if ZEN_PLATFORM_WINDOWS ProcessMonitor::ProcessMonitor() { } ProcessMonitor::~ProcessMonitor() { RwLock::ExclusiveLockScope _(m_Lock); for (HANDLE& Proc : m_ProcessHandles) { CloseHandle(Proc); Proc = 0; } } bool ProcessMonitor::IsRunning() { RwLock::ExclusiveLockScope _(m_Lock); bool FoundOne = false; for (HANDLE& Proc : m_ProcessHandles) { DWORD ExitCode = 0; GetExitCodeProcess(Proc, &ExitCode); if (ExitCode != STILL_ACTIVE) { CloseHandle(Proc); Proc = 0; } else { // Still alive FoundOne = true; } } std::erase_if(m_ProcessHandles, [](HANDLE Handle) { return Handle == 0; }); return FoundOne; } void ProcessMonitor::AddPid(int Pid) { HANDLE ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); if (ProcessHandle) { RwLock::ExclusiveLockScope _(m_Lock); m_ProcessHandles.push_back(ProcessHandle); } } bool ProcessMonitor::IsActive() const { RwLock::SharedLockScope _(m_Lock); return m_ProcessHandles.empty() == false; } #endif // ZEN_PLATFORM_WINDOWS ////////////////////////////////////////////////////////////////////////// bool IsProcessRunning(int pid) { // This function is arguably not super useful, a pid can be re-used // by the OS so holding on to a pid and polling it over some time // period will not necessarily tell you what you probably want to know. #if ZEN_PLATFORM_WINDOWS HANDLE hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); if (!hProc) { DWORD Error = zen::GetLastError(); if (Error == ERROR_INVALID_PARAMETER) { return false; } using namespace fmt::literals; ThrowSystemError(Error, "failed to open process with pid {}"_format(pid)); } CloseHandle(hProc); return true; #else char Buffer[64]; sprintf(Buffer, "/proc/%d", pid); return access(Buffer, F_OK) == 0; #endif } int GetCurrentProcessId() { #if ZEN_PLATFORM_WINDOWS return ::GetCurrentProcessId(); #else return int(getpid()); #endif } int GetCurrentThreadId() { #if ZEN_PLATFORM_WINDOWS return ::GetCurrentThreadId(); #else return int(gettid()); #endif } void Sleep(int ms) { #if ZEN_PLATFORM_WINDOWS ::Sleep(ms); #else usleep(ms * 1000U); #endif } ////////////////////////////////////////////////////////////////////////// // // 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