Go服务不加信号监听+Shutdown()即为猝死:请求中断、日志截断、事务不全;ListenAndServe()不响应信号,Ctrl+C或kill-15导致OS强制终止,defer不执行、连接RST、日志/DB写入中断。

Go 服务不加信号监听 + http.Server.Shutdown(),就不是退出,是猝死——请求中断、日志截断、数据库事务回滚不全。
为什么 http.ListenAndServe() 按 Ctrl+C 就 panic 或直接退出
它只是个阻塞调用,完全不处理任何系统信号。收到 SIGINT(Ctrl+C)或 SIGTERM(kill -15)时,进程被 OS 强制终止:defer 不执行、TCP 连接 RST、log.Printf() 写一半就停、db.Exec() 可能已扣款但订单没落库。
常见现象包括:
-
curl突然报Connection reset by peer - 日志最后一行卡在
processing req id=abc就没了 -
netstat -an | grep :8080显示大量残留ESTABLISHED或TIME_WAIT
别指望 os.Exit(0) 或函数 return 能补救——它们根本轮不到执行。
立即学习“go语言免费学习笔记(深入)”;
怎么写一个不卡死的 srv.Shutdown() 调用
Shutdown() 本身不阻塞,但它会等已有连接自然结束;没超时控制,就可能永远卡住。关键不是“关服务器”,而是“给请求留够收尾时间”。
- 必须用
context.WithTimeout(context.Background(), 15*time.Second),不能用context.Background()或context.TODO() - 超时时间得略大于你最长请求耗时:REST API 推荐 5–10 秒,文件上传或支付回调建议 20–30 秒
- 错误只忽略
http.ErrServerClosed;其他如context.DeadlineExceeded得记录 - 调用前确保
srv.Listener已关闭(Shutdown()内部会做,但自定义 listener 时建议显式ln.Close())
示例片段:
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil && err != http.ErrServerClosed {
log.Printf("server shutdown error: %v", err)
}为什么 Shutdown() 返回了,进程还在跑
http.Server.Shutdown() 只管 HTTP 连接,不管你的业务 goroutine。后台消费者、定时器、DB 连接池、Redis 客户端……全得手动关,否则主 goroutine 退出后它们还在跑。
- 所有长期 goroutine 必须用
select { case ,不能只轮询 <code>if ctx.Err() != nil(后者忙等且不及时) -
sql.DB要先db.SetMaxOpenConns(1)加速归还连接,再db.Close() -
redis.Client必须调client.Close();time.Ticker得在select里监听ctx.Done()并调ticker.Stop() - WebSocket / SSE 连接不会被
Shutdown()等待,得在 handler 里自己监听ctx.Done()主动断开
最容易被忽略的三个硬伤
不是代码写错,而是底层约束没对齐——哪怕 Shutdown() 跑通,这些点一漏,“优雅”就破功:
-
http.Server必须设ReadTimeout和WriteTimeout(比如各15 * time.Second),否则慢客户端占着连接不发完数据,Shutdown()就永远等不到它 idle - handler 里用了没传
ctx的阻塞操作:比如time.Sleep(10 * time.Second)、没设超时的http.Client.Do()、同步写磁盘,这些不会响应取消信号 - 信号监听写了
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)却收不到信号?问题不在注册,而在没“等”——若没或 <code>for range sigChan,程序启动完就退出,信号直接丢弃


















