From 995d04760888b829ff3048840919515fca5698b0 Mon Sep 17 00:00:00 2001 From: "guillaume.tardif" Date: Thu, 4 Jun 2020 16:32:21 +0200 Subject: [PATCH] Windows specific : ensure all child processes are killed when parent exits (also works when killing parent). From Desktop code --- cli/dockerclassic/job_windows.go | 93 ++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 cli/dockerclassic/job_windows.go diff --git a/cli/dockerclassic/job_windows.go b/cli/dockerclassic/job_windows.go new file mode 100644 index 000000000..e3c276e92 --- /dev/null +++ b/cli/dockerclassic/job_windows.go @@ -0,0 +1,93 @@ +package dockerclassic + +import ( + "fmt" + "os" + "syscall" + "unsafe" +) + +func init() { + if err := killSubProcessesOnClose(); err != nil { + fmt.Println("failed to create job:", err) + } +} + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") +) + +type jobObjectExtendedLimitInformation struct { + BasicLimitInformation struct { + PerProcessUserTimeLimit uint64 + PerJobUserTimeLimit uint64 + LimitFlags uint32 + MinimumWorkingSetSize uintptr + MaximumWorkingSetSize uintptr + ActiveProcessLimit uint32 + Affinity uintptr + PriorityClass uint32 + SchedulingClass uint32 + } + IoInfo struct { + ReadOperationCount uint64 + WriteOperationCount uint64 + OtherOperationCount uint64 + ReadTransferCount uint64 + WriteTransferCount uint64 + OtherTransferCount uint64 + } + ProcessMemoryLimit uintptr + JobMemoryLimit uintptr + PeakProcessMemoryUsed uintptr + PeakJobMemoryUsed uintptr +} + +// killSubProcessesOnClose will ensure on windows that all child processes of the current process are killed if parent is killed. +func killSubProcessesOnClose() error { + job, err := createJobObject() + if err != nil { + return err + } + info := jobObjectExtendedLimitInformation{} + info.BasicLimitInformation.LimitFlags = 0x2000 + if err := setInformationJobObject(job, info); err != nil { + _ = syscall.CloseHandle(job) + return err + } + proc, err := syscall.GetCurrentProcess() + if err != nil { + _ = syscall.CloseHandle(job) + return err + } + if err := assignProcessToJobObject(job, proc); err != nil { + _ = syscall.CloseHandle(job) + return err + } + return nil +} + +func createJobObject() (syscall.Handle, error) { + res, _, err := kernel32.NewProc("CreateJobObjectW").Call(uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(nil))) + if res == 0 { + return syscall.InvalidHandle, os.NewSyscallError("CreateJobObject", err) + } + return syscall.Handle(res), nil +} + +func setInformationJobObject(job syscall.Handle, info jobObjectExtendedLimitInformation) error { + infoClass := uint32(9) + res, _, err := kernel32.NewProc("SetInformationJobObject").Call(uintptr(job), uintptr(infoClass), uintptr(unsafe.Pointer(&info)), uintptr(uint32(unsafe.Sizeof(info)))) + if res == 0 { + return os.NewSyscallError("SetInformationJobObject", err) + } + return nil +} + +func assignProcessToJobObject(job syscall.Handle, process syscall.Handle) error { + res, _, err := kernel32.NewProc("AssignProcessToJobObject").Call(uintptr(job), uintptr(process)) + if res == 0 { + return os.NewSyscallError("AssignProcessToJobObject", err) + } + return nil +}