Description

I jus add a router with gin's method GET and it result in the program panic. But when I run the program again with do nothing, it works correctly. please check the below error.

[panic: runtime error: index out of range [2] with length 1 github.com/gin-gonic/gin@v1.7.1/tree.go:124

How to reproduce

package logging

import (
    "github.com/gin-gonic/gin"
)
// logging.go
func addLoggingRoute() {
// ...  
    for _, lp := range logPaths {
        logName, routerPath := lp.FileName, lp.RouterPath
        f, err := os.Open(logName)
        if err != nil {
            mylog.Ctx(context.TODO()).WithField("logName", logName).Error(err.Error())
            continue
        }
        pushPath := filepath.ToSlash(filepath.Join(routerPath, "push"))
        lc.logMap[pushPath] = f
        lc.logFileNameMap[routerPath] = logName
        lc.logFileNamePushMap[logName] = pushPath

        engine.GET(routerPath, lc.logIndex) // here panic  line number: 89
        mylog.Ctx(ctx).WithFields("method", "GET", "path", routerPath).Info("gin register router")

        engine.GET(pushPath, lc.logPush)
        mylog.Ctx(ctx).WithFields("method", "GET", "path", pushPath).Info("gin register router")

    }
}
// ...
}

Actual result

panic: runtime error: index out of range [2] with length 1

goroutine 1 [running]:
github.com/gin-gonic/gin.(*node).incrementChildPrio(0xc0003050a0, 0x2, 0x2)
    /home/appops/go/pkg/mod/github.com/gin-gonic/gin@v1.7.1/tree.go:124 +0x1b2
github.com/gin-gonic/gin.(*node).addRoute(0xc0003050a0, 0xc0001ae150, 0x22, 0xc000672008, 0x1, 0x1)
    /home/appops/go/pkg/mod/github.com/gin-gonic/gin@v1.7.1/tree.go:218 +0x445
github.com/gin-gonic/gin.(*Engine).addRoute(0xc0001b2b60, 0xf62f4e, 0x3, 0xc0001ae150, 0x22, 0xc000672008, 0x1, 0x1)
    /home/appops/go/pkg/mod/github.com/gin-gonic/gin@v1.7.1/gin.go:289 +0x12d
github.com/gin-gonic/gin.(*RouterGroup).handle(0xc0001b2b60, 0xf62f4e, 0x3, 0xc0004d3470, 0x22, 0xc000672000, 0x1, 0x1, 0x0, 0x0)
    /home/appops/go/pkg/mod/github.com/gin-gonic/gin@v1.7.1/routergroup.go:75 +0x1a7
github.com/gin-gonic/gin.(*RouterGroup).GET(...)
    /home/appops/go/pkg/mod/github.com/gin-gonic/gin@v1.7.1/routergroup.go:103
g.hz.netease.com/universe/goim/pkg/logging.RegisterGin(0xc0001b2b60, 0xc0001c6e58, 0xc0004d3410, 0x21, 0xc0004d3440, 0x26, 0x0, 0x0, 0xc0007bfc08, 0x2, ...)
    /data/heran/universe-im-logic-server/universe-im-logic-server-docker-cm_dev/pkg/logging/logging.go:89 +0xa16
g.hz.netease.com/universe/goim/pkg/logging.logInit(0xc0001b2b60, 0xf62bc3, 0x2, 0x0, 0x0)
    /data/heran/universe-im-logic-server/universe-im-logic-server-docker-cm_dev/pkg/logging/api.go:79 +0xdb0
g.hz.netease.com/universe/goim/pkg/logging.LogInitIM(...)
    /data/heran/universe-im-logic-server/universe-im-logic-server-docker-cm_dev/pkg/logging/api.go:31
main.main()
    /data/heran/universe-im-logic-server/universe-im-logic-server-docker-cm_dev/cmd/logic-server/main.go:84 +0x59f

Environment

  • go version: go1.17.5
  • gin version (or commit ref): v1.7.1
  • operating system: linux/amd64

Comment From: jincheng9

can you reproduce it?

Comment From: vito-go

can you reproduce it?

yes. I find the issue why the program will panic . I add a router with go in a goroutine, where I should do something asynchronousely with keyword go and should add router in this goroutine. When I add one router in the goroutine it just panic sometimes. But when I add many routers in the goroutine, it seem like panic is a must.

package main

