Files
cdp_hub/internal/platform/browser/job_windows.go
T

127 lines
3.6 KiB
Go

//go:build windows
package browser
import (
"context"
"errors"
"fmt"
"os"
"syscall"
"unsafe"
)
const (
jobObjectExtendedLimitClass = 9
jobObjectLimitKillOnJobClose = 0x2000
wmClose = 0x0010
)
var (
kernel32Job = syscall.NewLazyDLL("kernel32.dll")
createJobObjectW = kernel32Job.NewProc("CreateJobObjectW")
setInformationJobObject = kernel32Job.NewProc("SetInformationJobObject")
assignProcessToJobObject = kernel32Job.NewProc("AssignProcessToJobObject")
terminateJobObject = kernel32Job.NewProc("TerminateJobObject")
enumWindows = user32.NewProc("EnumWindows")
getWindowProcessID = user32.NewProc("GetWindowThreadProcessId")
postMessageW = user32.NewProc("PostMessageW")
)
type jobHandle struct{ handle syscall.Handle }
type jobObjectBasicLimitInformation struct {
PerProcessUserTimeLimit int64
PerJobUserTimeLimit int64
LimitFlags uint32
MinimumWorkingSetSize uintptr
MaximumWorkingSetSize uintptr
ActiveProcessLimit uint32
Affinity uintptr
PriorityClass uint32
SchedulingClass uint32
}
type ioCounters struct{ ReadOperationCount, WriteOperationCount, OtherOperationCount, ReadTransferCount, WriteTransferCount, OtherTransferCount uint64 }
type jobObjectExtendedLimitInformation struct {
BasicLimitInformation jobObjectBasicLimitInformation
IoInfo ioCounters
ProcessMemoryLimit uintptr
JobMemoryLimit uintptr
PeakProcessMemoryUsed uintptr
PeakJobMemoryUsed uintptr
}
func (j *jobHandle) assign(process *os.Process) error {
job, _, err := createJobObjectW.Call(0, 0)
if job == 0 {
return fmt.Errorf("create browser job object: %w", err)
}
j.handle = syscall.Handle(job)
info := jobObjectExtendedLimitInformation{BasicLimitInformation: jobObjectBasicLimitInformation{LimitFlags: jobObjectLimitKillOnJobClose}}
if result, _, callErr := setInformationJobObject.Call(job, jobObjectExtendedLimitClass, uintptr(unsafe.Pointer(&info)), unsafe.Sizeof(info)); result == 0 {
j.close()
return fmt.Errorf("configure browser job object: %w", callErr)
}
processHandle, err := syscall.OpenProcess(0x0100|0x0001, false, uint32(process.Pid))
if err != nil {
j.close()
return fmt.Errorf("open browser process for job object: %w", err)
}
defer syscall.CloseHandle(processHandle)
if result, _, callErr := assignProcessToJobObject.Call(job, uintptr(processHandle)); result == 0 {
j.close()
return fmt.Errorf("assign browser process to job object: %w", callErr)
}
return nil
}
func (j *jobHandle) close() {
if j == nil || j.handle == 0 {
return
}
_ = syscall.CloseHandle(j.handle)
j.handle = 0
}
func (j *jobHandle) terminate() error {
if j == nil || j.handle == 0 {
return errors.New("browser job object is unavailable")
}
result, _, err := terminateJobObject.Call(uintptr(j.handle), 1)
if result == 0 {
return err
}
return nil
}
func (p *osProcess) Stop(ctx context.Context, force bool) error {
if err := ctx.Err(); err != nil {
return err
}
if force {
return p.job.terminate()
}
return postCloseToProcess(p.command.Process.Pid)
}
func postCloseToProcess(pid int) error {
if pid <= 0 {
return errors.New("invalid browser PID")
}
callback := syscall.NewCallback(func(hwnd uintptr, lParam uintptr) uintptr {
var windowPID uint32
getWindowProcessID.Call(hwnd, uintptr(unsafe.Pointer(&windowPID)))
if int(windowPID) == pid {
postMessageW.Call(hwnd, wmClose, 0, 0)
}
return 1
})
result, _, err := enumWindows.Call(callback, uintptr(pid))
if result == 0 && err != syscall.Errno(0) {
return err
}
return nil
}