From d02b0720813a62a4f1fe875e6e784843f5c2da46 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 15 Nov 2023 11:30:56 +0100 Subject: fix race contdition when signaling shutdown of process and waiting for completion (#539) --- src/zencore/thread.cpp | 56 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 8 deletions(-) (limited to 'src/zencore/thread.cpp') diff --git a/src/zencore/thread.cpp b/src/zencore/thread.cpp index 1f1b1b8f5..99d7cdc61 100644 --- a/src/zencore/thread.cpp +++ b/src/zencore/thread.cpp @@ -606,7 +606,7 @@ ProcessHandle::Terminate(int ExitCode) bSuccess = (WaitResult != WAIT_OBJECT_0); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC ZEN_UNUSED(ExitCode); - bSuccess = (kill(m_Pid, SIGKILL) == 0); + bSuccess = (kill(m_Pid, SIGKILL) == 0); #endif if (!bSuccess) @@ -650,25 +650,65 @@ ProcessHandle::Wait(int TimeoutMs) break; } #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC + if (TimeoutMs < 0) + { + int WaitState = 0; + int Res = waitpid(m_Pid, &WaitState, WCONTINUED | WUNTRACED); + if (Res == -1) + { + int32_t LastError = zen::GetLastError(); + if (LastError == ECHILD || LastError == ESRCH) + { + return true; + } + ThrowSystemError(static_cast(LastError), "Process::Wait waitpid failed"sv); + } + if (WIFEXITED(WaitState)) + { + return true; + } + if (WIFSIGNALED(WaitState)) + { + return true; + } + return false; + } + const int SleepMs = 20; timespec SleepTime = {0, SleepMs * 1000 * 1000}; - for (int i = 0;; i += SleepMs) + for (int SleepedTimeMS = 0;; SleepedTimeMS += SleepMs) { -# if ZEN_PLATFORM_MAC int WaitState = 0; - waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED); -# endif + int Res = waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED); + if (Res == -1) + { + int32_t LastError = zen::GetLastError(); + if (LastError == ECHILD || LastError == ESRCH) + { + return true; + } + ThrowSystemError(static_cast(LastError), "Process::Wait waitpid failed"sv); + } + if (WIFEXITED(WaitState)) + { + return true; + } + if (WIFSIGNALED(WaitState)) + { + return true; + } if (kill(m_Pid, 0) < 0) { - if (zen::GetLastError() == ESRCH) + int32_t LastError = zen::GetLastError(); + if (LastError == ECHILD || LastError == ESRCH) { return true; } - break; + ThrowSystemError(static_cast(LastError), "Process::Wait kill failed"sv); } - if (TimeoutMs >= 0 && i >= TimeoutMs) + if (SleepedTimeMS >= TimeoutMs) { return false; } -- cgit v1.2.3 From 79347f5e118684f51e2b0c2a82f51e8667026062 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Wed, 15 Nov 2023 14:41:55 +0100 Subject: don't do blocking call to waitpid (#540) fix process wait timeout always use kill(pid, 0) to determine if process is running --- src/zencore/thread.cpp | 38 +++----------------------------------- 1 file changed, 3 insertions(+), 35 deletions(-) (limited to 'src/zencore/thread.cpp') diff --git a/src/zencore/thread.cpp b/src/zencore/thread.cpp index 99d7cdc61..758e88350 100644 --- a/src/zencore/thread.cpp +++ b/src/zencore/thread.cpp @@ -606,7 +606,7 @@ ProcessHandle::Terminate(int ExitCode) bSuccess = (WaitResult != WAIT_OBJECT_0); #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC ZEN_UNUSED(ExitCode); - bSuccess = (kill(m_Pid, SIGKILL) == 0); + bSuccess = (kill(m_Pid, SIGKILL) == 0); #endif if (!bSuccess) @@ -650,30 +650,6 @@ ProcessHandle::Wait(int TimeoutMs) break; } #elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC - if (TimeoutMs < 0) - { - int WaitState = 0; - int Res = waitpid(m_Pid, &WaitState, WCONTINUED | WUNTRACED); - if (Res == -1) - { - int32_t LastError = zen::GetLastError(); - if (LastError == ECHILD || LastError == ESRCH) - { - return true; - } - ThrowSystemError(static_cast(LastError), "Process::Wait waitpid failed"sv); - } - if (WIFEXITED(WaitState)) - { - return true; - } - if (WIFSIGNALED(WaitState)) - { - return true; - } - return false; - } - const int SleepMs = 20; timespec SleepTime = {0, SleepMs * 1000 * 1000}; for (int SleepedTimeMS = 0;; SleepedTimeMS += SleepMs) @@ -689,26 +665,18 @@ ProcessHandle::Wait(int TimeoutMs) } ThrowSystemError(static_cast(LastError), "Process::Wait waitpid failed"sv); } - if (WIFEXITED(WaitState)) - { - return true; - } - if (WIFSIGNALED(WaitState)) - { - return true; - } if (kill(m_Pid, 0) < 0) { int32_t LastError = zen::GetLastError(); - if (LastError == ECHILD || LastError == ESRCH) + if (LastError == ESRCH) { return true; } ThrowSystemError(static_cast(LastError), "Process::Wait kill failed"sv); } - if (SleepedTimeMS >= TimeoutMs) + if (TimeoutMs >= 0 && SleepedTimeMS >= TimeoutMs) { return false; } -- cgit v1.2.3 From 3d16e8a64e9ef76f407270ef092671a1be7bf346 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Thu, 16 Nov 2023 15:34:25 +0100 Subject: changed posix event implementation to use std::atomic instead of volatile (#547) --- src/zencore/thread.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'src/zencore/thread.cpp') diff --git a/src/zencore/thread.cpp b/src/zencore/thread.cpp index 758e88350..27a5ec1ae 100644 --- a/src/zencore/thread.cpp +++ b/src/zencore/thread.cpp @@ -152,12 +152,12 @@ RwLock::ReleaseExclusive() noexcept ////////////////////////////////////////////////////////////////////////// -#if !ZEN_PLATFORM_WINDOWS +#if !ZEN_USE_WINDOWS_EVENTS struct EventInner { std::mutex Mutex; std::condition_variable CondVar; - bool volatile bSet = false; + std::atomic_bool bSet{false}; }; #endif // !ZEN_PLATFORM_WINDOWS @@ -166,7 +166,7 @@ Event::Event() bool bManualReset = true; bool bInitialState = false; -#if ZEN_PLATFORM_WINDOWS +#if ZEN_USE_WINDOWS_EVENTS m_EventHandle = CreateEvent(nullptr, bManualReset, bInitialState, nullptr); #else ZEN_UNUSED(bManualReset); @@ -184,7 +184,7 @@ Event::~Event() void Event::Set() { -#if ZEN_PLATFORM_WINDOWS +#if ZEN_USE_WINDOWS_EVENTS SetEvent(m_EventHandle); #else auto* Inner = (EventInner*)m_EventHandle; @@ -199,7 +199,7 @@ Event::Set() void Event::Reset() { -#if ZEN_PLATFORM_WINDOWS +#if ZEN_USE_WINDOWS_EVENTS ResetEvent(m_EventHandle); #else auto* Inner = (EventInner*)m_EventHandle; @@ -213,7 +213,7 @@ Event::Reset() void Event::Close() { -#if ZEN_PLATFORM_WINDOWS +#if ZEN_USE_WINDOWS_EVENTS CloseHandle(m_EventHandle); #else auto* Inner = (EventInner*)m_EventHandle; @@ -225,7 +225,7 @@ Event::Close() bool Event::Wait(int TimeoutMs) { -#if ZEN_PLATFORM_WINDOWS +#if ZEN_USE_WINDOWS_EVENTS using namespace std::literals; const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs; @@ -250,14 +250,14 @@ Event::Wait(int TimeoutMs) return true; } - return Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(TimeoutMs), [&] { return Inner->bSet; }); + return Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(TimeoutMs), [&] { return Inner->bSet.load(); }); } std::unique_lock Lock(Inner->Mutex); if (!Inner->bSet) { - Inner->CondVar.wait(Lock, [&] { return Inner->bSet; }); + Inner->CondVar.wait(Lock, [&] { return Inner->bSet.load(); }); } return true; -- cgit v1.2.3 From 85097e245029ec973182e07ef79b966e748d0efa Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Thu, 16 Nov 2023 17:24:36 +0100 Subject: further posix event improvements (#549) * changed posix event implementation to use std::atomic instead of volatile * ensure Event::Close() can take lock before deleting the inner object * don't try to take the Event lock if the event is already signaled * changed logic around Event::Wait without time-out. this works around some apparent issues on MacOS/Linux * fix logic for posix process exit wait --- src/zencore/thread.cpp | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) (limited to 'src/zencore/thread.cpp') diff --git a/src/zencore/thread.cpp b/src/zencore/thread.cpp index 27a5ec1ae..1174f902f 100644 --- a/src/zencore/thread.cpp +++ b/src/zencore/thread.cpp @@ -190,7 +190,7 @@ Event::Set() auto* Inner = (EventInner*)m_EventHandle; { std::unique_lock Lock(Inner->Mutex); - Inner->bSet = true; + Inner->bSet.store(true); } Inner->CondVar.notify_all(); #endif @@ -205,7 +205,7 @@ Event::Reset() auto* Inner = (EventInner*)m_EventHandle; { std::unique_lock Lock(Inner->Mutex); - Inner->bSet = false; + Inner->bSet.store(false); } #endif } @@ -217,6 +217,10 @@ Event::Close() 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; @@ -239,13 +243,18 @@ Event::Wait(int TimeoutMs) return (Result == WAIT_OBJECT_0); #else - auto* Inner = (EventInner*)m_EventHandle; + auto* Inner = reinterpret_cast(m_EventHandle); + + if (Inner->bSet.load()) + { + return true; + } if (TimeoutMs >= 0) { std::unique_lock Lock(Inner->Mutex); - if (Inner->bSet) + if (Inner->bSet.load()) { return true; } @@ -253,11 +262,15 @@ Event::Wait(int TimeoutMs) 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) + if (!Inner->bSet.load()) { - Inner->CondVar.wait(Lock, [&] { return Inner->bSet.load(); }); + while (!Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(1000), [&] { return Inner->bSet.load(); })) + ; } return true; @@ -656,6 +669,9 @@ ProcessHandle::Wait(int TimeoutMs) { int WaitState = 0; int Res = waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED); +# if 1 + ZEN_UNUSED(Res); +# else if (Res == -1) { int32_t LastError = zen::GetLastError(); @@ -665,6 +681,7 @@ ProcessHandle::Wait(int TimeoutMs) } ThrowSystemError(static_cast(LastError), "Process::Wait waitpid failed"sv); } +# endif if (kill(m_Pid, 0) < 0) { -- cgit v1.2.3 From 573907447db3e19d49c0bcaf3f659cf2d599c738 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Thu, 16 Nov 2023 18:50:27 +0100 Subject: blocking queue fix (#550) * make BlockingQueue::m_CompleteAdding non-atomic * ZenCacheDiskLayer::Flush logging * name worker threads in ZenCacheDiskLayer::DiscoverBuckets * name worker threads in gcv2 * improved logging in ZenServerInstance * scrub threadpool naming * remove waitpid handling, we should just call wait to kill zombie processes --- src/zencore/thread.cpp | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) (limited to 'src/zencore/thread.cpp') diff --git a/src/zencore/thread.cpp b/src/zencore/thread.cpp index 1174f902f..a55bc5d69 100644 --- a/src/zencore/thread.cpp +++ b/src/zencore/thread.cpp @@ -668,20 +668,7 @@ ProcessHandle::Wait(int TimeoutMs) for (int SleepedTimeMS = 0;; SleepedTimeMS += SleepMs) { int WaitState = 0; - int Res = waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED); -# if 1 - ZEN_UNUSED(Res); -# else - if (Res == -1) - { - int32_t LastError = zen::GetLastError(); - if (LastError == ECHILD || LastError == ESRCH) - { - return true; - } - ThrowSystemError(static_cast(LastError), "Process::Wait waitpid failed"sv); - } -# endif + waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED); if (kill(m_Pid, 0) < 0) { -- cgit v1.2.3 From b0082066596178f7b72d9963bffdec306a5b6250 Mon Sep 17 00:00:00 2001 From: Dan Engelbrecht Date: Fri, 17 Nov 2023 14:38:13 +0100 Subject: fix named event (#553) * fix named event timout and test, fix blocking queue --- src/zencore/thread.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'src/zencore/thread.cpp') diff --git a/src/zencore/thread.cpp b/src/zencore/thread.cpp index a55bc5d69..6092895b0 100644 --- a/src/zencore/thread.cpp +++ b/src/zencore/thread.cpp @@ -411,9 +411,10 @@ NamedEvent::Wait(int TimeoutMs) } # if defined(_GNU_SOURCE) + const int TimeoutSec = TimeoutMs / 1000; struct timespec TimeoutValue = { - .tv_sec = TimeoutMs >> 10, - .tv_nsec = (TimeoutMs & 0x3ff) << 20, + .tv_sec = TimeoutSec, + .tv_nsec = (TimeoutMs - (TimeoutSec * 1000)) * 1000000, }; Result = semtimedop(Sem, &SemOp, 1, &TimeoutValue); # else @@ -431,7 +432,6 @@ NamedEvent::Wait(int TimeoutMs) TimeoutMs -= SleepTimeMs; } while (TimeoutMs > 0); # endif // _GNU_SOURCE - return Result == 0; #endif } @@ -1225,19 +1225,20 @@ TEST_CASE("NamedEvent") 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(100); + TestEvent.Wait(1000); }); - NamedEvent ReadyEvent(Name + "_ready"); ReadyEvent.Wait(); - zen::Sleep(50); + zen::Sleep(100); TestEvent.Set(); Waiter.join(); -- cgit v1.2.3 From b8285f70b7bde814ab7eb0a00ded1018cc5c4395 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 20 Nov 2023 11:09:53 +0100 Subject: moved process handling code into separate h/cpp (#555) --- src/zencore/thread.cpp | 680 +------------------------------------------------ 1 file changed, 13 insertions(+), 667 deletions(-) (limited to 'src/zencore/thread.cpp') diff --git a/src/zencore/thread.cpp b/src/zencore/thread.cpp index 6092895b0..149a0d781 100644 --- a/src/zencore/thread.cpp +++ b/src/zencore/thread.cpp @@ -8,6 +8,9 @@ #include #include #include +#include + +#include #if ZEN_PLATFORM_LINUX # if !defined(_GNU_SOURCE) @@ -15,15 +18,15 @@ # endif #endif -#if ZEN_PLATFORM_WINDOWS -# include -# include -# include -#else +#if !ZEN_USE_WINDOWS_EVENTS # include # include # include +#endif +#if ZEN_PLATFORM_WINDOWS +# include +#else # include # include # include @@ -36,10 +39,6 @@ # include #endif -#include - -#include - ZEN_THIRD_PARTY_INCLUDES_START #include ZEN_THIRD_PARTY_INCLUDES_END @@ -78,22 +77,6 @@ SetNameInternal(DWORD thread_id, const char* name) } #endif -#if ZEN_PLATFORM_LINUX -const bool bNoZombieChildren = []() { - // When a child process exits it is put into a zombie state until the parent - // collects its result. This doesn't fit the Windows-like model that Zen uses - // where there is a less strict familial model and no zombification. Ignoring - // SIGCHLD siganals removes the need to call wait() on zombies. Another option - // would be for the child to call setsid() but that would detatch the child - // from the terminal. - struct sigaction Action = {}; - sigemptyset(&Action.sa_mask); - Action.sa_handler = SIG_IGN; - sigaction(SIGCHLD, &Action, nullptr); - return true; -}(); -#endif - void SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName) { @@ -533,581 +516,6 @@ NamedMutex::Exists(std::string_view MutexName) #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 || ZEN_PLATFORM_MAC - if (Pid > 0) - { - m_ProcessHandle = (void*)(intptr_t(Pid)); - } -#endif - - if (!m_ProcessHandle) - { - ThrowLastError(fmt::format("ProcessHandle::Initialize(pid: {}) failed", 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 || ZEN_PLATFORM_MAC - bActive = (kill(pid_t(m_Pid), 0) == 0); -#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_PLATFORM_MAC - ZEN_UNUSED(ExitCode); - bSuccess = (kill(m_Pid, SIGKILL) == 0); -#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 || ZEN_PLATFORM_MAC - const int SleepMs = 20; - timespec SleepTime = {0, SleepMs * 1000 * 1000}; - for (int SleepedTimeMS = 0;; SleepedTimeMS += SleepMs) - { - int WaitState = 0; - waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED); - - if (kill(m_Pid, 0) < 0) - { - int32_t LastError = zen::GetLastError(); - if (LastError == ESRCH) - { - return true; - } - ThrowSystemError(static_cast(LastError), "Process::Wait kill failed"sv); - } - - if (TimeoutMs >= 0 && SleepedTimeMS >= TimeoutMs) - { - return false; - } - - nanosleep(&SleepTime, nullptr); - } -#endif - - // What might go wrong here, and what is meaningful to act on? - ThrowLastError("Process::Wait failed"sv); -} - -int -ProcessHandle::WaitExitCode() -{ - Wait(-1); - -#if ZEN_PLATFORM_WINDOWS - DWORD ExitCode = 0; - GetExitCodeProcess(m_ProcessHandle, &ExitCode); - - ZEN_ASSERT(ExitCode != STILL_ACTIVE); - - return ExitCode; -#else - ZEN_NOT_IMPLEMENTED(); - - return 0; -#endif -} - -////////////////////////////////////////////////////////////////////////// - -#if !ZEN_PLATFORM_WINDOWS || ZEN_WITH_TESTS -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; - } -} -#endif // !WINDOWS || TESTS - -#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) - { - int Result = chdir(Options.WorkingDirectory->c_str()); - ZEN_UNUSED(Result); - } - - 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; - } - - ThrowSystemError(Error, fmt::format("failed to open process with pid {}", pid)); - } - - bool bStillActive = true; - DWORD ExitCode = 0; - if (0 != GetExitCodeProcess(hProc, &ExitCode)) - { - bStillActive = ExitCode == STILL_ACTIVE; - } - else - { - ZEN_WARN("Unable to get exit code from handle for process '{}', treating the process as active", pid); - } - - CloseHandle(hProc); - - return bStillActive; -#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC - return (kill(pid_t(pid), 0) == 0); -#endif -} - -int -GetCurrentProcessId() -{ -#if ZEN_PLATFORM_WINDOWS - return ::GetCurrentProcessId(); -#else - return int(getpid()); -#endif -} - int GetCurrentThreadId() { @@ -1120,16 +528,6 @@ GetCurrentThreadId() #endif } -int -GetProcessId(CreateProcResult ProcId) -{ -#if ZEN_PLATFORM_WINDOWS - return static_cast(::GetProcessId(ProcId)); -#else - return ProcId; -#endif -} - void Sleep(int ms) { @@ -1152,65 +550,11 @@ thread_forcelink() { } -TEST_CASE("Thread") -{ - int Pid = GetCurrentProcessId(); - CHECK(Pid > 0); - CHECK(IsProcessRunning(Pid)); - - CHECK_FALSE(GetCurrentThreadId() == 0); -} +TEST_SUITE_BEGIN("core.thread"); -TEST_CASE("BuildArgV") +TEST_CASE("GetCurrentThreadId") { - 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], '\"'); - } - } - } + CHECK_FALSE(GetCurrentThreadId() == 0); } TEST_CASE("NamedEvent") @@ -1266,6 +610,8 @@ TEST_CASE("NamedMutex") CHECK(!NamedMutex::Exists(Name)); } +TEST_SUITE_END(); + #endif // ZEN_WITH_TESTS } // namespace zen -- cgit v1.2.3