Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gin/context Copy method comment is confuse #3137

Open
fengbenming opened this issue May 5, 2022 · 6 comments
Open

gin/context Copy method comment is confuse #3137

fengbenming opened this issue May 5, 2022 · 6 comments

Comments

@fengbenming
Copy link

fengbenming commented May 5, 2022

  • 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/gin@v1.7.7/render/render.go:36 +0x3a
github.com/gin-gonic/gin/render.WriteJSON({0x7fe3541d1f58, 0xc0003c5300}, {0xd0ae40, 0xc0002865f0})
        /go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/render/json.go:68 +0x4c
github.com/gin-gonic/gin/render.JSON.Render(...)
        /go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/render/json.go:55
github.com/gin-gonic/gin.(*Context).Render(0xc0003c5300, 0x190, {0xebec20, 0xc0004241b0})
        /go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:927 +0xf8
github.com/gin-gonic/gin.(*Context).JSON(...)
        /go/pkg/mod/github.com/gin-gonic/gin@v1.7.7/context.go:970

Environment

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

capric8416 commented May 5, 2022

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
}

@andfasano
Copy link

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.

@amirrezapanahi
Copy link

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

@shubh-gupta00
Copy link

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

@fengbenming
Copy link
Author

fengbenming commented Mar 14, 2023

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")
}

@shubh-gupta00
Copy link

shubh-gupta00 commented Mar 14, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants