Go CLI工具信号处理问题:关闭终端窗口未触发清理函数
解决方案:处理终端关闭时的信号触发问题
1. 扩展监听的信号集合
不同终端(如iTerm)关闭窗口时发送的信号可能与MacOS默认终端不同,除已监听的信号外,需添加更多可捕获的终端相关信号:
修改信号监听代码为:
signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGABRT, os.Interrupt)
2. 调整进程组设置
终端关闭时通常会向前台进程组发送信号,若程序未成为进程组组长,可能无法接收信号。在main函数开头添加进程组设置:
func main() { // 将当前进程设为新进程组的组长 if err := syscall.Setpgid(0, 0); err != nil { logrus.Warnf("Failed to set process group: %v", err) } // ... 原有代码逻辑 }
该操作确保终端关闭时,信号能正确传递到目标进程。
3. 直接在信号处理goroutine中执行清理
避免通道传递可能引发的阻塞,直接在信号处理逻辑中调用cleanup后退出:
go func() { sig := <-signals fmt.Printf("\nReceived signal: %v, starting cleanup...\n", sig) cleanup() os.Exit(0) }()
跳过主goroutine的通道等待,确保清理逻辑立即触发。
4. 验证信号发送情况
若以上方法无效,可先确认iTerm关闭时发送的信号类型:
- 运行程序后,用
ps aux | grep <程序名>获取进程PID - 关闭iTerm窗口,同时在另一个终端执行
pkill -l -P <父进程PID>查看发送的信号
根据实际捕获的信号,补充到监听列表中。
修改后的完整测试代码
package main import ( "fmt" "os" "syscall" "os/signal" "os/exec" "github.com/sirupsen/logrus" ) func main() { // 设置当前进程为新进程组的组长 if err := syscall.Setpgid(0, 0); err != nil { logrus.Warnf("Failed to set process group: %v", err) } // Parse the command-line argument. if len(os.Args) < 2 { logrus.Fatalf("Missing required argument") } arg := os.Args[1] logrus.Infof("Running with argument: %s", arg) // Set up signal handling. signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGABRT, os.Interrupt) go func() { sig := <-signals fmt.Printf("\nReceived signal: %v\n", sig) cleanup() os.Exit(0) }() logrus.Infof("Press Ctrl+C or close terminal to trigger cleanup.") select {} // 阻塞主goroutine,直到信号触发 } func cleanup() { fmt.Println("Performing cleanup tasks...") touchFile := exec.Command("touch", "testfile.txt") _, err := touchFile.CombinedOutput() if err != nil { fmt.Printf("Cleanup error: %v\n", err) } }
关键说明
syscall.Setpgid(0,0):确保程序成为进程组组长,终端关闭时信号能直接传递- 扩展信号监听:覆盖iTerm等终端可能发送的非标准信号
- 直接执行清理:避免通道传递的潜在阻塞问题
内容的提问来源于stack exchange,提问作者gurpsone
相关产品推荐
相关产品推荐

