gin icon indicating copy to clipboard operation
gin copied to clipboard

gin/context Copy method comment is confuse

Open fengbenming opened this issue 2 years ago • 6 comments

  • With issues:
    • gin/context Copy method comment is confuse. It'll be invalid memory address or nil pointer dereference when context copyed be passed to a goroutine.

Description

// Copy returns a copy of the current context that can be safely used outside the request's scope. // This has to be used when the context has to be passed to a goroutine. func (c *Context) Copy() *Context { cp := Context{ writermem: c.writermem, Request: c.Request, Params: c.Params, engine: c.engine, } ...

the comment say can be safely used outside the request's scope, but it is wrong, it just in the request's scope.

How to reproduce

func CreateVulJob(c *gin.Context) {
	env := c.Param("env")
	if env == "aliyun" {
		go doSomething(c.Copy())
	} else{
	       go doOtherSomething(c.Copy())
	}
}

Expectations

not panic

Actual result

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x931ae4]

goroutine 100 [running]:
github.com/gin-gonic/gin.(*responseWriter).Header(0x30?)
        <autogenerated>:1 +0x24
github.com/gin-gonic/gin/render.writeContentType({0x7fe3541d1f58?, 0xc0003c5300?}, {0x24d3b40, 0x1, 0x1})
        /go/pkg/mod/github.com/gin-gonic/[email protected]/render/render.go:36 +0x3a
github.com/gin-gonic/gin/render.WriteJSON({0x7fe3541d1f58, 0xc0003c5300}, {0xd0ae40, 0xc0002865f0})
        /go/pkg/mod/github.com/gin-gonic/[email protected]/render/json.go:68 +0x4c
github.com/gin-gonic/gin/render.JSON.Render(...)
        /go/pkg/mod/github.com/gin-gonic/[email protected]/render/json.go:55
github.com/gin-gonic/gin.(*Context).Render(0xc0003c5300, 0x190, {0xebec20, 0xc0004241b0})
        /go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:927 +0xf8
github.com/gin-gonic/gin.(*Context).JSON(...)
        /go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:970

Environment

  • go version: go1.18.1 linux/amd64
  • gin version (or commit ref):
  • operating system: centos 7.0

fengbenming avatar May 05 '22 01:05 fengbenming

Panic because c.Writer.ResponseWriter was nil

package main

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

func CreateVulJob(c *gin.Context) {
	env := c.Param("env")
	if env == "aliyun" {
		go doSomething(c)
	} else {
		go doOtherSomething(c)
	}
}

func doSomething(c *gin.Context) {
	c.JSON(200, `{"message": "doSomething"}`)
}

func doOtherSomething(c *gin.Context) {
	// panic because c.Writer.ResponseWriter was nil
	c.JSON(200, `{"message": "doOtherSomething"}`)
}

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		CreateVulJob(c.Copy())
		// c.JSON(200, `{"message": "ok"}`)
	})
	r.Run(":18080")
}

cp.Writer just get its pointer

// Copy returns a copy of the current context that can be safely used outside the request's scope.
// This has to be used when the context has to be passed to a goroutine.
func (c *Context) Copy() *Context {
	cp := Context{
		writermem: c.writermem,
		Request:   c.Request,
		Params:    c.Params,
		engine:    c.engine,
	}
	cp.writermem.ResponseWriter = nil
	cp.Writer = &cp.writermem
	cp.index = abortIndex
	cp.handlers = nil
	cp.Keys = map[string]interface{}{}
	for k, v := range c.Keys {
		cp.Keys[k] = v
	}
	paramCopy := make([]Param, len(cp.Params))
	copy(paramCopy, cp.Params)
	cp.Params = paramCopy
	return &cp
}

capric8416 avatar May 05 '22 10:05 capric8416

I'm getting a similar issue when using Copy(). Do you maybe know if there's any workaround available? I'd like to use the context in a goroutine.

andfasano avatar Jul 17 '22 15:07 andfasano

documentation regarding using go routines as middleware states that "you have to use a read-only copy" of the context. maybe this is why the response writer is nil and is therefore producing the panic

amirrezapanahi avatar Dec 14 '22 00:12 amirrezapanahi

Facing same issue, anyone find anything for the workaround of this?

shubh-gupta00 avatar Mar 13 '23 15:03 shubh-gupta00

Facing same issue, anyone find anything for the workaround of this?

It designed just data copy, not the stream copy. Because if the write stream handle can copy, then the program can't decide when close the stream, so it may cause goroutine leak.
If you want to implent the feature,just pass the gin.Context itself, not the copy, and waiting timeout in parent handle function. code like below, wish i helped you:

package main

import (
	"fmt"
	"time"

	"github.com/gin-gonic/gin"
)

func CreateVulJob(c *gin.Context) {
	env := c.Param("env")
	if env == "aliyun" {
		go doSomething(c)
	} else {
		go doOtherSomething(c)
	}

	// prevent close the stream in main handle
	a := time.Tick(time.Second * 3)
	<-a
	fmt.Println("hello world")
}

func doSomething(c *gin.Context) {
	c.JSON(200, `{"message": "doSomething"}`)
}

func doOtherSomething(c *gin.Context) {
	// panic because c.Writer.ResponseWriter was nil
	c.JSON(200, `{"message": "doOtherSomething"}`)
}

func main() {
	r := gin.Default()
	// r.GET("/", func(c *gin.Context) {
	// 	CreateVulJob(c.Copy())
	// 	// c.JSON(200, `{"message": "ok"}`)
	// })
	r.GET("/", CreateVulJob)
	r.Run(":18080")
}

fengbenming avatar Mar 14 '23 07:03 fengbenming

Thanks a lot @fengbenming for the help. I'm still a bit new to Go & Gin so learning the ropes. I now understand how ResponseWriter is working in gin, but I still am not able to come with a solution in my use case. Basically, I'm internally making a call to another api to fetch some data by passing in ginContext and some other values. I just want that, if for some reason the internal api call was not successful, my parent context does not throw error, and it just skips whatever the error the internal api has given.

func fetchReportEntityData(c *gin.Context, report interface{}, userId string) interface{} {
	copyContext := c.copy()  // Trying to make a copy context so that it does not affect my original context
	post_data := feed.GetPostInternal(copyContext, userId) // Internally making a call to another api, which takes ginContext and other values as params
		if post_data != nil {
		   return post_data
		} else {
			return nil
		}
}

Using a copy context, throws panic in the GetPostInternal function on a "c.JSON()" binding, as a copy context's writer is NIL. And if I pass my original context, it gets updated whatever the error GetPostInternal throws.

shubh-gupta00 avatar Mar 14 '23 08:03 shubh-gupta00