我正在使用Gin网络框架来提供Go API后台服务.在API端点的gin处理程序函数中,我正在启动Go routine 来运行一些后台任务.这些任务应该运行而不依赖于发送到客户端的响应.但是,我经常在子Go routine 中的数据库操作过程中遇到"上下文取消"错误,并且后台操作失败.

我无法创建一个新的上下文并将其作为gin上下文传递我需要的一些数据以及我需要重用的一些现有函数,预计有*gin.context个参数.

我进行了一些调试,发现错误是由于子 routine 使用与父 routine (即处理程序本身)相同的*gin.context而发生的.向客户端发送响应后,上下文被取消,从而导致数据库操作中出现错误.

我读到一个解决方案,其中说使用context.Background()创建一个新的上下文并传递它.但我需要使用*gin.context本身,否则我将不得不重写很多函数并复制一些数据.

我看到的另一个有希望的解决方案是建议使用*gin.context.Copy().在Copy()的杜松子wine 文档中,它明确写道:

Copy返回当前上下文的副本,该副本可以在请求范围之外安全使用.
当上下文必须传递给goroutine时,必须使用这一点.

我原以为Copy()个能正常工作.但即使我传递了复制的上下文,我也会得到相同的上下文取消错误.

以下是我所做的:

func SendApiResponseToClient(c *gin.Context) {
  response := doSomeOperations(c)
  
  newContext := c.Copy()
  go doSomeBackGroundOperations(newContext, response)

  sendResponse(response)
}

doSomeBackGroundOperations()个函数有一些DB操作,这就是失败的原因.

为了确认上下文以外的所有其他内容都正常,我在向客户端发送API响应以等待后台操作完成之前添加了一秒的超时.在这种情况下,我没有遇到任何错误,一切都按预期进行.

我还在gin-gonia/gin存储库中发现了一些github开放问题,如下所示:

我相信这些问题在某种程度上与我面临的问题有关.但是,我仍然找不到明确的解决方案.

任何可能对我有帮助的建议都会非常感激. 谢谢

推荐答案

*gin.Context是一种具有名为Request的字段的类型.该Request字段属于类型*http.Request,并且包含标准库context.Context.正是这个context.Context负责通过您的代码传播取消.

因此,如果我们想改变这些取消的传播方式,我们需要修改此context.Context.

以下代码片段将用于复制杜松子wine 上下文,并附加背景上下文:

func detachContext(c *gin.Context) *gin.Context {
  // First call .Copy on the gin.Context. This doesn't behave quite as
  // we'd originally expect, and references the c.Request rather than
  // actually copying it. Hence, we'll need to follow up with a proper
  // clone of the underlying Request.
  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.
  // We use context.WithoutCancel to take a copy of the values of
  // a context without inheriting it's cancellation propagation.
  copy.Request = copy.Request.Clone(context.WithoutCancel(copy.Request.Context))
  return copy
}

请记住,新创建的上下文没有取消,这可能会导致围棋 routine 的积累.您可能想使用context.WithTimeout或类似值来控制子元素Goroutine的生命周期.

最后,既然说了这么多,我建议避免*gin.Context太深入地渗透您的代码.这是杜松子wine 特有的.相反,我建议您在HTTP处理程序层从其中提取所需的信息,然后将此信息直接传递给其他代码单元.这将减少整个项目与杜松子wine 的结合量.

Go相关问答推荐

Go 1.22 net/http群组路由

如何使用Docker Compose配置Go,使main. go文件位于/CMD文件夹中

正在使用terratest执行terraform脚本测试,但遇到错误退出状态1

golang.org/x/oauth2 oauth2.Config.Endpoint.TokenURL mock:缺少access_token

如何在S汇编器中更高效地将全局数据加载到霓虹灯寄存器?

使用Golang的Lambda自定义al2运行时,初始化阶段超时

go-chi: 接受带有反斜杠的 url 路径参数

死锁 - 所有 goroutine 都处于睡眠状态(即使使用等待组)

GORM中是否可能自动迁移具有循环关系的表?

Go time.Parse 无效的 ISO 日期

Global Thread-local Storage 在 Go 中的可行性和最佳实践

在 Go 中使用 Apache Arrow 按时间间隔对事件进行分区

用于提取 <*n 的正则表达式(其中 n 是一个数字)

go - 仅在函数即将返回错误时清理资源

获取不带类型参数的泛型 struct 的类型名称

我相信我正确地在 sRGB 和线性 RGB 之间进行了转换,那么为什么深色的结果看起来更糟呢?

函数超时和 goroutine 泄漏

Go 并发、goroutine 同步和关闭通道

如何在循环中旋转图像以便在 golang 中创建 GIF?

(如何)我可以基于接口抽象地实现Stringer吗?