Skip to content

Instantly share code, notes, and snippets.

@hallazzang
Last active April 28, 2024 06:58
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save hallazzang/76f3970bfc949831808bbebc8ca15209 to your computer and use it in GitHub Desktop.
Save hallazzang/76f3970bfc949831808bbebc8ca15209 to your computer and use it in GitHub Desktop.
[go] (Windows) ensure all child processes are killed when main program exits
package main
import (
"os/exec"
"unsafe"
"golang.org/x/sys/windows"
)
// We use this struct to retreive process handle(which is unexported)
// from os.Process using unsafe operation.
type process struct {
Pid int
Handle uintptr
}
func main() {
job, err := windows.CreateJobObject(nil, nil)
if err != nil {
panic(err)
}
defer windows.CloseHandle(job)
info := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{
BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{
LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
},
}
if _, err := windows.SetInformationJobObject(
job,
windows.JobObjectExtendedLimitInformation,
uintptr(unsafe.Pointer(&info)),
uint32(unsafe.Sizeof(info))); err != nil {
panic(err)
}
cmd := exec.Command("notepad.exe", "noname")
if err := cmd.Start(); err != nil {
panic(err)
}
if err := windows.AssignProcessToJobObject(
job,
windows.Handle((*process)(unsafe.Pointer(cmd.Process)).Handle)); err != nil {
panic(err)
}
if err := cmd.Wait(); err != nil {
panic(err)
}
}
@hallazzang
Copy link
Author

Here's a better version using struct and methods:

package main

import (
	"os"
	"os/exec"
	"unsafe"

	"golang.org/x/sys/windows"
)

// We use this struct to retreive process handle(which is unexported)
// from os.Process using unsafe operation.
type process struct {
	Pid    int
	Handle uintptr
}

type ProcessExitGroup windows.Handle

func NewProcessExitGroup() (ProcessExitGroup, error) {
	handle, err := windows.CreateJobObject(nil, nil)
	if err != nil {
		return 0, err
	}

	info := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{
		BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{
			LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
		},
	}
	if _, err := windows.SetInformationJobObject(
		handle,
		windows.JobObjectExtendedLimitInformation,
		uintptr(unsafe.Pointer(&info)),
		uint32(unsafe.Sizeof(info))); err != nil {
		return 0, err
	}

	return ProcessExitGroup(handle), nil
}

func (g ProcessExitGroup) Dispose() error {
	return windows.CloseHandle(windows.Handle(g))
}

func (g ProcessExitGroup) AddProcess(p *os.Process) error {
	return windows.AssignProcessToJobObject(
		windows.Handle(g),
		windows.Handle((*process)(unsafe.Pointer(p)).Handle))
}

func main() {
	g, err := NewProcessExitGroup()
	if err != nil {
		panic(err)
	}
	defer g.Dispose()

	cmd := exec.Command("notepad.exe", "noname")
	if err := cmd.Start(); err != nil {
		panic(err)
	}

	if err := g.AddProcess(cmd.Process); err != nil {
		panic(err)
	}

	if err := cmd.Wait(); err != nil {
		panic(err)
	}
}

You can easily use ProcessExitGroup anywhere.

@pascallin
Copy link

Nice work, it works perfect.

@jdelrue
Copy link

jdelrue commented Feb 8, 2023

❤️❤️❤️❤️

Saved our day

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment