使用Gin Request Context可能遇到的問題(附解法)

撰文原因 問題程式碼(範例) 解法 1. 暫時先傳 context.TODO() 2. 使用 context.WithoutCancel 3. 不要用go關鍵字 結語 參考資料 撰文原因 最近工作遇到服務使用的新方法需要傳遞context到函數中執行, 結果導致本來要跑在背景任務沒完成(ex: 通知訊息/發送郵件)。 問題程式碼(範例) 事情是因為傳入的是 c.Request.Context(),然而這個gin所實作的Context在回應HTTP Response結束時會自動取消, 導致引用ctx的do函數在Context連帶著被取消後內部的函數也跟著取消。 程式碼範例如下: package main import ( "context" "fmt" "net/http" "time" "github.com/gin-gonic/gin" ) func main() { // Create a Gin router with default middleware (logger and recovery) r := gin.Default() // Define a simple GET endpoint r.GET("/ping", func(c *gin.Context) { go doWork(c.Request.Context()) time.Sleep(2 * time.Second) // Return JSON response c.JSON(http.StatusOK, gin.H{ "message": "pong", }) }) r.Run("127.0.0.1:8080") } func doWork(ctx context.Context) { defer ctx.Done() elapsed := 0 * time.Millisecond for { select { case <-ctx.Done(): // Check if the context is done (cancelled or timed out) fmt.Printf("worker stopped err: %v\n", ctx.Err()) return default: secs := 500 * time.Millisecond fmt.Printf("working... %v passed\n", elapsed) time.Sleep(secs) elapsed += secs } if elapsed >= 5*time.Second { fmt.Println("worker completed") break } } } 實際跑起來會發現doWork只是每0.5秒印出一段訊息,原本預期最多跑5秒就會結束, 然而實際上2秒就會因為gin response被標記為完成自動結束, 這邊只是粗略模擬尊重context機制的函式庫的行為(qmgo…), 當select捕捉到<-ctx.Done()會停止或取消後續的行為。 ...

October 5, 2025 · 1 min · 宗嘉