From e4bd861ca9da7594465123ae58328ce6ddb8c979 Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Tue, 15 Oct 2024 10:06:47 -0400 Subject: [PATCH] windows: set job object for executor and children On Windows, if the `raw_exec` driver's executor exits, the child processes are not also killed. Create a Windows "job object" (not to be confused with a Nomad job) and add the executor to it. Child processes of the executor will inherit the job automatically. When the handle to the job object is freed (on executor exit), the job itself is destroyed and this causes all processes in that job to exit. Fixes: https://github.com/hashicorp/nomad/issues/23668 Ref: https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects --- .changelog/24214.txt | 3 ++ drivers/shared/executor/executor_windows.go | 33 ++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 .changelog/24214.txt diff --git a/.changelog/24214.txt b/.changelog/24214.txt new file mode 100644 index 00000000000..d0e59532db9 --- /dev/null +++ b/.changelog/24214.txt @@ -0,0 +1,3 @@ +```release-note:bug +windows: Fixed a bug where a crashed executor would orphan task processes +``` diff --git a/drivers/shared/executor/executor_windows.go b/drivers/shared/executor/executor_windows.go index 457f29a6e02..25134ece5d1 100644 --- a/drivers/shared/executor/executor_windows.go +++ b/drivers/shared/executor/executor_windows.go @@ -9,17 +9,48 @@ import ( "fmt" "os" "syscall" + "unsafe" "golang.org/x/sys/windows" ) -// configure new process group for child process +// configure new process group for child process and creates a JobObject for the +// executor. Children of the executor will be created in the same JobObject +// Ref: https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects func (e *UniversalExecutor) setNewProcessGroup() error { // We need to check that as build flags includes windows for this file if e.childCmd.SysProcAttr == nil { e.childCmd.SysProcAttr = &syscall.SysProcAttr{} } e.childCmd.SysProcAttr.CreationFlags = syscall.CREATE_NEW_PROCESS_GROUP + + // note: we don't call CloseHandle on this job handle because we need to + // hold onto it until the executor exits + job, err := windows.CreateJobObject(nil, nil) + if err != nil { + return fmt.Errorf("could not create Windows job object for executor: %w", err) + } + + info := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{ + BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{ + LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE, + }, + } + _, err = windows.SetInformationJobObject( + job, + windows.JobObjectExtendedLimitInformation, + uintptr(unsafe.Pointer(&info)), + uint32(unsafe.Sizeof(info))) + if err != nil { + return fmt.Errorf("could not configure Windows job object for executor: %w", err) + } + + handle := windows.CurrentProcess() + err = windows.AssignProcessToJobObject(job, handle) + if err != nil { + return fmt.Errorf("could not assign executor to Windows job object: %w", err) + } + return nil }