// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #include #include #include #if ZEN_PLATFORM_LINUX # if !defined(_GNU_SOURCE) # define _GNU_SOURCE // for semtimedop() # endif #endif #if !ZEN_USE_WINDOWS_EVENTS # include # include # include #endif #if ZEN_PLATFORM_WINDOWS # include #else # 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) { std::string ThreadNameZ{ThreadName}; const int ThreadId = GetCurrentThreadId(); #if ZEN_WITH_TRACE trace::ThreadRegister(ThreadNameZ.c_str(), /* system id */ ThreadId, /* sort id */ 0); #endif // ZEN_WITH_TRACE #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()) { SetNameInternal(ThreadId, ThreadNameZ.c_str()); } #elif ZEN_PLATFORM_MAC pthread_setname_np(ThreadNameZ.c_str()); #else pthread_setname_np(pthread_self(), ThreadNameZ.c_str()); #endif } // namespace zen void RwLock::AcquireShared() noexcept { m_Mutex.lock_shared(); } void RwLock::ReleaseShared() noexcept { m_Mutex.unlock_shared(); } void RwLock::AcquireExclusive() noexcept { m_Mutex.lock(); } void RwLock::ReleaseExclusive() noexcept { m_Mutex.unlock(); } ////////////////////////////////////////////////////////////////////////// #if !ZEN_USE_WINDOWS_EVENTS struct EventInner { std::mutex Mutex; std::condition_variable CondVar; std::atomic_bool bSet{false}; }; #endif // !ZEN_PLATFORM_WINDOWS Event::Event() { bool bManualReset = true; bool bInitialState = false; #if ZEN_USE_WINDOWS_EVENTS 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_USE_WINDOWS_EVENTS SetEvent(m_EventHandle); #else auto* Inner = (EventInner*)m_EventHandle; { std::unique_lock Lock(Inner->Mutex); Inner->bSet.store(true); } Inner->CondVar.notify_all(); #endif } void Event::Reset() { #if ZEN_USE_WINDOWS_EVENTS ResetEvent(m_EventHandle); #else auto* Inner = (EventInner*)m_EventHandle; { std::unique_lock Lock(Inner->Mutex); Inner->bSet.store(false); } #endif } void Event::Close() { #if ZEN_USE_WINDOWS_EVENTS CloseHandle(m_EventHandle); #else auto* Inner = (EventInner*)m_EventHandle; { std::unique_lock Lock(Inner->Mutex); Inner->bSet.store(true); } delete Inner; #endif m_EventHandle = nullptr; } bool Event::Wait(int TimeoutMs) { #if ZEN_USE_WINDOWS_EVENTS 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 = reinterpret_cast(m_EventHandle); if (Inner->bSet.load()) { return true; } if (TimeoutMs >= 0) { std::unique_lock Lock(Inner->Mutex); if (Inner->bSet.load()) { return true; } return Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(TimeoutMs), [&] { return Inner->bSet.load(); }); } // Infinite wait. This does not actually call the wait() function to work around // an apparent issue in the underlying implementation. std::unique_lock Lock(Inner->Mutex); if (!Inner->bSet.load()) { while (!Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(1000), [&] { return Inner->bSet.load(); })) ; } return true; #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()); #elif ZEN_PLATFORM_LINUX || 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 | O_CLOEXEC, 0666); if (Fd < 0) { ThrowLastError(fmt::format("Failed to create '{}' for named event", EventPath)); } fchmod(Fd, 0666); // 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 } NamedEvent::~NamedEvent() { Close(); } void NamedEvent::Close() { if (m_EventHandle == nullptr) { return; } #if ZEN_PLATFORM_WINDOWS CloseHandle(m_EventHandle); #elif ZEN_PLATFORM_LINUX || 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; } void NamedEvent::Set() { #if ZEN_PLATFORM_WINDOWS SetEvent(m_EventHandle); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC int Sem = int(intptr_t(m_EventHandle) >> 32); semctl(Sem, 0, SETVAL, 0); #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); #elif ZEN_PLATFORM_LINUX || 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) const int TimeoutSec = TimeoutMs / 1000; struct timespec TimeoutValue = { .tv_sec = TimeoutSec, .tv_nsec = (TimeoutMs - (TimeoutSec * 1000)) * 1000000, }; Result = semtimedop(Sem, &SemOp, 1, &TimeoutValue); # 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); # endif // _GNU_SOURCE return Result == 0; #endif } ////////////////////////////////////////////////////////////////////////// NamedMutex::~NamedMutex() { #if ZEN_PLATFORM_WINDOWS if (m_MutexHandle) { CloseHandle(m_MutexHandle); } #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC int Inner = int(intptr_t(m_MutexHandle)); flock(Inner, LOCK_UN); close(Inner); #endif } bool NamedMutex::Create(std::string_view MutexName) { #if ZEN_PLATFORM_WINDOWS 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; #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC ExtendableStringBuilder<64> Name; Name << "/tmp/" << MutexName; int Inner = open(Name.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666); if (Inner < 0) { return false; } fchmod(Inner, 0666); if (flock(Inner, LOCK_EX) != 0) { close(Inner); Inner = 0; return false; } m_MutexHandle = (void*)(intptr_t(Inner)); return true; #endif // ZEN_PLATFORM_WINDOWS } bool NamedMutex::Exists(std::string_view MutexName) { #if ZEN_PLATFORM_WINDOWS 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; #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC ExtendableStringBuilder<64> Name; Name << "/tmp/" << MutexName; bool bExists = false; int Fd = open(Name.c_str(), O_RDWR | O_CLOEXEC); if (Fd >= 0) { if (flock(Fd, LOCK_EX | LOCK_NB) == 0) { flock(Fd, LOCK_UN | LOCK_NB); } else { bExists = true; } close(Fd); } return bExists; #endif // ZEN_PLATFORM_WINDOWS } int GetCurrentThreadId() { #if ZEN_PLATFORM_WINDOWS return ::GetCurrentThreadId(); #elif ZEN_PLATFORM_LINUX return int(syscall(SYS_gettid)); #elif ZEN_PLATFORM_MAC return int(pthread_mach_thread_np(pthread_self())); #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_SUITE_BEGIN("core.thread"); TEST_CASE("GetCurrentThreadId") { CHECK_FALSE(GetCurrentThreadId() == 0); } TEST_CASE("NamedEvent") { std::string Name = "zencore_test_event"; NamedEvent TestEvent(Name); // Timeout test for (uint32_t i = 0; i < 8; ++i) { bool bEventSet = TestEvent.Wait(10); CHECK(!bEventSet); } NamedEvent ReadyEvent(Name + "_ready"); // Thread check std::thread Waiter = std::thread([Name]() { NamedEvent ReadyEvent(Name + "_ready"); ReadyEvent.Set(); NamedEvent TestEvent(Name); TestEvent.Wait(1000); }); ReadyEvent.Wait(); zen::Sleep(100); TestEvent.Set(); Waiter.join(); // Manual reset property for (uint32_t i = 0; i < 8; ++i) { bool bEventSet = TestEvent.Wait(100); CHECK(bEventSet); } } TEST_CASE("NamedMutex") { static const char* Name = "zen_test_mutex"; CHECK(!NamedMutex::Exists(Name)); { NamedMutex TestMutex; CHECK(TestMutex.Create(Name)); CHECK(NamedMutex::Exists(Name)); } CHECK(!NamedMutex::Exists(Name)); } TEST_SUITE_END(); #endif // ZEN_WITH_TESTS } // namespace zen