aboutsummaryrefslogtreecommitdiff
path: root/src/zencore/process.cpp
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2026-01-19 13:14:42 +0100
committerGitHub Enterprise <[email protected]>2026-01-19 13:14:42 +0100
commit5081a33dcd19c5d6f9d9cdae9f8745ba786374d4 (patch)
tree6733e6f69137afc690a8c6dcdf618e69ce8029c5 /src/zencore/process.cpp
parentMerge pull request #701 from ue-foundation/lm/mac-daemon-mode (diff)
downloadzen-5081a33dcd19c5d6f9d9cdae9f8745ba786374d4.tar.xz
zen-5081a33dcd19c5d6f9d9cdae9f8745ba786374d4.zip
consul package and basic client added (#716)
* this adds a consul package which can be used to fetch a consul binary * it also adds a `ConsulProcess` helper which can be used to spawn and manage a consul service instance * zencore dependencies brought across: - `except_fmt.h` for easer generation of formatted exception messages - `process.h/cpp` changes (adds `Kill` operation and process group support on Windows) - `string.h` changes to allow generic use of `WideToUtf8()`
Diffstat (limited to 'src/zencore/process.cpp')
-rw-r--r--src/zencore/process.cpp142
1 files changed, 140 insertions, 2 deletions
diff --git a/src/zencore/process.cpp b/src/zencore/process.cpp
index 0b25d14f4..56849a10d 100644
--- a/src/zencore/process.cpp
+++ b/src/zencore/process.cpp
@@ -8,14 +8,19 @@
#include <zencore/scopeguard.h>
#include <zencore/string.h>
#include <zencore/testing.h>
+#include <zencore/timer.h>
#include <thread>
+ZEN_THIRD_PARTY_INCLUDES_START
+
#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+
+# include <Psapi.h>
# include <shellapi.h>
# include <Shlobj.h>
# include <TlHelp32.h>
-# include <zencore/windows.h>
#else
# include <fcntl.h>
# include <pthread.h>
@@ -35,8 +40,8 @@
# include <sys/sysctl.h>
#endif
-ZEN_THIRD_PARTY_INCLUDES_START
#include <fmt/format.h>
+
ZEN_THIRD_PARTY_INCLUDES_END
namespace zen {
@@ -215,6 +220,51 @@ ProcessHandle::IsValid() const
}
bool
+ProcessHandle::Kill()
+{
+ if (!IsRunning())
+ {
+ return true;
+ }
+
+#if ZEN_PLATFORM_WINDOWS
+ SetConsoleCtrlHandler(nullptr, TRUE); // Prevent this process from terminating itself
+ auto _ = MakeGuard([] { SetConsoleCtrlHandler(nullptr, FALSE); });
+
+ // Try graceful shutdown first
+ if (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, m_Pid))
+ {
+ // Wait briefly for graceful shutdown
+ if (WaitForSingleObject(m_ProcessHandle, 5000) == WAIT_OBJECT_0)
+ {
+ Reset();
+ return true;
+ }
+ }
+
+ // Fall back to forceful termination if graceful shutdown failed
+ if (!TerminateProcess(m_ProcessHandle, 0))
+ {
+ return false;
+ }
+#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ int Res = kill(pid_t(m_Pid), SIGTERM);
+ if (Res != 0)
+ {
+ int err = errno;
+ if (err != ESRCH)
+ {
+ return false;
+ }
+ }
+#endif
+
+ Reset();
+
+ return true;
+}
+
+bool
ProcessHandle::Terminate(int ExitCode)
{
if (!IsRunning())
@@ -449,6 +499,10 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma
{
CreationFlags |= CREATE_NO_WINDOW;
}
+ if (Options.Flags & CreateProcOptions::Flag_Windows_NewProcessGroup)
+ {
+ CreationFlags |= CREATE_NEW_PROCESS_GROUP;
+ }
const wchar_t* WorkingDir = nullptr;
if (Options.WorkingDirectory != nullptr)
@@ -1082,6 +1136,90 @@ FindProcess(const std::filesystem::path& ExecutableImage, ProcessHandle& OutHand
#endif // ZEN_PLATFORM_LINUX
}
+void
+WaitForThreads(uint64_t WaitTimeMs)
+{
+#if ZEN_PLATFORM_WINDOWS
+ auto ThreadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (ThreadSnapshot == INVALID_HANDLE_VALUE)
+ {
+ return;
+ }
+ auto _ = MakeGuard([ThreadSnapshot] { CloseHandle(ThreadSnapshot); });
+
+ const DWORD CurrentProcessId = ::GetCurrentProcessId();
+ const DWORD CurrentThreadId = ::GetCurrentThreadId();
+
+ Stopwatch Timer;
+
+ do
+ {
+ THREADENTRY32 ThreadEntry;
+ ThreadEntry.dwSize = sizeof(THREADENTRY32);
+
+ uint32_t ThreadCount = 0;
+ if (Thread32First(ThreadSnapshot, &ThreadEntry))
+ {
+ do
+ {
+ if (ThreadEntry.th32OwnerProcessID == CurrentProcessId && ThreadEntry.th32ThreadID != CurrentThreadId)
+ {
+ ThreadCount++;
+ }
+ } while (Thread32Next(ThreadSnapshot, &ThreadEntry));
+ }
+
+ if (ThreadCount <= 1)
+ {
+ break;
+ }
+
+ const uint64_t SleepMs = 10;
+ Sleep(SleepMs);
+ } while (Timer.GetElapsedTimeMs() < WaitTimeMs);
+#else
+ ZEN_UNUSED(WaitTimeMs);
+#endif
+}
+
+void
+GetProcessMetrics(ProcessHandle& Handle, ProcessMetrics& OutMetrics)
+{
+#if ZEN_PLATFORM_WINDOWS
+ FILETIME CreationTime;
+ FILETIME ExitTime;
+ FILETIME KernelTime;
+ FILETIME UserTime;
+
+ if (GetProcessTimes(Handle.Handle(), &CreationTime, &ExitTime, &KernelTime, &UserTime))
+ {
+ ULARGE_INTEGER KTime;
+ KTime.LowPart = KernelTime.dwLowDateTime;
+ KTime.HighPart = KernelTime.dwHighDateTime;
+
+ ULARGE_INTEGER UTime;
+ UTime.LowPart = UserTime.dwLowDateTime;
+ UTime.HighPart = UserTime.dwHighDateTime;
+
+ OutMetrics.KernelTimeMs = KTime.QuadPart / 10000;
+ OutMetrics.UserTimeMs = UTime.QuadPart / 10000;
+ }
+
+ PROCESS_MEMORY_COUNTERS MemCounters;
+ if (GetProcessMemoryInfo(Handle.Handle(), &MemCounters, sizeof(MemCounters)))
+ {
+ OutMetrics.WorkingSetSize = MemCounters.WorkingSetSize;
+ OutMetrics.PeakWorkingSetSize = MemCounters.PeakWorkingSetSize;
+ OutMetrics.PagefileUsage = MemCounters.PagefileUsage;
+ OutMetrics.PeakPagefileUsage = MemCounters.PeakPagefileUsage;
+ }
+#else
+ // TODO: implement for Linux and Mac
+ ZEN_UNUSED(Handle);
+ ZEN_UNUSED(OutMetrics);
+#endif
+}
+
#if ZEN_WITH_TESTS
void