[gin-gonic/gin]gin context 当函数返回后 goroutine 中context的值可能受到影响

2023-12-11 730 views
7
  • 有问题:
    • gin context 当函数返回后 goroutine 中context的值可能受到影响
描述

1、访问http://127.0.0.1:8080/test/test?a=1 2、连续访问http://127.0.0.1:8080/test/test?a=2 3、正常输出: 1 +++ + 1 ++++ 1 ---------------------- 1 2 ++++ 2 ++++ 2 --------- ------------- 2 2 ========== 2 =============== 1 ======== == 1 =============== //正常 2 ++++ 2 ++++ 2 ------------------ ---- 2 2 ========== 2 =============== 异常输出: 1 ++++ 1 ++++ 1 ---- ------------------ 1 2 ++++ 2 ++++ 2 -------------------- -- 2 2 ========== 2 =============== 2 ++++ 2 ++++ 2 --------- ------------- 2 2 ========== 2 =============== 1 ======== == 2 =============== //异常http://127.0.0.1:8080/test/test?a=1结果返回从上下文中取出a=2 2 ++ ++ 2 ++++ 2 ---------------------- 2 2 ========== 2 ======= ========

如何重现

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "time"
)

func main() {

    gin.SetMode("debug")
    r := gin.New()
    r.Use(func(ctx *gin.Context) {
        ctx.Request = ctx.Request.WithContext(ctx)
        ctx.Set("a", ctx.Query("a"))
        value, _ := ctx.Get("a")
        fmt.Println(ctx.Query("a"), "++++", value, "++++")
    })
    group := r.Group("/test")
    group.GET("/test", func(ctx *gin.Context) {

        go func(c *gin.Context) {
                       //c := &gin.Context{} //重新定以后恢复正常
              //*c = *ctx 
            a := c.Query("a")
            fmt.Println(a, "----------------------", c.Value("a"))
            if a=="1"{
                time.Sleep(3*time.Second)
            }
            fmt.Println(a, "==========", c.Value("a"), "===============") //此处输出可能异常
        }(ctx)
              //主函数不退出正常
             //time.Sleep
    })
    url := "0.0.0.0:8080"
    r.Run(url)
}

go 1.17.3 windows   gin 1.8.1

回答

7

我认为这个问题的原因是你gin.Context在处理程序返回后仍在使用。

在 gin 的实现中,当处理程序返回时,它会将 放入gin.Context池中以供重用,并且很可能接下来当您请求 时http://127.0.0.1:8080/test/test\?a\=2,它会重用上下文,然后将新值分配给上下文。因此,3秒后,处理程序中的 goroutine 醒来并从上下文中获取值将获得新值(“2”)而不是旧值(“1”)。

所以会有两种解决方案:

  1. 处理程序返回后,不要在正在运行的 goroutine 中使用原始上下文
  2. 就像你的注释代码一样,你可以分配一个新的上下文来存储原始上下文中的数据,这样 gin 重用原始上下文就不会影响你正在运行的 goroutine。
7

你可以尝试打印出代码中上下文的指针地址,你会发现发生了什么,我只是改变你的代码的打印函数:

package main

import (
    "fmt"
    "github.com/gin-gonic/gin"
    "strings"
    "time"
)

func main() {
    gin.SetMode("debug")
    r := gin.New()
    r.Use(func(ctx *gin.Context) {
        ctx.Request = ctx.Request.WithContext(ctx)
        ctx.Set("a", ctx.Query("a"))
        value, _ := ctx.Get("a")
        fmt.Printf("%s,%p,%s,%s\n", strings.Repeat("+", 10), &ctx, value, strings.Repeat("+", 5))
    })
    group := r.Group("/test")
    group.GET("/test", func(ctx *gin.Context) {

        go func(c *gin.Context) {
            //c := &gin.Context{} //重新定以后恢复正常
            //*c = *ctx
            a := c.Query("a")
            fmt.Println(a, "----------------------", c.Value("a"))
            if a == "1" {
                time.Sleep(3 * time.Second)
            }
            fmt.Printf("%s,%p,%s,%s\n", strings.Repeat("=", 10), &c, c.Value("a"), strings.Repeat("=", 10)) //此处输出可能异常
        }(ctx)
        //主函数不退出正常
        //time.Sleep
    })
    url := "0.0.0.0:8080"
    r.Run(url)
}