资讯详情

资讯详情

Gin 在中间件里开 goroutine 时怎么用 c.Copy 安全复用只读 Context

Gin 在中间件里开 goroutine 时怎么用 c.Copy 安全复用只读 Context【免费下载链接】ginGin is a high-performance HTTP web framework written in Go. It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to httprouter. Gin is designed for building REST APIs, web applications, and microservices.项目地址: https://gitcode.com/GitHub_Trending/gi/gin在 Gin 中间件或 handler 里发起 goroutine 做异步任务比如后台记录日志、等待长任务完成时最常见的隐患是goroutine 执行时原请求已经结束*gin.Context会被归还到 Engine 的 sync.Pool 并被下一个请求reset()复用此时再读原始 context 拿到的可能是别的路径、别的参数。官方文档 docs/doc.md 的 “Goroutines inside a middleware” 一节给出的规则很明确在中间件或 handler 里启动新 goroutine 时不应该SHOULD NOT在 goroutine 内使用原始 context必须使用一个只读副本。本文按这个规则给出可运行的示例、Copy()的实际行为边界以及仓库自带测试中的验证方式。先分清什么时候必须 Copydocs/doc.md 的示例同时给出了两种路由对照关系就是判断标准开了 goroutine、任务会晚于 handler 返回goroutine 里必须使用c.Copy()的副本同步执行、handler 内部自己time.Sleep等任务完成后才继续没有 goroutine直接使用原始c即可无需 copy。文档原文的要求是When starting new Goroutines inside a middleware or handler, youSHOULD NOTuse the original context inside it, you have to use a read-only copy.“只读”这个限定词很关键副本是用来在请求作用域之外读取请求信息路径、参数、Keys 等的不是用来替代原始 context 继续发响应的。最小可运行示例goroutine 里用副本读请求信息以下代码完整来自 docs/doc.md 的 “Goroutines inside a middleware” 示例是一个可直接保存为main.go运行的独立程序func main() { r : gin.Default() r.GET(/long_async, func(c *gin.Context) { // create copy to be used inside the goroutine cCp : c.Copy() go func() { // simulate a long task with time.Sleep(). 5 seconds time.Sleep(5 * time.Second) // note that you are using the copied context cCp, IMPORTANT log.Println(Done! in path cCp.Request.URL.Path) }() }) r.GET(/long_sync, func(c *gin.Context) { // simulate a long task with time.Sleep(). 5 seconds time.Sleep(5 * time.Second) // since we are NOT using a goroutine, we do not have to copy the context log.Println(Done! in path c.Request.URL.Path) }) // Listen and serve on 0.0.0.0:8080 r.Run(:8080) }运行后访问localhost:8080/long_async请求会立刻返回handler 只负责起一个 goroutine 就结束约 5 秒后终端打印文档示例中的日志Done! in path /long_async注意这条日志来自副本cCp而不是原始c——如果 5 秒后恰好有新请求复用了原始 context 的内存用c打出来的路径就不可信了这正是该规则要规避的问题。副本里到底带了什么、丢了什么Copy()的定义在 context.go注释写明其用途“Copy returns a copy of the current context that can be safely used outside the requests scope. This has to be used when the context has to be passed to a goroutine.” 从实现可以看到它做了这些事项目副本中的状态对异步任务的影响Request与原 context 同一个指针可安全读取 URL、Header 等请求信息fullPath原样复制路由模板路径可用Keys通过maps.Clone复制加mu.RLock读锁中间件里c.Set写入的 key 在 goroutine 里可读Params重新分配切片后拷贝第二个请求复用的参数不会串到副本里Errors/Accepted非 nil 时重新分配切片后拷贝修改副本不影响原始 contexthandlers/index置为nil/abortIndex副本已脱离 handler 链writermem.ResponseWriter置为nil副本的 ResponseWriter 内部没有真实 writer最后一行是最主要的边界副本的Writer指向一个内部ResponseWriter为nil的responseWriter见 response_writer.go。所以 goroutine 里应该用cCp读路径、参数、Keys 并写自己的日志而不要指望通过副本给客户端发响应——响应只能由原始 context 在请求生命周期内写出。用仓库自带测试验证行为如果想在本地直接核对Copy()的语义仓库里已有对应的测试无需自己造场景# 在 gin 仓库根目录执行 go test -race -run TestRaceContextCopy . go test -race -run TestRaceParamsContextCopy .githubapi_test.go 的TestRaceContextCopyhandler 里c.Set两个 key 后把两个c.Copy()副本分别发给两个 goroutine 做读写handler 立即返回run OK, no panics测试断言响应体即为该字符串且-race下无数据竞争报告。context_test.go 的TestRaceParamsContextCopy对/:name/api连发两个不同路径参数的请求每个 handler 在 goroutine 里用c.Copy()的副本读取c.Param(name)断言 goroutine 读到的参数与发起请求时一致name1请求的副本不会因为第二个name2请求而被覆盖——这正是“参数被后续请求复用污染”场景的回归测试。context_test.go 的TestContextCopy、TestContextCopyCopiesErrors、TestContextCopyCopiesAccepted逐项断言了上表中的拷贝与隔离行为cp.handlers为 nil、cp.index为abortIndex、修改副本的 Keys/Errors/Accepted 不影响原始 context 等。另外 Makefile 提供了整库测试入口make test会遍历 gin、ginS、binding、render 等包执行go test -v -covermodecount并以--- FAIL判定失败跑单个用例时按上面的go test -run方式更直接。限制与边界Copy()必须在请求仍在处理时调用即在 handler/中间件内goroutine 内部拿到的是快照它不是用来“复活”已结束请求的完整上下文。副本上handlers为 nil、index为abortIndex不要对副本调用依赖 handler 链的方法如Next、Abort来完成业务。副本的 ResponseWriter 内部 writer 为 nilcontext_test.go 明确断言cp.writermem.ResponseWriter为 nil异步逻辑里发响应、写 body 不是该副本的职责。文档给出的约束范围是“goroutine 内使用只读副本”对同步长任务不开 goroutine文档明确不需要 copy不要把 copy 套在同步路径上。参考资料docs/doc.mdGoroutines inside a middleware 一节、context.goCopy实现、githubapi_test.go 与 context_test.go行为验证。【免费下载链接】ginGin is a high-performance HTTP web framework written in Go. It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to httprouter. Gin is designed for building REST APIs, web applications, and microservices.项目地址: https://gitcode.com/GitHub_Trending/gi/gin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →