Skip to content

Commit f2c10d0

Browse files
authored
fix: memory leak in jwks (#425)
1 parent ee538d6 commit f2c10d0

File tree

4 files changed

+73
-7
lines changed

4 files changed

+73
-7
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [unreleased]
99

10-
## [0.24.1] - 2024-09-07
10+
## [0.24.2] - 2024-09-03
11+
12+
- Fixes memory leak with the JWKS cache.
13+
14+
## [0.24.1] - 2024-08-07
1115

1216
- Improves debug logs for error handlers.
1317

recipe/session/jwksMemory_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package session
2+
3+
import (
4+
"runtime"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/supertokens/supertokens-golang/recipe/session/sessmodels"
10+
"github.com/supertokens/supertokens-golang/supertokens"
11+
"github.com/supertokens/supertokens-golang/test/unittesting"
12+
)
13+
14+
func TestThatThereIsNoMemoryLeakWithJWKSCache(t *testing.T) {
15+
configValue := supertokens.TypeInput{
16+
Supertokens: &supertokens.ConnectionInfo{
17+
ConnectionURI: "http://localhost:8080",
18+
},
19+
AppInfo: supertokens.AppInfo{
20+
AppName: "SuperTokens",
21+
WebsiteDomain: "supertokens.io",
22+
APIDomain: "api.supertokens.io",
23+
},
24+
RecipeList: []supertokens.Recipe{
25+
Init(&sessmodels.TypeInput{
26+
JWKSRefreshIntervalSec: &[]uint64{0}[0],
27+
}),
28+
},
29+
}
30+
BeforeEach()
31+
unittesting.StartUpST("localhost", "8080")
32+
defer AfterEach()
33+
err := supertokens.Init(configValue)
34+
if err != nil {
35+
t.Error(err.Error())
36+
}
37+
38+
testServer := GetTestServer(t)
39+
defer func() {
40+
testServer.Close()
41+
}()
42+
43+
sess, err := CreateNewSessionWithoutRequestResponse("public", "testuser", map[string]interface{}{}, map[string]interface{}{}, nil)
44+
assert.NoError(t, err)
45+
46+
accessToken := sess.GetAccessToken()
47+
48+
_, err = GetSessionWithoutRequestResponse(accessToken, nil, nil)
49+
assert.NoError(t, err)
50+
51+
numGoroutinesBeforeJWKSRefresh := runtime.NumGoroutine()
52+
53+
for i := 0; i < 100; i++ {
54+
_, err = GetSessionWithoutRequestResponse(accessToken, nil, nil)
55+
assert.NoError(t, err)
56+
57+
time.Sleep(10 * time.Millisecond)
58+
}
59+
60+
time.Sleep(1 * time.Second)
61+
assert.Equal(t, numGoroutinesBeforeJWKSRefresh, runtime.NumGoroutine())
62+
}

recipe/session/recipeImplementation.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,11 @@ func getJWKS() (*keyfunc.JWKS, error) {
105105
LastFetched: time.Now().UnixNano() / int64(time.Millisecond),
106106
}
107107

108-
// Dont add to cache if there is an error to keep the logic of checking cache simple
109-
//
110-
// This also has the added benefit where if initially the request failed because the core
111-
// was down and then it comes back up, the next time it will try to request that core again
112-
// after the cache has expired
108+
// Close any existing JWKS in the cache before replacing it
109+
if jwksCache != nil && jwksCache.JWKS != nil {
110+
jwksCache.JWKS.EndBackground()
111+
}
112+
113113
jwksCache = &jwksResult
114114

115115
if supertokens.IsRunningInTestMode() {

supertokens/constants.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const (
2121
)
2222

2323
// VERSION current version of the lib
24-
const VERSION = "0.24.1"
24+
const VERSION = "0.24.2"
2525

2626
var (
2727
cdiSupported = []string{"3.1"}

0 commit comments

Comments
 (0)