import (
    "strconv"
    "time"

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

func main() {
    engine := gin.New()
    gin.SetMode(gin.ReleaseMode)
    engine.GET("/universe/api/v1/im/unilog", ginAddPanicTest)
    for i := 0; i < 100; i++ {
        go engine.GET("/universe/api/v1/im/logging/"+strconv.FormatInt(int64(i),10), ginAddPanicTest)
    }
    go engine.GET("/universe/api/v1/im/logging/tid-search", ginAddPanicTest)
    go engine.GET("/universe/api/v1/im/logging/im.log", ginAddPanicTest)
    engine.GET("/universe/api/v1/im/logging/err-im.log", ginAddPanicTest)
    time.Sleep(time.Second)
    err:=engine.Run(":8181")
    if err != nil {
        panic(err)
    }
}

func ginAddPanicTest(ctx *gin.Context)  {
    return
}


panic: runtime error: index out of range [0] with length 0

goroutine 94 [running]:
github.com/gin-gonic/gin.(*node).addRoute(0xc0003e8000, {0xc00048e360, 0x1e}, {0xc00048c078, 0x1, 0x1})
    /home/ithink/go/pkg/mod/github.com/gin-gonic/gin@v1.7.1/tree.go:180 +0xc77
github.com/gin-gonic/gin.(*Engine).addRoute(0xc000001860, {0x968ef3, 0xc000026ce0}, {0xc00048e360, 0x1e}, {0xc00048c078, 0x1, 0x1})
    /home/ithink/go/pkg/mod/github.com/gin-gonic/gin@v1.7.1/gin.go:289 +0x1e5
github.com/gin-gonic/gin.(*RouterGroup).handle(0xc000001860, {0x968ef3, 0x3}, {0xc000026ce0, 0x40e947}, {0xc00048c070, 0x1, 0x1})
    /home/ithink/go/pkg/mod/github.com/gin-gonic/gin@v1.7.1/routergroup.go:75 +0x145
github.com/gin-gonic/gin.(*RouterGroup).GET(0x0, {0xc000026ce0, 0x0}, {0xc00048c070, 0x0, 0x0})
    /home/ithink/go/pkg/mod/github.com/gin-gonic/gin@v1.7.1/routergroup.go:103 +0x3e
created by main.main
    /home/ithink/go/src/local/win3f/main.go:15 +0xbe
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)


Process finished with the exit code 2



Expectations

I hope gin support adding routers asynchronousely.

Comment From: jincheng9

  1. Usually the router should be set when program starts, so the best practice is not let multiple goroutines write a single engine, which is not concurrently safe. engine.GET is an action to write the engine's members.
  2. If you still want to use multiple goroutines to write a shared engine, you need to protect the shared engine among the goroutines. Use mutex or channel to do this.

Comment From: vito-go

  1. Usually the router should be set when program starts, so the best practice is not let multiple goroutines write a single engine, which is not concurrently safe. engine.GET is an action to write the engine's members.
  2. If you still want to use multiple goroutines to write a shared engine, you need to protect the shared engine among the goroutines. Use mutex or channel to do this.

Sometimes I want to make an distributed sevice. When the program is runing, it may receive new register and i need to add a router. In this case, it's a good idea that gin can add router asynchronousely. The standard package net/http can do this.

package main

import (
    "fmt"
    "net/http"
    "net/http/httputil"
    "sync"
)

type Server struct {
    ServeMux *http.ServeMux
}

type Nginx struct {
    mux *sync.Mutex
    rps []*httputil.ReverseProxy
}

var nginx = Nginx{} // do something to implement.

func (n *Nginx) GetProxy(appName string) *httputil.ReverseProxy {
    // to get a ReverseProxy
    return nil
}
func ReverseHttp(w http.ResponseWriter, r *http.Request) {
    // do something
    // for example, here can do as a reverse http server.
    var appName string
    // e.g. r.URL.Path=="/user/hello"
    for i := 1; i < len(r.URL.Path); i++ {
        if r.URL.Path[i] == '/' {
            appName = r.URL.Path[1:i]
            break
        }
    }
    nginx.GetProxy(appName).ServeHTTP(w, r)
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    appName := r.URL.Query().Get("appName")
    // Important!! ServeMux is support to add router asynchronously!
    s.ServeMux.HandleFunc(fmt.Sprintf("/%s/", appName), ReverseHttp)
}

func main() {
    mux := http.NewServeMux()
    s := Server{ServeMux: mux}
    mux.Handle("/add-service", &s)
    http.ListenAndServe(":9191", mux)
}