// Copyright Epic Games, Inc. All Rights Reserved. #include #include #include #include #include #include #if ZEN_PLATFORM_WINDOWS # include # include # include #elif ZEN_PLATFORM_LINUX # include # include # include # include # include # include # include # include # include # include #endif #include 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 static bool IsThereAMessageInQueue(int Fd) { // Check if there is already a message in the queue. mq_attr Attributes = { O_NONBLOCK, 1, 1, 0 }; mq_getattr(Fd, &Attributes); return (Attributes.mq_curmsgs > 0); } #endif // ZEN_PLATFORM_LINUX 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 ExtendableStringBuilder<64> Name; Name << "/"; Name << EventName; mq_attr Attributes = { 0, // flags 1, // max message count 1, // max message size 0, // current messages }; int Inner = mq_open(Name.c_str(), O_RDWR|O_CREAT|O_CLOEXEC, 0644, &Attributes); if (Inner < 0) { ThrowLastError("Failed to get message queue from mq_open()"); } int LockResult = flock(Inner, LOCK_EX|LOCK_NB); if (LockResult == 0) { ZEN_ASSERT( IsThereAMessageInQueue(Inner) == false, "Ownership of a non-empty '{}' message queue occurred", Name.c_str()); } m_EventHandle = (void*)intptr_t(Inner); #else # error Implement NamedEvent for this platform #endif } NamedEvent::~NamedEvent() { Close(); } void NamedEvent::Close() { if (m_EventHandle == nullptr) { return; } #if ZEN_PLATFORM_WINDOWS CloseHandle(m_EventHandle); #elif ZEN_PLATFORM_LINUX int Inner = int(intptr_t(m_EventHandle)); if (flock(Inner, LOCK_EX|LOCK_NB) == 0) { flock(Inner, LOCK_UN|LOCK_NB); std::filesystem::path Name = PathFromHandle((void*)(intptr_t(Inner))); mq_unlink(Name.c_str()); } close(Inner); #endif m_EventHandle = nullptr; } void NamedEvent::Set() { #if ZEN_PLATFORM_WINDOWS SetEvent(m_EventHandle); #elif ZEN_PLATFORM_LINUX int Inner = int(intptr_t(m_EventHandle)); if (IsThereAMessageInQueue(Inner)) { return; } char Message = 0x49; if (mq_send(Inner, &Message, sizeof(Message), 0) != 0) { ThrowLastError("Unable to send set message to queue"); } IsThereAMessageInQueue(Inner); #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 int Inner = int(intptr_t(m_EventHandle)); if (IsThereAMessageInQueue(Inner)) { return true; } struct timeval TimeoutValue = { .tv_sec = 0, .tv_usec = TimeoutMs << 10, }; struct timeval* TimeoutPtr = (TimeoutMs < 0) ? nullptr : &TimeoutValue; fd_set FdSet; FD_ZERO(&FdSet); FD_SET(Inner, &FdSet); return select(Inner + 1, &FdSet, nullptr, nullptr, TimeoutPtr) > 0; #endif } ////////////////////////////////////////////////////////////////////////// NamedMutex::~NamedMutex() { #if ZEN_PLATFORM_WINDOWS if (m_MutexHandle) { CloseHandle(m_MutexHandle); } #else /* ZEN_TODO_MR: NamedMutex */ #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; #else ZEN_UNUSED(MutexName); /* ZEN_TODO_MR: NamedMutex */ 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; #else ZEN_UNUSED(MutexName); /* ZEN_TODO_MR: NamedMutex */ return false; #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); } ////////////////////////////////////////////////////////////////////////// static void BuildArgV(std::vector& Out, char* CommandLine) { char* Cursor = CommandLine; while (true) { // Skip leading whitespace for (; *Cursor == ' '; ++Cursor); // Check for nullp terminator if (*Cursor == '\0') { break; } Out.push_back(Cursor); // Extract word int QuoteCount = 0; do { QuoteCount += (*Cursor == '\"'); if (*Cursor == ' ' && !(QuoteCount & 1)) { break; } ++Cursor; } while (*Cursor != '\0'); if (*Cursor == '\0') { break; } *Cursor = '\0'; ++Cursor; } } #if ZEN_PLATFORM_WINDOWS static CreateProcResult CreateProcNormal( const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) { PROCESS_INFORMATION ProcessInfo{}; STARTUPINFO StartupInfo{.cb = sizeof(STARTUPINFO)}; const bool InheritHandles = false; void* Environment = nullptr; LPSECURITY_ATTRIBUTES ProcessAttributes = nullptr; LPSECURITY_ATTRIBUTES ThreadAttributes = nullptr; DWORD CreationFlags = 0; if (Options.Flags & CreateProcOptions::Flag_NewConsole) { CreationFlags |= CREATE_NEW_CONSOLE; } const wchar_t* WorkingDir = nullptr; if (Options.WorkingDirectory != nullptr) { WorkingDir = Options.WorkingDirectory->c_str(); } ExtendableWideStringBuilder<256> CommandLineZ; CommandLineZ << CommandLine; BOOL Success = CreateProcessW(Executable.c_str(), CommandLineZ.Data(), ProcessAttributes, ThreadAttributes, InheritHandles, CreationFlags, Environment, WorkingDir, &StartupInfo, &ProcessInfo); if (!Success) { return nullptr; } CloseHandle(ProcessInfo.hThread); return ProcessInfo.hProcess; } static CreateProcResult CreateProcUnelevated( const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) { /* Launches a binary with the shell as its parent. The shell (such as Explorer) should be an unelevated process. */ // No sense in using this route if we are not elevated in the first place if (IsUserAnAdmin() == FALSE) { return CreateProcNormal(Executable, CommandLine, Options); } // Get the users' shell process and open it for process creation HWND ShellWnd = GetShellWindow(); if (ShellWnd == nullptr) { return nullptr; } DWORD ShellPid; GetWindowThreadProcessId(ShellWnd, &ShellPid); HANDLE Process = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, ShellPid); if (Process == nullptr) { return nullptr; } auto $0 = MakeGuard([&] { CloseHandle(Process); }); // Creating a process as a child of another process is done by setting a // thread-attribute list on the startup info passed to CreateProcess() SIZE_T AttrListSize; InitializeProcThreadAttributeList(nullptr, 1, 0, &AttrListSize); auto AttrList = (PPROC_THREAD_ATTRIBUTE_LIST)malloc(AttrListSize); auto $1 = MakeGuard([&] { free(AttrList); }); if (!InitializeProcThreadAttributeList(AttrList, 1, 0, &AttrListSize)) { return nullptr; } BOOL bOk = UpdateProcThreadAttribute(AttrList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, (HANDLE*)&Process, sizeof(Process), nullptr, nullptr); if (!bOk) { return nullptr; } // By this point we know we are an elevated process. It is not allowed to // create a process as a child of another unelevated process that share our // elevated console window if we have one. So we'll need to create a new one. uint32_t CreateProcFlags = EXTENDED_STARTUPINFO_PRESENT; if (GetConsoleWindow() != nullptr) { CreateProcFlags |= CREATE_NEW_CONSOLE; } else { CreateProcFlags |= DETACHED_PROCESS; } // Everything is set up now so we can proceed and launch the process STARTUPINFOEXW StartupInfo = { .StartupInfo = { .cb = sizeof(STARTUPINFOEXW) }, .lpAttributeList = AttrList, }; PROCESS_INFORMATION ProcessInfo = {}; if (Options.Flags & CreateProcOptions::Flag_NewConsole) { CreateProcFlags |= CREATE_NEW_CONSOLE; } ExtendableWideStringBuilder<256> CommandLineZ; CommandLineZ << CommandLine; bOk = CreateProcessW(Executable.c_str(), CommandLineZ.Data(), nullptr, nullptr, FALSE, CreateProcFlags, nullptr, nullptr, &StartupInfo.StartupInfo, &ProcessInfo); if (bOk == FALSE) { return nullptr; } CloseHandle(ProcessInfo.hThread); return ProcessInfo.hProcess; } static CreateProcResult CreateProcElevated( const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) { ExtendableWideStringBuilder<256> CommandLineZ; CommandLineZ << CommandLine; SHELLEXECUTEINFO ShellExecuteInfo; ZeroMemory(&ShellExecuteInfo, sizeof(ShellExecuteInfo)); ShellExecuteInfo.cbSize = sizeof(ShellExecuteInfo); ShellExecuteInfo.fMask = SEE_MASK_UNICODE | SEE_MASK_NOCLOSEPROCESS; ShellExecuteInfo.lpFile = Executable.c_str(); ShellExecuteInfo.lpVerb = TEXT("runas"); ShellExecuteInfo.nShow = SW_SHOW; ShellExecuteInfo.lpParameters = CommandLineZ.c_str(); if (Options.WorkingDirectory != nullptr) { ShellExecuteInfo.lpDirectory = Options.WorkingDirectory->c_str(); } if (::ShellExecuteEx(&ShellExecuteInfo)) { return ShellExecuteInfo.hProcess; } return nullptr; } #endif // ZEN_PLATFORM_WINDOWS CreateProcResult CreateProc( const std::filesystem::path& Executable, std::string_view CommandLine, const CreateProcOptions& Options) { #if ZEN_PLATFORM_WINDOWS if (Options.Flags & CreateProcOptions::Flag_Unelevated) { return CreateProcUnelevated(Executable, CommandLine, Options); } if (Options.Flags & CreateProcOptions::Flag_Elevated) { return CreateProcElevated(Executable, CommandLine, Options); } return CreateProcNormal(Executable, CommandLine, Options); #else std::vector ArgV; std::string CommandLineZ(CommandLine); BuildArgV(ArgV, CommandLineZ.data()); ArgV.push_back(nullptr); int ChildPid = fork(); if (ChildPid < 0) { ThrowLastError("Failed to fork a new child process"); } else if (ChildPid == 0) { if (Options.WorkingDirectory != nullptr) { chdir(Options.WorkingDirectory->c_str()); } if (execv(Executable.c_str(), ArgV.data()) < 0) { ThrowLastError("Failed to exec() a new process image"); } } return ChildPid; #endif } ////////////////////////////////////////////////////////////////////////// ProcessMonitor::ProcessMonitor() { } ProcessMonitor::~ProcessMonitor() { RwLock::ExclusiveLockScope _(m_Lock); for (HandleType& Proc : m_ProcessHandles) { #if ZEN_PLATFORM_WINDOWS CloseHandle(Proc); #endif Proc = 0; } } bool ProcessMonitor::IsRunning() { RwLock::ExclusiveLockScope _(m_Lock); bool FoundOne = false; for (HandleType& Proc : m_ProcessHandles) { bool ProcIsActive; #if ZEN_PLATFORM_WINDOWS DWORD ExitCode = 0; GetExitCodeProcess(Proc, &ExitCode); ProcIsActive = (ExitCode != STILL_ACTIVE); if (!ProcIsActive) { CloseHandle(Proc); } #else int Pid = int(intptr_t(Proc)); ProcIsActive = IsProcessRunning(Pid); #endif if (!ProcIsActive) { Proc = 0; } // Still alive FoundOne |= ProcIsActive; } std::erase_if(m_ProcessHandles, [](HandleType Handle) { return Handle == 0; }); return FoundOne; } void ProcessMonitor::AddPid(int Pid) { HandleType ProcessHandle; #if ZEN_PLATFORM_WINDOWS ProcessHandle = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, Pid); #else ProcessHandle = HandleType(intptr_t(Pid)); #endif if (ProcessHandle) { RwLock::ExclusiveLockScope _(m_Lock); m_ProcessHandles.push_back(ProcessHandle); } } bool ProcessMonitor::IsActive() const { RwLock::SharedLockScope _(m_Lock); return m_ProcessHandles.empty() == false; } ////////////////////////////////////////////////////////////////////////// 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)); } TEST_CASE("BuildArgV") { const char* Words[] = { "one", "two", "three", "four", "five" }; struct { int WordCount; const char* Input; } Cases[] = { { 0, "" }, { 0, " " }, { 1, "one" }, { 1, " one" }, { 1, "one " }, { 2, "one two" }, { 2, " one two" }, { 2, "one two " }, { 2, " one two" }, { 2, "one two " }, { 2, "one two " }, { 3, "one two three" }, { 3, "\"one\" two \"three\"" }, { 5, "one two three four five" }, }; for (const auto& Case : Cases) { std::vector OutArgs; StringBuilder<64> Mutable; Mutable << Case.Input; BuildArgV(OutArgs, Mutable.Data()); CHECK_EQ(OutArgs.size(), Case.WordCount); for (int i = 0, n = int(OutArgs.size()); i < n; ++i) { const char* Truth = Words[i]; size_t TruthLen = strlen(Truth); const char* Candidate = OutArgs[i]; bool bQuoted = (Candidate[0] == '\"'); Candidate += bQuoted; CHECK(strncmp(Truth, Candidate, TruthLen) == 0); if (bQuoted) { CHECK_EQ(Candidate[TruthLen], '\"'); } } } } TEST_CASE("ipc") { using namespace fmt::literals; std::string Name = "zencore_test_event_{}"_format(GetCurrentProcessId()); NamedEvent TestEvent(Name); // Timeout test for (uint32_t i = 0; i < 8; ++i) { bool bEventSet = TestEvent.Wait(100); CHECK(!bEventSet); } // Thread check std::thread Waiter = std::thread([Name] () { NamedEvent ReadyEvent(Name + "_ready"); ReadyEvent.Set(); NamedEvent TestEvent(Name); TestEvent.Wait(1000); }); NamedEvent ReadyEvent(Name + "_ready"); ReadyEvent.Wait(); zen::Sleep(500); TestEvent.Set(); Waiter.join(); // Manual reset property for (uint32_t i = 0; i < 8; ++i) { bool bEventSet = TestEvent.Wait(100); CHECK(bEventSet); } } #endif // ZEN_WITH_TESTS } // namespace zen