Buffalo框架默认不处理SIGTERM,需手动注册signal.Notify监听os.Interrupt和syscall.SIGTERM,自行托管http.Server并调用Shutdown(),显式关闭数据库等资源,且生产环境必须用buffalo build启动而非buffalo dev。

Buffalo 框架默认不处理 SIGTERM,必须手动注册
Buffalo 基于 Go 的 net/http 服务器启动,本身不内置信号监听逻辑。它启动后直接阻塞在 http.Serve 或 http.ListenAndServe,而 Go 标准库对 SIGTERM 的默认行为是**进程立即退出**,不会等待清理。这意味着你写的 defer、database.Close()、redis.Pool.Close() 等都不会执行。
用 signal.Notify 捕获 SIGTERM 并触发 shutdown 流程
标准做法是启动一个 goroutine 监听 os.Interrupt 和 syscall.SIGTERM,收到后调用自定义的 shutdown 函数。关键点在于:不能直接 os.Exit(0),而要先关闭 HTTP server,再清理其他资源。
-
buffalo.App实例本身没有Shutdown()方法,需自己封装http.Server - 推荐在
actions/app.go的App()函数返回前,把*http.Server赋值给全局变量或结构体字段,方便后续调用srv.Shutdown(context.WithTimeout(...)) - 务必设置超时(如 10 秒),避免因长连接或死循环卡住退出
- 清理顺序建议:HTTP server → 数据库连接池 → Redis 客户端 → 文件句柄 / 临时目录
示例片段:
Buffalo框架 1.0.1 版本源码包下载,适合需要错误处理改进、依赖更新、render.Download 注释和 request logger 调整的 v1 项目。
srv := &http.Server{
Addr: env.MustString("ADDR", ":3000"),
Handler: app.Handler,
}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan // 阻塞等待信号
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("HTTP shutdown error: %v", err)
}
// 这里写你的 cleanup():db.Close(), redis.Close(), etc.
别忘了禁用 Buffalo 自带的 dev 模式热重载干扰
开发时用 buffalo dev 启动,它会 fork 子进程并监听文件变更——此时你发 SIGTERM 到主进程,实际被 fork 出来的子进程忽略,导致清理逻辑不执行。生产部署必须用 buffalo build && ./myapp 启动原生二进制,否则信号捕获无效。
-
buffalo dev下的进程树里,真正跑 HTTP 的是子进程,父进程只管 watch - 用
ps aux | grep myapp查看实际 PID,确认你 kill 的是 HTTP server 所在进程 - CI/CD 打包阶段务必检查是否误用了
buffalo dev构建产物
数据库连接池未关闭会导致 Kubernetes readiness probe 失败
在 K8s 环境下,如果只关了 HTTP server 却没调 db.Close(),PostgreSQL 或 MySQL 连接池仍维持空闲连接。K8s 的 preStop hook 发出 SIGTERM 后,若容器内还有活跃连接,endpoint 可能延迟摘除,新流量继续打进来,而 server 已 shutdown —— 表现为 502/503。这是最容易被忽略的「假优雅」。
- Buffalo 默认用
pop管理数据库,它的tx.Close()不等于db.Close();要显式调用pop.Connections["default"].DB.Close() - 如果你用了
github.com/gobuffalo/pop/v6,注意v6中tx.Close()是空操作,真正释放连接池得靠pop.DestroyAll()或直接pop.Connections["default"].DB.Close() - 测试方法:在 shutdown 前加
log.Println("DB connections:", pop.Connections["default"].DB.Stats().OpenConnections),确认降为 0

















