Merge pull request #171 from docker/windows_kill_child_processes

Windows : kill child processes
This commit is contained in:
Djordje Lukic 2020-06-05 00:42:21 -07:00 committed by GitHub
commit e84b508e80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 115 additions and 6 deletions

View File

@ -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
}

View File

@ -109,7 +109,7 @@ func (s *E2eSuite) TestSetupError() {
func (s *E2eSuite) TestKillChildOnCancel() {
It("should kill docker-classic if parent command is cancelled", func() {
out := s.NewCommand("ps", "-x").ExecOrDie()
out := s.ListProcessesCommand().ExecOrDie()
Expect(out).NotTo(ContainSubstring("docker-classic"))
dir := s.ConfigDir
@ -122,16 +122,16 @@ RUN sleep 100`), 0644)).To(Succeed())
_, err := ctx.Exec()
errs <- err
}()
err := WaitFor(time.Second, 3*time.Second, errs, func() bool {
out := s.NewCommand("ps", "-x").ExecOrDie()
err := WaitFor(time.Second, 10*time.Second, errs, func() bool {
out := s.ListProcessesCommand().ExecOrDie()
return strings.Contains(out, "docker-classic")
})
Expect(err).NotTo(HaveOccurred())
log.Println("Killing docker process")
close(shutdown)
err = WaitFor(time.Second, 4*time.Second, nil, func() bool {
out := s.NewCommand("ps", "-x").ExecOrDie()
err = WaitFor(time.Second, 12*time.Second, nil, func() bool {
out := s.ListProcessesCommand().ExecOrDie()
return !strings.Contains(out, "docker-classic")
})
Expect(err).NotTo(HaveOccurred())

View File

@ -32,6 +32,7 @@ import (
"fmt"
"io"
"os/exec"
"runtime"
"strings"
"syscall"
"time"
@ -176,7 +177,7 @@ func Execute(cmd *exec.Cmd, timeout <-chan time.Time) (string, error) {
}
case <-timeout:
log.Debugf("%s %s timed-out", cmd.Path, strings.Join(cmd.Args[1:], " "))
if err := cmd.Process.Signal(syscall.SIGTERM); err != nil {
if err := terminateProcess(cmd); err != nil {
return "", err
}
return "", fmt.Errorf(
@ -189,6 +190,13 @@ func Execute(cmd *exec.Cmd, timeout <-chan time.Time) (string, error) {
return stdout.String(), nil
}
func terminateProcess(cmd *exec.Cmd) error {
if runtime.GOOS == "windows" {
return cmd.Process.Kill()
}
return cmd.Process.Signal(syscall.SIGTERM)
}
func mergeWriter(other io.Writer, buf io.Writer) io.Writer {
if other != nil {
return io.MultiWriter(other, buf)

View File

@ -107,6 +107,14 @@ func (s *Suite) AfterTest(suite, test string) {
_ = os.RemoveAll(s.ConfigDir)
}
// ListProcessesCommand creates a command to list processes, "tasklist" on windows, "ps" otherwise.
func (s *Suite) ListProcessesCommand() *CmdContext {
if runtime.GOOS == "windows" {
return s.NewCommand("tasklist")
}
return s.NewCommand("ps")
}
// NewCommand creates a command context.
func (s *Suite) NewCommand(command string, args ...string) *CmdContext {
return &CmdContext{