diff --git a/timeout.go b/timeout.go index 75165a9..2c869ee 100644 --- a/timeout.go +++ b/timeout.go @@ -85,9 +85,11 @@ func New(opts ...Option) gin.HandlerFunc { tw.FreeBuffer() bufPool.Put(buffer) - c.Writer = w - t.response(c) - c.Writer = tw + // Copy gin.Context here to avoid concurrent writes to c.ResponseWriter's header(which is a map), + // see https://github.com/gin-contrib/timeout/issues/15 + ctxCopy := c.Copy() + ctxCopy.Writer = w + t.response(ctxCopy) } } } diff --git a/timeout_test.go b/timeout_test.go index 63aa7bd..0647719 100644 --- a/timeout_test.go +++ b/timeout_test.go @@ -100,3 +100,26 @@ func TestPanic(t *testing.T) { assert.Equal(t, http.StatusInternalServerError, w.Code) assert.Equal(t, "", w.Body.String()) } + +func TestConcurrentHeaderWrites(t *testing.T) { + r := gin.New() + r.Use(gin.Recovery()) + r.Use(New( + WithTimeout(time.Millisecond*50), + WithHandler(func(c *gin.Context) { + c.Next() + }), + )) + r.GET("/", func(c *gin.Context) { + for { + c.Header("X-Foo", "bar") + } + }) + + w := httptest.NewRecorder() + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", nil) + if err != nil { + t.Fatal("http NewRequest: ", err) + } + r.ServeHTTP(w, req) +}