我正在使用 Gin Web 框架作为 Go API 后端服务。在 API 端点的 gin 处理程序函数内,我启动一个 Go 例程来运行一些后台任务。这些任务的运行应该不依赖于发送到客户端的响应。但是,在子 Go 例程中进行数据库操作时,我经常遇到“上下文已取消”错误,并且后台操作失败。
我无法创建一个新的上下文并将其作为 gin 上下文传递一些我需要的数据,并且有一些我需要重用的现有函数,这些函数需要
*gin.context
作为参数。
我做了一些调试,发现错误是由于子例程使用与父例程相同的
*gin.context
(即处理程序本身)而发生的。将响应发送到客户端后,上下文被取消,这会导致数据库操作出错。
我读到一个解决方案,说使用
context.Background()
创建一个新的上下文并将其传递。但我需要使用 *gin.context
本身,否则我将不得不重写很多函数并复制一些数据。
我看到的另一个有希望的解决方案是建议使用
*gin.context.Copy()
。在 Copy()
的杜松子酒文档中,明确指出:
Copy 返回当前上下文的副本,可以在请求范围之外安全地使用。
当上下文必须传递给 goroutine 时必须使用它。
我期待
Copy()
能够正常工作。但即使我传递了复制的上下文,我也会得到相同的上下文取消错误。
以下是我所做的:
func SendApiResponseToClient(c *gin.Context) {
response := doSomeOperations(c)
newContext := c.Copy()
go doSomeBackGroundOperations(newContext, response)
sendResponse(response)
}
doSomeBackGroundOperations()
函数有一些数据库操作,这就是失败的原因。
为了确认除上下文之外的所有其他内容都正常,我在将 API 响应发送到客户端之前添加了一秒超时,以等待后台操作完成。在这种情况下,我没有遇到任何错误,一切都按预期进行。
我还在 gin-gonic/gin 存储库中发现了一些 github 未解决的问题,如下所示:
我相信这些问题在某种程度上与我面临的问题有关。但是,我仍然找不到明确的解决方案。
任何可能对我有帮助的建议将不胜感激。 谢谢
*gin.Context
包含一个标准库 context.Context
埋在 *http.Request
中 - 这实际上是负责管理取消的。
以下代码片段可以复制 Gin 上下文,并附加背景上下文:
func detachContext(c *gin.Context) *gin.Context {
// First copy the gin.Context, this doesn't actually copy the
// underlying http.Request, but references it.
copy := c.Copy()
// As we don't want to modify the existing http.Request,
// we want to use Clone, which handily allows us to specify
// a new context.Context.
copy.Request = copy.Request.Clone(context.Background())
return copy
}
请记住:
context.WithTimeout
或类似的工具来控制它。context.Context
中的任何值都不会包含在新的 context.Context
中。这应该没问题,因为 Gin 使用它自己的 Context 类型来存储重要值。最后,说了这么多,我建议避免
*gin.Context
太深入地渗透你的代码。这是杜松子酒特有的。相反,我建议您在 HTTP 处理程序层从中提取所需的信息,然后直接将此信息传递给其他代码单元。这将减少整个项目与 Gin 的配合量。