فهرست منبع

Merge pull request #171 from docker/windows_kill_child_processes

Windows : kill child processes
Djordje Lukic 5 سال پیش
والد
کامیت
e84b508e80
4فایلهای تغییر یافته به همراه115 افزوده شده و 6 حذف شده
  1. 93 0
      cli/dockerclassic/job_windows.go
  2. 5 5
      tests/e2e/e2e_test.go
  3. 9 1
      tests/framework/exec.go
  4. 8 0
      tests/framework/suite.go

+ 93 - 0
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
+}

+ 5 - 5
tests/e2e/e2e_test.go

@@ -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())

+ 9 - 1
tests/framework/exec.go

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

+ 8 - 0
tests/framework/suite.go

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