I want to exit the target process gracefully instead of terminating it directly. Process A send a command to Process B, and after receiving it, B will complete its cleanup tasks and then exit.
windows.GenerateConsoleCtrlEvent
.WM_CLOSE
.The Kill
method in the Golang os
package actually uses TerminateProcess
, which is not what I want.
Update:
I tried to make the target process listen to stdin. The subprocess started and then ended immediately. It seems that this doesn't work in non-interactive mode.
reader := bufio.NewReader(os.Stdin)
if _, err := reader.ReadString('\n'); err != nil {
stop()
}
Sorry, that's a stupid error. This method is feasible. It's just that I used stdin incorrectly.
I tried using taskkill /t
, but it told me that the target process is a child process of another process. So I adjusted the parent-child relationship and had the parent process call taskkill /t
, but it still returned the error Exit status 128
. That's very strange.
Finally:
In the end, I solved the problem using named pipes. The downside is that it requires support in the target process's code. But that's sufficient for me because the target process is not a third-party one.
In the end, I solved the problem using pipes.
// target process main.go
func main() {
// ... some logic
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
go func() {
pipeName := fmt.Sprintf("\\\\.\\pipe\\%d", os.Getpid())
listener, err := winio.ListenPipe(pipeName, nil)
if err != nil {
log.Errorf("Failed to create pipe: %v", err)
return
}
defer listener.Close()
conn, err := listener.Accept()
if err != nil {
log.Errorf("Pipe accept error: %v", err)
return
}
defer conn.Close()
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
log.Errorf("Pipe read error: %v", err)
}
msg := string(buf[:n])
if msg == "shutdown" {
sig <- syscall.SIGTERM
}
}()
<-sig
stop()
}
// control process
func killProcessGraceful(targetPID int) {
pipeName := fmt.Sprintf(`\\.\pipe\%d`, targetPID)
conn, err := winio.DialPipe(pipeName, nil)
if err != nil {
log.Errorf("Failed to connect to pipe (PID=%d): %v", targetPID, err)
return killProcessForcefully(targetPID)
}
defer conn.Close()
if _, err := conn.Write([]byte("shutdown")); err != nil {
log.Errorf("Failed to send shutdown command: %v", err)
return killProcessForcefully(targetPID)
}
return nil
}
// target process main.go
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
go func() {
buf := make([]byte, 1)
if _, err := os.Stdin.Read(buf); err == io.EOF {
sig <- syscall.SIGTERM
}
}()
// control process
func StartProcess(path string, arg ...string) error {
cmd := exec.Command(path, arg...)
cmd.Dir = filepath.Dir(path)
stdinPipe, err := cmd.StdinPipe()
if err != nil {
return errors.Errorf("get stdin pipe failed: %v", err)
}
pipeMap.Store(filepath.Base(path), stdinPipe)
if err := cmd.Start(); err != nil {
return errors.Errorf("start process %s failed: %v", filepath.Base(path), err)
}
return nil
}
func kill(process string) {
if pipe, ok := pipeMap.Load(processName); ok {
pipe.(io.WriteCloser).Close()
}
// ...force kill
}