iris 的 mvc 层不支持优雅关闭,必须在 main.go 中通过 signal.notify 捕获 sigint/sigterm 并手动关闭 http 服务器、grpc 连接、db 连接池等资源,使用 app.shutdown() 等待请求完成后再退出。

Iris 的 MVC 层本身不提供后台服务生命周期管理能力,所谓“优雅关闭”必须绕过 MVC,直接在 app 或宿主进程层面处理。 它没有 IHostedService 那类抽象,也不监听系统信号或提供 OnStopping 钩子。你得自己注册信号、控制连接、手动释放资源。
为什么 MVC 控制器里不能做优雅关闭
MVC 控制器是请求级对象,每次 HTTP 请求触发一次实例化(或复用 Singleton),它不持有长期运行的 goroutine,也不参与应用启动/退出流程。你在控制器里写个 Stop() 方法,没人会调用它 —— Iris 不会在进程退出前自动遍历所有控制器并调用清理逻辑。
-
BeforeActivation只用于路由注册和依赖注入,不是生命周期钩子 - 控制器字段(如
conn *grpc.ClientConn)若未显式关闭,进程退出时由 Go runtime 强制回收,但连接可能没发完 FIN、gRPC 流未 graceful shutdown、数据库连接池未 drain - 任何在控制器方法内启的
go func() {...}()都属于“失控 goroutine”,无法被统一等待或取消
真正能优雅关闭的地方:main.go + signal.Notify
必须在 main() 函数中捕获 SIGINT / SIGTERM,然后主动关闭你创建的所有长期资源。典型顺序是:停 HTTP 服务器 → 关 gRPC 连接 → 关 DB 连接池 → 关日志 flush → exit。
- 用
app.Shutdown()替代直接os.Exit():它会等待正在处理的请求完成(默认 30 秒超时) - 把关键资源(如
*sql.DB、*grpc.ClientConn、自定义 worker channel)声明为包级变量或传入结构体,在 signal handler 里依次调用.Close()或.GracefulStop() - 别忘了给 gRPC client 加
WithBlock()和超时上下文,否则conn.Close()可能卡住
func main() {
app := iris.New()
// ... 注册 MVC、中间件等
srv := &http.Server{
Addr: ":8080",
Handler: app,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<h3>如果非要让后台任务和 MVC “有关联”,只能靠共享依赖注入</h3>
<p>你可以把后台 worker 封装成一个结构体,实现 <code>Start()</code> / <code>Stop()</code>,然后在 <code>main()</code> 中初始化它,并通过 <code>app.Register()</code> 注入到 MVC 控制器里 —— 但这只是“共享实例”,不是“MVC 管理生命周期”。停止动作仍必须由 <code>main()</code> 中的 signal handler 触发。</p>
- 不要在控制器里调
worker.Stop():HTTP 接口不是关机按钮,它不该承担进程级职责 - 避免用
sync.WaitGroup或context.WithCancel在控制器里启动 goroutine:一旦请求结束,context 被 cancel,goroutine 可能提前退出,导致任务中断 - 若需从 HTTP 触发停止(如运维接口
POST /admin/shutdown),仍要确保该 handler 最终调用的是和 signal handler 相同的 shutdown 函数,且只允许执行一次(加sync.Once)
最容易被忽略的一点:app.Run() 是阻塞调用,它内部已经启了一个 http server 并监听信号;如果你自己再起一个 srv.ListenAndServe(),就重复了。要么全用 app.Run()(然后用 app.Shutdown()),要么全自己管 http.Server —— 混用会导致 shutdown 行为不可预测。











