Skip to content

Commit 41d869d

Browse files
authored
Merge pull request #5 from dolthub/fulghum/regex
Add `RegexLeakHandler` to allow callers to supply a custom leak handler
2 parents 549b8d7 + 451ea8d commit 41d869d

File tree

2 files changed

+59
-3
lines changed

2 files changed

+59
-3
lines changed

regex.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"fmt"
2020
"runtime"
21+
"sync/atomic"
2122
"unicode/utf16"
2223

2324
"github.com/tetratelabs/wazero/api"
@@ -63,7 +64,28 @@ var (
6364
)
6465

6566
// ShouldPanic determines whether the finalizer will panic if it finds a Regex that has not been closed.
66-
var ShouldPanic bool = true
67+
var ShouldPanic = true
68+
69+
// RegexLeakHandler is a callback function that will be invoked if a finalizer is called for a Regex that has not
70+
// been closed. This allows applications to provide custom handling logic beyond panicing if a Regex leak is detected.
71+
var regexLeakHandler atomic.Value
72+
73+
// SetRegexLeakHandler sets a |handler| function to be executed if a finalizer is called for a Regex instance that
74+
// has not been properly closed through it's Close() method. Note that if a custom leak handler function is provided,
75+
// the ShouldPanic variable will be ignored and the code will not panic if a Regex leak is detected.
76+
func SetRegexLeakHandler(handler func()) {
77+
regexLeakHandler.Store(handler)
78+
}
79+
80+
// getRegexLeakHandler returns the custom handler function set by SetRegexLeakHandler, or nil if no custom handler has
81+
// been set.
82+
func getRegexLeakHandler() func() {
83+
val := regexLeakHandler.Load()
84+
if val == nil {
85+
return nil
86+
}
87+
return val.(func())
88+
}
6789

6890
// RegexFlags are flags to define the behavior of the regular expression. Use OR (|) to combine flags. All flag values
6991
// were taken directly from ICU.
@@ -171,8 +193,13 @@ func CreateRegex(stringBufferInBytes uint32) Regex {
171193
// by GC, this finalizer ensures that regexes are being used as efficiently as possible by maximizing pool rotations.
172194
// Hopefully, this would be caught during development and not in production.
173195
runtime.SetFinalizer(pr, func(pr *privateRegex) {
174-
if pr.mod != nil && ShouldPanic {
175-
panic("Finalizer found a Regex that was never closed")
196+
if pr.mod != nil {
197+
if leakHandler := getRegexLeakHandler(); leakHandler != nil {
198+
leakHandler()
199+
} else if ShouldPanic {
200+
panic("Finalizer found a Regex that was never closed")
201+
}
202+
_ = pr.Close()
176203
}
177204
})
178205
return pr

regex_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ package regex
1616

1717
import (
1818
"context"
19+
"runtime"
20+
"sync/atomic"
1921
"testing"
22+
"time"
2023

2124
"github.com/stretchr/testify/require"
2225
)
@@ -187,3 +190,29 @@ func TestRegexSubstring(t *testing.T) {
187190
require.Equal(t, "ghx", substr)
188191
require.NoError(t, regex.Close())
189192
}
193+
194+
// TestRegexLeakHandler asserts that RegexLeakHandler is invoked when a regex is not closed before
195+
// it gets garbage collected.
196+
func TestRegexLeakHandler(t *testing.T) {
197+
leakHandlerCalled := atomic.Bool{}
198+
199+
SetRegexLeakHandler(func() {
200+
leakHandlerCalled.Store(true)
201+
})
202+
defer func() {
203+
SetRegexLeakHandler(nil)
204+
}()
205+
206+
regex := CreateRegex(1024)
207+
regex.Close()
208+
regex = nil
209+
runtime.GC()
210+
time.Sleep(500 * time.Millisecond)
211+
require.False(t, leakHandlerCalled.Load())
212+
213+
regex = CreateRegex(1024)
214+
regex = nil
215+
runtime.GC()
216+
time.Sleep(500 * time.Millisecond)
217+
require.True(t, leakHandlerCalled.Load())
218+
}

0 commit comments

Comments
 (0)