Skip to content

Commit

Permalink
Add RegexpCompileFunc to override regexp.Compile
Browse files Browse the repository at this point in the history
  • Loading branch information
Tit Petric committed Sep 21, 2023
1 parent 3401478 commit 8526625
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 3 deletions.
6 changes: 5 additions & 1 deletion mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ var (
ErrMethodMismatch = errors.New("method is not allowed")
// ErrNotFound is returned when no route match is found.
ErrNotFound = errors.New("no matching route was found")
// RegexpCompileFunc aliases regexp.Compile and enables overriding it.
// Do not run this function from `init()` in importable packages.
// Changing this value is not safe for concurrent use.
RegexpCompileFunc = regexp.Compile
)

// NewRouter returns a new router instance.
Expand Down Expand Up @@ -524,7 +528,7 @@ func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
}
m := make(map[string]*regexp.Regexp, length/2)
for i := 0; i < length; i += 2 {
regex, err := regexp.Compile(pairs[i+1])
regex, err := RegexpCompileFunc(pairs[i+1])
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions regexp.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*ro

// Append variable name and compiled pattern.
varsN[i/2] = name
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
varsR[i/2], err = RegexpCompileFunc(fmt.Sprintf("^%s$", patt))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -125,7 +125,7 @@ func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*ro
reverse.WriteByte('/')
}
// Compile full regexp.
reg, errCompile := regexp.Compile(pattern.String())
reg, errCompile := RegexpCompileFunc(pattern.String())
if errCompile != nil {
return nil, errCompile
}
Expand Down
66 changes: 66 additions & 0 deletions route_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package mux

import (
"net/http"
"regexp"
"sync"
"testing"
)

var testNewRouterMu sync.Mutex
var testHandler = http.NotFoundHandler()

func BenchmarkNewRouter(b *testing.B) {
testNewRouterMu.Lock()
defer testNewRouterMu.Unlock()

// Set the RegexpCompileFunc to the default regexp.Compile.
RegexpCompileFunc = regexp.Compile

b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
testNewRouter(b, testHandler)
}
}

func BenchmarkNewRouterRegexpFunc(b *testing.B) {
testNewRouterMu.Lock()
defer testNewRouterMu.Unlock()

// We preallocate the size to 8.
cache := make(map[string]*regexp.Regexp, 8)

// Override the RegexpCompileFunc to reuse compiled expressions
// from the `cache` map. Real world caches should have eviction
// policies or some sort of approach to limit memory use.
RegexpCompileFunc = func(expr string) (*regexp.Regexp, error) {
if regex, ok := cache[expr]; ok {
return regex, nil
}

regex, err := regexp.Compile(expr)
if err != nil {
return nil, err
}

cache[expr] = regex
return regex, nil
}

b.ReportAllocs()
b.ResetTimer()

for i := 0; i < b.N; i++ {
testNewRouter(b, testHandler)
}
}

func testNewRouter(tb testing.TB, handler http.Handler) {
r := NewRouter()
// A route with a route variable:
r.Handle("/metrics/{type}", handler)
r.Queries("orgID", "{orgID:[0-9]*?}")
r.Host("{subdomain}.domain.com")
}

0 comments on commit 8526625

Please sign in to comment.