Skip to content

Deadlock when calling GetStatusCode() in SetOnLimitReached() #106

Description

@jarv

Calling GetStatusCode() which will acquire a RLock in the function called by ExecOnLimitReached() will cause a deadlock on concurrent requests.

The deadlock is fairly easy to reproduce using the code below and a request generator like ab.

My use-case for calling GetStatusCode() in the function passed to SetOnLimitReached() is to increment a prometheus counter that had a label value for the status code being used.

code to reproduce:

package main

import (
    "fmt"
    "log"
    "net/http"
    _ "net/http/pprof"
    "runtime"

    "github.com/didip/tollbooth/v7"
)

func testHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "OK\n")
}


func main() {
    runtime.SetBlockProfileRate(1)
    runtime.SetMutexProfileFraction(5)
    listenStr := fmt.Sprintf(":%d", 6060)

    lmt := tollbooth.NewLimiter(float64(1), nil)
    log.Println("Setting rate limit for 1req/sec")
    lmt.SetIPLookups([]string{"RemoteAddr"})
    lmt.SetMessage("Your are sending requests too fast, slow down!")
    lmt.SetOnLimitReached(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Rate limit reached StatusCode: %d\n", lmt.GetStatusCode())
    })

    http.Handle("/", tollbooth.LimitFuncHandler(lmt, testHandler))

    log.Printf("Server started %s\n", listenStr)
    log.Fatal(http.ListenAndServe(listenStr, nil))
}

and the following to generate load to cause the deadlock:

ab -n 2000 -c 50 http://localhost:6060/'

Full backtrace (condensed using panicparse):

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions