Fix concurrent-safe route registration

Add sync.RWMutex to protect concurrent access to the trees slice
in Engine struct. This fixes a data race between addRoute() and
Routes() functions where one goroutine appends to trees while
another reads from it.

Changes:
- Add mu sync.RWMutex field to Engine struct
- Protect addRoute() with Lock/Unlock
- Protect Routes() with RLock/RUnlock

Fixes #4457
This commit is contained in:
mehrdadbn9 2026-02-05 21:55:28 +03:30
parent d7776de7d4
commit 808ea1897b

9
gin.go
View File

@ -96,6 +96,10 @@ type Engine struct {
// (used for routing HTTP requests) happens only once, even if called multiple times concurrently. // (used for routing HTTP requests) happens only once, even if called multiple times concurrently.
routeTreesUpdated sync.Once routeTreesUpdated sync.Once
// mu protects concurrent access to trees
mu sync.RWMutex
// RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a // RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
// handler for the path with (without) the trailing slash exists. // handler for the path with (without) the trailing slash exists.
// For example if /foo/ is requested but a route only exists for /foo, the // For example if /foo/ is requested but a route only exists for /foo, the
@ -368,6 +372,9 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
debugPrintRoute(method, path, handlers) debugPrintRoute(method, path, handlers)
engine.mu.Lock()
defer engine.mu.Unlock()
root := engine.trees.get(method) root := engine.trees.get(method)
if root == nil { if root == nil {
root = new(node) root = new(node)
@ -388,6 +395,8 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
// Routes returns a slice of registered routes, including some useful information, such as: // Routes returns a slice of registered routes, including some useful information, such as:
// the http method, path, and the handler name. // the http method, path, and the handler name.
func (engine *Engine) Routes() (routes RoutesInfo) { func (engine *Engine) Routes() (routes RoutesInfo) {
engine.mu.RLock()
defer engine.mu.RUnlock()
for _, tree := range engine.trees { for _, tree := range engine.trees {
routes = iterate("", tree.method, routes, tree.root) routes = iterate("", tree.method, routes, tree.root)
} }