Skip to content

Adding a route is not concurrent-safe #4457

@maxsours

Description

@maxsours

Description

Running the following test with -race trips the race detector:

func TestRepro(t *testing.T) {
	r := gin.New()
	go r.Routes() // could also be server startup + send request
	r.GET("/", func(ctx *gin.Context) {
		ctx.String(200, "OK")
	})
	time.Sleep(time.Second)
}

Output:

==================
WARNING: DATA RACE
Read at 0x00c0001b94c8 by goroutine 8:
  github.com/gin-gonic/gin.(*Engine).Routes()
      /Users/maxsours/go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:381 +0x3c
  max-assess-app.TestRepro.gowrap1()
      /Users/maxsours/Documents/go/max-assess-app/main_test.go:20 +0x20

Previous write at 0x00c0001b94c8 by goroutine 7:
  github.com/gin-gonic/gin.(*Engine).addRoute()
      /Users/maxsours/go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:365 +0x22c
  github.com/gin-gonic/gin.(*RouterGroup).handle()
      /Users/maxsours/go/pkg/mod/github.com/gin-gonic/[email protected]/routergroup.go:89 +0x14c
  github.com/gin-gonic/gin.(*RouterGroup).GET()
      /Users/maxsours/go/pkg/mod/github.com/gin-gonic/[email protected]/routergroup.go:117 +0x104
  max-assess-app.TestRepro()
      /Users/maxsours/Documents/go/max-assess-app/main_test.go:21 +0x94
  testing.tRunner()
      /usr/local/go/src/testing/testing.go:1934 +0x164
  testing.(*T).Run.gowrap1()
      /usr/local/go/src/testing/testing.go:1997 +0x3c

I believe this is due to a lack of concurrent safety when accessing gin.Engine.trees. One goroutine iterates through trees while another goroutine calls appends to it. The analogous operation on http.ServeMux is concurrent-safe due to their use of a sync.RWMutex when adding/reading new route patterns:
https://cs.opensource.google/go/go/+/refs/tags/go1.25.4:src/net/http/server.go;l=2940

Gin Version

v1.11.0

Can you reproduce the bug?

Yes

Source Code

Run the following test with -race:

func TestRepro(t *testing.T) {
	r := gin.New()
	go r.Routes()
	r.GET("/", func(ctx *gin.Context) {
		ctx.String(200, "OK")
	})
	time.Sleep(time.Second)
}

Go Version

go1.25.4

Operating System

GOOS=darwin GOARCH=arm64

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/bugFound something you weren't expecting? Report it here!

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions