aboutsummaryrefslogtreecommitdiff
path: root/src/zencore
diff options
context:
space:
mode:
Diffstat (limited to 'src/zencore')
-rw-r--r--src/zencore/include/zencore/process.h33
-rw-r--r--src/zencore/process.cpp89
2 files changed, 122 insertions, 0 deletions
diff --git a/src/zencore/include/zencore/process.h b/src/zencore/include/zencore/process.h
index c51163a68..809312c7b 100644
--- a/src/zencore/include/zencore/process.h
+++ b/src/zencore/include/zencore/process.h
@@ -9,6 +9,10 @@
namespace zen {
+#if ZEN_PLATFORM_WINDOWS
+class JobObject;
+#endif
+
/** Basic process abstraction
*/
class ProcessHandle
@@ -46,6 +50,7 @@ private:
/** Basic process creation
*/
+
struct CreateProcOptions
{
enum
@@ -63,6 +68,9 @@ struct CreateProcOptions
const std::filesystem::path* WorkingDirectory = nullptr;
uint32_t Flags = 0;
std::filesystem::path StdoutFile;
+#if ZEN_PLATFORM_WINDOWS
+ JobObject* AssignToJob = nullptr; // When set, the process is created suspended, assigned to the job, then resumed
+#endif
};
#if ZEN_PLATFORM_WINDOWS
@@ -99,6 +107,31 @@ private:
std::vector<HandleType> m_ProcessHandles;
};
+#if ZEN_PLATFORM_WINDOWS
+/** Windows Job Object wrapper
+ *
+ * When configured with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, the OS will
+ * terminate all assigned child processes when the job handle is closed
+ * (including abnormal termination of the owning process). This provides
+ * an OS-level guarantee against orphaned child processes.
+ */
+class JobObject
+{
+public:
+ JobObject();
+ ~JobObject();
+ JobObject(const JobObject&) = delete;
+ JobObject& operator=(const JobObject&) = delete;
+
+ void Initialize();
+ bool AssignProcess(void* ProcessHandle);
+ [[nodiscard]] bool IsValid() const;
+
+private:
+ void* m_JobHandle = nullptr;
+};
+#endif // ZEN_PLATFORM_WINDOWS
+
bool IsProcessRunning(int pid);
bool IsProcessRunning(int pid, std::error_code& OutEc);
int GetCurrentProcessId();
diff --git a/src/zencore/process.cpp b/src/zencore/process.cpp
index 4a2668912..226a94050 100644
--- a/src/zencore/process.cpp
+++ b/src/zencore/process.cpp
@@ -490,6 +490,8 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma
LPSECURITY_ATTRIBUTES ProcessAttributes = nullptr;
LPSECURITY_ATTRIBUTES ThreadAttributes = nullptr;
+ const bool AssignToJob = Options.AssignToJob && Options.AssignToJob->IsValid();
+
DWORD CreationFlags = 0;
if (Options.Flags & CreateProcOptions::Flag_NewConsole)
{
@@ -503,6 +505,10 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma
{
CreationFlags |= CREATE_NEW_PROCESS_GROUP;
}
+ if (AssignToJob)
+ {
+ CreationFlags |= CREATE_SUSPENDED;
+ }
const wchar_t* WorkingDir = nullptr;
if (Options.WorkingDirectory != nullptr)
@@ -571,6 +577,15 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma
return nullptr;
}
+ if (AssignToJob)
+ {
+ if (!Options.AssignToJob->AssignProcess(ProcessInfo.hProcess))
+ {
+ ZEN_WARN("Failed to assign newly created process to job object");
+ }
+ ResumeThread(ProcessInfo.hThread);
+ }
+
CloseHandle(ProcessInfo.hThread);
return ProcessInfo.hProcess;
}
@@ -644,6 +659,8 @@ CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view C
};
PROCESS_INFORMATION ProcessInfo = {};
+ const bool AssignToJob = Options.AssignToJob && Options.AssignToJob->IsValid();
+
if (Options.Flags & CreateProcOptions::Flag_NewConsole)
{
CreateProcFlags |= CREATE_NEW_CONSOLE;
@@ -652,6 +669,10 @@ CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view C
{
CreateProcFlags |= CREATE_NO_WINDOW;
}
+ if (AssignToJob)
+ {
+ CreateProcFlags |= CREATE_SUSPENDED;
+ }
ExtendableWideStringBuilder<256> CommandLineZ;
CommandLineZ << CommandLine;
@@ -679,6 +700,15 @@ CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view C
return nullptr;
}
+ if (AssignToJob)
+ {
+ if (!Options.AssignToJob->AssignProcess(ProcessInfo.hProcess))
+ {
+ ZEN_WARN("Failed to assign newly created process to job object");
+ }
+ ResumeThread(ProcessInfo.hThread);
+ }
+
CloseHandle(ProcessInfo.hThread);
return ProcessInfo.hProcess;
}
@@ -845,6 +875,65 @@ ProcessMonitor::IsActive() const
//////////////////////////////////////////////////////////////////////////
+#if ZEN_PLATFORM_WINDOWS
+JobObject::JobObject() = default;
+
+JobObject::~JobObject()
+{
+ if (m_JobHandle)
+ {
+ CloseHandle(m_JobHandle);
+ m_JobHandle = nullptr;
+ }
+}
+
+void
+JobObject::Initialize()
+{
+ ZEN_ASSERT(m_JobHandle == nullptr, "JobObject already initialized");
+
+ m_JobHandle = CreateJobObjectW(nullptr, nullptr);
+ if (!m_JobHandle)
+ {
+ ZEN_WARN("Failed to create job object: {}", zen::GetLastError());
+ return;
+ }
+
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION LimitInfo = {};
+ LimitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+
+ if (!SetInformationJobObject(m_JobHandle, JobObjectExtendedLimitInformation, &LimitInfo, sizeof(LimitInfo)))
+ {
+ ZEN_WARN("Failed to set job object limits: {}", zen::GetLastError());
+ CloseHandle(m_JobHandle);
+ m_JobHandle = nullptr;
+ }
+}
+
+bool
+JobObject::AssignProcess(void* ProcessHandle)
+{
+ ZEN_ASSERT(m_JobHandle != nullptr, "JobObject not initialized");
+ ZEN_ASSERT(ProcessHandle != nullptr, "ProcessHandle is null");
+
+ if (!AssignProcessToJobObject(m_JobHandle, ProcessHandle))
+ {
+ ZEN_WARN("Failed to assign process to job object: {}", zen::GetLastError());
+ return false;
+ }
+
+ return true;
+}
+
+bool
+JobObject::IsValid() const
+{
+ return m_JobHandle != nullptr;
+}
+#endif // ZEN_PLATFORM_WINDOWS
+
+//////////////////////////////////////////////////////////////////////////
+
bool
IsProcessRunning(int pid, std::error_code& OutEc)
{