Skip to content

Commit 49464e5

Browse files
committed
feat: add lifecycle hook support and documentation for server shutdown
- Add lifecycle hook support with WithBeforeShutdown and WithAfterShutdown options for registering custom logic before and after graceful server shutdown. - Document lifecycle hooks usage, execution order, and sample code in all README files with examples in English, Simplified Chinese, and Traditional Chinese. - Create a complete example under _examples/hooks demonstrating lifecycle hook usage, expected output, and use cases. - Validate that lifecycle hook options reject nil functions and handle hook errors properly. - Test lifecycle hook execution order, error handling, and context cancellation in the shutdown process. Signed-off-by: appleboy <appleboy.tw@gmail.com>
1 parent 6f9b980 commit 49464e5

10 files changed

Lines changed: 750 additions & 0 deletions

File tree

README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ English | [繁體中文](README.zh-tw.md) | [简体中文](README.zh-cn.md)
1919
- [Server Start Methods](#server-start-methods)
2020
- [Shutdown and Cleanup Methods](#shutdown-and-cleanup-methods)
2121
- [Options](#options)
22+
- [Lifecycle Hooks Example](#lifecycle-hooks-example)
2223
- [License](#license)
2324

2425
## Features
@@ -154,6 +155,12 @@ Various options allow configuration of servers:
154155
- `WriteTimeout`: Response write timeout (default: 30 seconds)
155156
- `IdleTimeout`: Keep-alive idle connection timeout (default: 60 seconds)
156157

158+
- **WithBeforeShutdown(hook Hook)**:
159+
Register a hook to be called before server shutdown begins. Multiple hooks can be registered and will execute in registration order. Hooks receive the shutdown context and can return errors, which are collected but do not prevent shutdown from proceeding.
160+
161+
- **WithAfterShutdown(hook Hook)**:
162+
Register a hook to be called after all servers have shut down. Multiple hooks can be registered and will execute in registration order. Hooks receive the shutdown context and can return errors, which are collected and returned but do not affect server shutdown.
163+
157164
Example with custom timeouts:
158165

159166
```go
@@ -163,6 +170,76 @@ router, err := graceful.Default(
163170
)
164171
```
165172

173+
## Lifecycle Hooks Example
174+
175+
Lifecycle hooks allow you to execute custom logic during graceful shutdown. Use `WithBeforeShutdown` for cleanup that should happen before servers stop (like deregistering from service discovery), and `WithAfterShutdown` for cleanup after servers have stopped (like closing database connections).
176+
177+
```go
178+
package main
179+
180+
import (
181+
"context"
182+
"log"
183+
"net/http"
184+
"os/signal"
185+
"syscall"
186+
187+
"github.com/gin-contrib/graceful"
188+
"github.com/gin-gonic/gin"
189+
)
190+
191+
func main() {
192+
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
193+
defer stop()
194+
195+
router, err := graceful.Default(
196+
// BeforeShutdown: called before server shutdown begins
197+
graceful.WithBeforeShutdown(func(ctx context.Context) error {
198+
log.Println("Notifying load balancer...")
199+
// Deregister from load balancer
200+
return nil
201+
}),
202+
graceful.WithBeforeShutdown(func(ctx context.Context) error {
203+
log.Println("Stopping background workers...")
204+
// Stop accepting new background jobs
205+
return nil
206+
}),
207+
208+
// AfterShutdown: called after all servers have shut down
209+
graceful.WithAfterShutdown(func(ctx context.Context) error {
210+
log.Println("Closing database connections...")
211+
// Close database pool
212+
return nil
213+
}),
214+
graceful.WithAfterShutdown(func(ctx context.Context) error {
215+
log.Println("Flushing metrics...")
216+
// Send final metrics
217+
return nil
218+
}),
219+
)
220+
if err != nil {
221+
panic(err)
222+
}
223+
defer router.Close()
224+
225+
router.GET("/", func(c *gin.Context) {
226+
c.String(http.StatusOK, "Welcome Gin Server")
227+
})
228+
229+
if err := router.RunWithContext(ctx); err != nil && err != context.Canceled {
230+
panic(err)
231+
}
232+
}
233+
```
234+
235+
When shutdown is triggered, hooks execute in this order:
236+
237+
1. All `BeforeShutdown` hooks (in registration order)
238+
2. Server graceful shutdown
239+
3. All `AfterShutdown` hooks (in registration order)
240+
241+
See the [hooks example](_examples/hooks) for a complete working example.
242+
166243
---
167244

168245
## License

README.zh-cn.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- [服务器启动方法](#服务器启动方法)
2020
- [关闭与清理方法](#关闭与清理方法)
2121
- [选项](#选项)
22+
- [生命周期 Hooks 示例](#生命周期-hooks-示例)
2223
- [许可证](#许可证)
2324

2425
## 特性
@@ -154,6 +155,12 @@ type Graceful struct {
154155
- `WriteTimeout`:响应写入超时(默认:30 秒)
155156
- `IdleTimeout`:Keep-Alive 空闲连接超时(默认:60 秒)
156157

158+
- **WithBeforeShutdown(hook Hook)**
159+
注册一个在服务器开始关闭前调用的 hook。可注册多个 hooks,将按注册顺序执行。Hooks 会接收到 shutdown context,可返回错误,这些错误会被收集但不会阻止关闭流程继续执行。
160+
161+
- **WithAfterShutdown(hook Hook)**
162+
注册一个在所有服务器关闭后调用的 hook。可注册多个 hooks,将按注册顺序执行。Hooks 会接收到 shutdown context,可返回错误,这些错误会被收集并返回,但不影响服务器关闭流程。
163+
157164
自定义超时设置示例:
158165

159166
```go
@@ -163,6 +170,76 @@ router, err := graceful.Default(
163170
)
164171
```
165172

173+
## 生命周期 Hooks 示例
174+
175+
生命周期 hooks 允许你在优雅关闭过程中执行自定义逻辑。使用 `WithBeforeShutdown` 进行应在服务器停止前的清理工作(如从服务发现中注销),使用 `WithAfterShutdown` 进行服务器停止后的清理工作(如关闭数据库连接)。
176+
177+
```go
178+
package main
179+
180+
import (
181+
"context"
182+
"log"
183+
"net/http"
184+
"os/signal"
185+
"syscall"
186+
187+
"github.com/gin-contrib/graceful"
188+
"github.com/gin-gonic/gin"
189+
)
190+
191+
func main() {
192+
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
193+
defer stop()
194+
195+
router, err := graceful.Default(
196+
// BeforeShutdown:在服务器开始关闭前调用
197+
graceful.WithBeforeShutdown(func(ctx context.Context) error {
198+
log.Println("通知负载均衡器...")
199+
// 从负载均衡器注销
200+
return nil
201+
}),
202+
graceful.WithBeforeShutdown(func(ctx context.Context) error {
203+
log.Println("停止后台工作进程...")
204+
// 停止接收新的后台任务
205+
return nil
206+
}),
207+
208+
// AfterShutdown:在所有服务器关闭后调用
209+
graceful.WithAfterShutdown(func(ctx context.Context) error {
210+
log.Println("关闭数据库连接...")
211+
// 关闭数据库连接池
212+
return nil
213+
}),
214+
graceful.WithAfterShutdown(func(ctx context.Context) error {
215+
log.Println("推送指标数据...")
216+
// 发送最终指标
217+
return nil
218+
}),
219+
)
220+
if err != nil {
221+
panic(err)
222+
}
223+
defer router.Close()
224+
225+
router.GET("/", func(c *gin.Context) {
226+
c.String(http.StatusOK, "Welcome Gin Server")
227+
})
228+
229+
if err := router.RunWithContext(ctx); err != nil && err != context.Canceled {
230+
panic(err)
231+
}
232+
}
233+
```
234+
235+
当触发关闭时,hooks 会按以下顺序执行:
236+
237+
1. 所有 `BeforeShutdown` hooks(按注册顺序)
238+
2. 服务器优雅关闭
239+
3. 所有 `AfterShutdown` hooks(按注册顺序)
240+
241+
完整可执行示例请参阅 [hooks 示例](_examples/hooks)
242+
166243
---
167244

168245
## 许可证

README.zh-tw.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
- [伺服器啟動方法](#伺服器啟動方法)
2020
- [關閉與清理方法](#關閉與清理方法)
2121
- [選項](#選項)
22+
- [生命週期 Hooks 範例](#生命週期-hooks-範例)
2223
- [授權條款](#授權條款)
2324

2425
## 特色
@@ -154,6 +155,12 @@ type Graceful struct {
154155
- `WriteTimeout`:回應寫入逾時(預設:30 秒)
155156
- `IdleTimeout`:Keep-Alive 閒置連線逾時(預設:60 秒)
156157

158+
- **WithBeforeShutdown(hook Hook)**
159+
註冊一個在伺服器開始關閉前呼叫的 hook。可註冊多個 hooks,將按註冊順序執行。Hooks 會接收到 shutdown context,可回傳錯誤,這些錯誤會被收集但不會阻止關閉流程繼續執行。
160+
161+
- **WithAfterShutdown(hook Hook)**
162+
註冊一個在所有伺服器關閉後呼叫的 hook。可註冊多個 hooks,將按註冊順序執行。Hooks 會接收到 shutdown context,可回傳錯誤,這些錯誤會被收集並回傳,但不影響伺服器關閉流程。
163+
157164
自訂逾時設定範例:
158165

159166
```go
@@ -163,6 +170,76 @@ router, err := graceful.Default(
163170
)
164171
```
165172

173+
## 生命週期 Hooks 範例
174+
175+
生命週期 hooks 允許你在優雅關閉過程中執行自訂邏輯。使用 `WithBeforeShutdown` 進行應在伺服器停止前的清理工作(如從服務發現中註銷),使用 `WithAfterShutdown` 進行伺服器停止後的清理工作(如關閉資料庫連線)。
176+
177+
```go
178+
package main
179+
180+
import (
181+
"context"
182+
"log"
183+
"net/http"
184+
"os/signal"
185+
"syscall"
186+
187+
"github.com/gin-contrib/graceful"
188+
"github.com/gin-gonic/gin"
189+
)
190+
191+
func main() {
192+
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
193+
defer stop()
194+
195+
router, err := graceful.Default(
196+
// BeforeShutdown:在伺服器開始關閉前呼叫
197+
graceful.WithBeforeShutdown(func(ctx context.Context) error {
198+
log.Println("通知負載平衡器...")
199+
// 從負載平衡器註銷
200+
return nil
201+
}),
202+
graceful.WithBeforeShutdown(func(ctx context.Context) error {
203+
log.Println("停止背景工作程序...")
204+
// 停止接收新的背景任務
205+
return nil
206+
}),
207+
208+
// AfterShutdown:在所有伺服器關閉後呼叫
209+
graceful.WithAfterShutdown(func(ctx context.Context) error {
210+
log.Println("關閉資料庫連線...")
211+
// 關閉資料庫連線池
212+
return nil
213+
}),
214+
graceful.WithAfterShutdown(func(ctx context.Context) error {
215+
log.Println("推送指標資料...")
216+
// 傳送最終指標
217+
return nil
218+
}),
219+
)
220+
if err != nil {
221+
panic(err)
222+
}
223+
defer router.Close()
224+
225+
router.GET("/", func(c *gin.Context) {
226+
c.String(http.StatusOK, "Welcome Gin Server")
227+
})
228+
229+
if err := router.RunWithContext(ctx); err != nil && err != context.Canceled {
230+
panic(err)
231+
}
232+
}
233+
```
234+
235+
當觸發關閉時,hooks 會按以下順序執行:
236+
237+
1. 所有 `BeforeShutdown` hooks(按註冊順序)
238+
2. 伺服器優雅關閉
239+
3. 所有 `AfterShutdown` hooks(按註冊順序)
240+
241+
完整可執行範例請參閱 [hooks 範例](_examples/hooks)
242+
166243
---
167244

168245
## 授權條款

_examples/hooks/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Lifecycle Hooks Example
2+
3+
This example demonstrates how to use lifecycle hooks (`WithBeforeShutdown` and `WithAfterShutdown`) to execute custom logic during graceful shutdown.
4+
5+
## Usage
6+
7+
```bash
8+
go run main.go
9+
```
10+
11+
Then send a request:
12+
13+
```bash
14+
curl http://localhost:8080/
15+
```
16+
17+
To see the hooks in action, stop the server with `Ctrl+C` (SIGTERM).
18+
19+
## Expected Output
20+
21+
When you stop the server, you should see output like:
22+
23+
```txt
24+
Shutdown signal received, starting graceful shutdown...
25+
HOOK: Before shutdown - Notifying load balancer...
26+
HOOK: Load balancer notified
27+
HOOK: Before shutdown - Stopping background workers...
28+
HOOK: Background workers stopped
29+
HOOK: After shutdown - Closing database connections...
30+
HOOK: Database connections closed
31+
HOOK: After shutdown - Flushing metrics...
32+
HOOK: Metrics flushed
33+
Server stopped gracefully
34+
```
35+
36+
## Hook Execution Order
37+
38+
1. **BeforeShutdown hooks** - Execute in registration order before server shutdown begins
39+
- Notify load balancer to stop sending traffic
40+
- Stop background workers
41+
42+
2. **Server Shutdown** - All HTTP servers shut down gracefully
43+
44+
3. **AfterShutdown hooks** - Execute in registration order after servers have stopped
45+
- Close database connections
46+
- Flush metrics to external service
47+
48+
## Use Cases
49+
50+
- **BeforeShutdown**: Deregister from service discovery, notify load balancers, stop accepting new work
51+
- **AfterShutdown**: Close database connections, flush buffers, cleanup resources, send final metrics

_examples/hooks/go.mod

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
module example
2+
3+
go 1.25.1
4+
5+
require (
6+
github.com/gin-contrib/graceful v1.1.4
7+
github.com/gin-gonic/gin v1.11.0
8+
)
9+
10+
require (
11+
github.com/bytedance/gopkg v0.1.3 // indirect
12+
github.com/bytedance/sonic v1.14.2 // indirect
13+
github.com/bytedance/sonic/loader v0.4.0 // indirect
14+
github.com/cloudwego/base64x v0.1.6 // indirect
15+
github.com/gabriel-vasile/mimetype v1.4.11 // indirect
16+
github.com/gin-contrib/sse v1.1.0 // indirect
17+
github.com/go-playground/locales v0.14.1 // indirect
18+
github.com/go-playground/universal-translator v0.18.1 // indirect
19+
github.com/go-playground/validator/v10 v10.28.0 // indirect
20+
github.com/goccy/go-json v0.10.5 // indirect
21+
github.com/goccy/go-yaml v1.19.0 // indirect
22+
github.com/json-iterator/go v1.1.12 // indirect
23+
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
24+
github.com/leodido/go-urn v1.4.0 // indirect
25+
github.com/mattn/go-isatty v0.0.20 // indirect
26+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
27+
github.com/modern-go/reflect2 v1.0.2 // indirect
28+
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
29+
github.com/quic-go/qpack v0.6.0 // indirect
30+
github.com/quic-go/quic-go v0.57.1 // indirect
31+
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
32+
github.com/ugorji/go/codec v1.3.1 // indirect
33+
golang.org/x/arch v0.23.0 // indirect
34+
golang.org/x/crypto v0.45.0 // indirect
35+
golang.org/x/net v0.47.0 // indirect
36+
golang.org/x/sync v0.18.0 // indirect
37+
golang.org/x/sys v0.38.0 // indirect
38+
golang.org/x/text v0.31.0 // indirect
39+
google.golang.org/protobuf v1.36.10 // indirect
40+
)
41+
42+
replace github.com/gin-contrib/graceful v1.1.4 => ../../

0 commit comments

Comments
 (0)