@@ -55,6 +55,27 @@ struct ProcessHandle
5555 CloseHandle (process_handle);
5656 }
5757};
58+ // =============================================================================
59+ // Job Object for child process cleanup
60+ // =============================================================================
61+
62+ // Singleton job object that kills all child processes when parent exits.
63+ // Using JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE ensures spawned servers die
64+ // when the client process terminates (even abnormally).
65+ static HANDLE get_child_process_job ()
66+ {
67+ static HANDLE job = []() -> HANDLE {
68+ HANDLE h = CreateJobObjectA (nullptr , nullptr );
69+ if (h)
70+ {
71+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION info = {};
72+ info.BasicLimitInformation .LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
73+ SetInformationJobObject (h, JobObjectExtendedLimitInformation, &info, sizeof (info));
74+ }
75+ return h;
76+ }();
77+ return job;
78+ }
5879
5980// =============================================================================
6081// Helper functions
@@ -520,6 +541,11 @@ void Process::spawn(
520541 handle_->process_id = pi.dwProcessId ;
521542 handle_->running = true ;
522543
544+ // Assign to job object so child dies when parent dies
545+ HANDLE job = get_child_process_job ();
546+ if (job)
547+ AssignProcessToJobObject (job, pi.hProcess );
548+
523549 stdin_->handle_ ->handle = stdin_write;
524550 stdout_->handle_ ->handle = stdout_read;
525551 stderr_->handle_ ->handle = stderr_read;
0 commit comments