// 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 #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); return Inner->CondVar.wait_for( Lock, std::chrono::milliseconds(TimeoutMs), [&] { return Inner->bSet; } ); } std::unique_lock Lock(Inner->Mutex); Inner->CondVar.wait(Lock, [&] { return Inner->bSet; }); return true; #endif } ////////////////////////////////////////////////////////////////////////// #if ZEN_PLATFORM_WINDOWS NamedEvent::NamedEvent(std::u8string_view EventName) : Event(nullptr) { using namespace std::literals; ExtendableStringBuilder<64> Name; Name << "Local\\"sv; Name << EventName; m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str()); } NamedEvent::NamedEvent(std::string_view EventName) : Event(nullptr) { using namespace std::literals; ExtendableStringBuilder<64> Name; Name << "Local\\"sv; Name << EventName; m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str()); } #endif // ZEN_PLATFORM_WINDOWS ////////////////////////////////////////////////////////////////////////// #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()) { CloseHandle(m_ProcessHandle); m_ProcessHandle = nullptr; } } bool ProcessHandle::Wait(int TimeoutMs) { 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: // What might go wrong here, and what is meaningful to act on? using namespace std::literals; ThrowLastError("Process::Wait failed"sv); } return false; } ////////////////////////////////////////////////////////////////////////// #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