Skip to content

Commit 8a4b7e1

Browse files
authored
fix(manager): optimize durationToRenewal (#6)
* fix(manager): optimize token durationToRenewal calculation I observed the durationToRenewal was significantly slower than before due to changes done to cover the unnecessary precision. Using Milliseconds as the TTL is sufficient and significantly faster. * chore(example): update deps in example * chore(tests): restore TestConcurrentTokenManagerOperations * fix(manager): use floating point for refresh ratio * refactor(tests): use time.Sleep when possible copilot thinks time.After can cause flakiness * fix(manager): use integer math with higher precision * fix(manager): precision as variable, fix typo * test(manager): test durationToRenewal calculation * test(manager): add 100% coverage on GetToken * fix(manager): skip racy test and address comments * actually, the defer will be executed even if the function panics * chore(manager): improve comments
1 parent 50c261b commit 8a4b7e1

15 files changed

+849
-128
lines changed

credentials_provider_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func TestCredentialsProviderWithMockIdentityProvider(t *testing.T) {
117117
"test-token",
118118
time.Now().Add(time.Hour),
119119
time.Now(),
120-
int64(time.Hour),
120+
time.Hour.Milliseconds(),
121121
),
122122
}
123123

@@ -159,7 +159,7 @@ func TestCredentialsProviderWithMockIdentityProvider(t *testing.T) {
159159
"test-token",
160160
time.Now().Add(time.Hour),
161161
time.Now(),
162-
int64(time.Hour),
162+
time.Hour.Milliseconds(),
163163
),
164164
}
165165

@@ -219,7 +219,7 @@ func TestCredentialsProviderWithMockIdentityProvider(t *testing.T) {
219219
"initial-token",
220220
time.Now().Add(time.Hour),
221221
time.Now(),
222-
int64(time.Hour),
222+
time.Hour.Milliseconds(),
223223
),
224224
}
225225

@@ -253,7 +253,7 @@ func TestCredentialsProviderWithMockIdentityProvider(t *testing.T) {
253253
"updated-token",
254254
time.Now().Add(time.Hour),
255255
time.Now(),
256-
int64(time.Hour),
256+
time.Hour.Milliseconds(),
257257
)
258258
tm.lock.Unlock()
259259

@@ -329,14 +329,14 @@ func TestCredentialsProviderSubscribe(t *testing.T) {
329329
rawTokenString,
330330
time.Now().Add(tokenExpiration),
331331
time.Now(),
332-
int64(tokenExpiration),
332+
tokenExpiration.Milliseconds(),
333333
)
334334

335335
listener := &mockCredentialsListener{
336336
LastTokenCh: make(chan string, 1),
337337
LastErrCh: make(chan error, 1),
338338
}
339-
mtm := &mockTokenManager{done: make(chan struct{})}
339+
mtm := &mockTokenManager{done: make(chan struct{}), lock: &sync.Mutex{}}
340340
// Set the token manager factory in the options
341341
options := opts
342342
options.tokenManagerFactory = mockTokenManagerFactory(mtm)
@@ -386,9 +386,9 @@ func TestCredentialsProviderSubscribe(t *testing.T) {
386386
rawTokenString,
387387
time.Now().Add(tokenExpiration),
388388
time.Now(),
389-
int64(tokenExpiration),
389+
tokenExpiration.Milliseconds(),
390390
)
391-
mtm := &mockTokenManager{done: make(chan struct{})}
391+
mtm := &mockTokenManager{done: make(chan struct{}), lock: &sync.Mutex{}}
392392
// Set the token manager factory in the options
393393
options := opts
394394
options.tokenManagerFactory = mockTokenManagerFactory(mtm)
@@ -459,7 +459,7 @@ func TestCredentialsProviderSubscribe(t *testing.T) {
459459

460460
t.Run("concurrent subscribe and get token error ", func(t *testing.T) {
461461
t.Parallel()
462-
mtm := &mockTokenManager{done: make(chan struct{})}
462+
mtm := &mockTokenManager{done: make(chan struct{}), lock: &sync.Mutex{}}
463463
// Set the token manager factory in the options
464464
options := opts
465465
options.tokenManagerFactory = mockTokenManagerFactory(mtm)
@@ -514,7 +514,7 @@ func TestCredentialsProviderSubscribe(t *testing.T) {
514514
rawTokenString,
515515
time.Now().Add(tokenExpiration),
516516
time.Now(),
517-
int64(tokenExpiration),
517+
tokenExpiration.Milliseconds(),
518518
)
519519
// Set the token manager factory in the options
520520
options := opts

entraid_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (m *fakeTokenManager) GetToken(forceRefresh bool) (*token.Token, error) {
4848
rawTokenString,
4949
time.Now().Add(tokenExpiration),
5050
time.Now(),
51-
int64(tokenExpiration.Seconds()),
51+
tokenExpiration.Milliseconds(),
5252
)
5353
}
5454
return m.token, m.err
@@ -136,7 +136,7 @@ type mockTokenManager struct {
136136
done chan struct{}
137137
options manager.TokenManagerOptions
138138
listener manager.TokenListener
139-
lock sync.Mutex
139+
lock *sync.Mutex
140140
}
141141

142142
func (m *mockTokenManager) GetToken(forceRefresh bool) (*token.Token, error) {

examples/custom_idp/go.mod

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,23 @@ module custom_example
33
go 1.23.4
44

55
require (
6-
github.com/redis/go-redis-entraid v1.0.0
6+
github.com/redis/go-redis-entraid v1.0.1
77
github.com/redis/go-redis/v9 v9.9.0
88
)
99

1010
require (
11-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
12-
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 // indirect
13-
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
14-
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 // indirect
11+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
12+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0 // indirect
13+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
14+
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
1515
github.com/cespare/xxhash/v2 v2.3.0 // indirect
1616
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
17-
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
17+
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
1818
github.com/google/uuid v1.6.0 // indirect
1919
github.com/kylelemons/godebug v1.1.0 // indirect
2020
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
21-
golang.org/x/crypto v0.33.0 // indirect
22-
golang.org/x/net v0.35.0 // indirect
23-
golang.org/x/sys v0.30.0 // indirect
24-
golang.org/x/text v0.22.0 // indirect
21+
golang.org/x/crypto v0.38.0 // indirect
22+
golang.org/x/net v0.40.0 // indirect
23+
golang.org/x/sys v0.33.0 // indirect
24+
golang.org/x/text v0.25.0 // indirect
2525
)

examples/custom_idp/go.sum

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
2-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
3-
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1 h1:iw4+KCeCoieuKodp1d5YhAa1TU/GgogCbw8RbGvsfLA=
4-
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0-beta.1/go.mod h1:AP8cDnDTGIVvayqKAhwzpcAyTJosXpvLYNmVFJb98x8=
5-
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3 h1:BAUsn6/icUFtvUalVwCO0+hSF7qgU9DwwcEfCvtILtw=
6-
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.2.3/go.mod h1:QlAsNp4gk9zLD2wiZIvIuv699ynpZ2Tq2ZBp+6MrSEw=
7-
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
8-
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
1+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
2+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
3+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0 h1:j8BorDEigD8UFOSZQiSqAMOOleyQOOQPnUAwV+Ls1gA=
4+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0/go.mod h1:JdM5psgjfBf5fo2uWOZhflPWyDBZ/O/CNAH9CtsuZE4=
5+
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
6+
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
7+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
8+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
99
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
1010
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
11-
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 h1:8BKxhZZLX/WosEeoCvWysmKUscfa9v8LIPEEU0JjE2o=
12-
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
11+
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
12+
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
1313
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
1414
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
1515
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
@@ -20,36 +20,34 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
2020
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2121
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
2222
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
23-
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
24-
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
23+
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
24+
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
2525
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
2626
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
27-
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
28-
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
27+
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
28+
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
2929
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
3030
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
3131
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
3232
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
3333
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
3434
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
35-
github.com/redis/go-redis-entraid v0.0.0-20250415111332-9d087bc29c12 h1:H5ZfgueBAxs2eAvXtCMEbT2/fLQz/wxW5Ds4c0uzl50=
36-
github.com/redis/go-redis-entraid v0.0.0-20250415111332-9d087bc29c12/go.mod h1:uXKLxCMUAu1VKgWdt8gWc4PWCygiL2pAI5XpnRSVc0w=
37-
github.com/redis/go-redis-entraid v1.0.0/go.mod h1:b+YPtHM3oFJ74Y2eFHPuz1Cp59kUL0fwdiARp27VW8Q=
38-
github.com/redis/go-redis/v9 v9.5.3-0.20250415103233-40a89c56cc52 h1:jRx2gINoJsGKxi/RYXCq1VneAAYes9JxUp13xH2oU2g=
39-
github.com/redis/go-redis/v9 v9.5.3-0.20250415103233-40a89c56cc52/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
35+
github.com/redis/go-redis-entraid v1.0.1 h1:Q2gxpSRFLn+KyZuPrF7zDUCQ9iISoUxqzaCjxPqJKQI=
36+
github.com/redis/go-redis-entraid v1.0.1/go.mod h1:OS6s3V1DdSRzOJEIjpK38/w4chZpl/Sy+1pzby+6nEk=
37+
github.com/redis/go-redis/v9 v9.9.0 h1:URbPQ4xVQSQhZ27WMQVmZSo3uT3pL+4IdHVcYq2nVfM=
4038
github.com/redis/go-redis/v9 v9.9.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
4139
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
4240
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
4341
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
4442
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
45-
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
46-
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
47-
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
48-
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
43+
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
44+
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
45+
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
46+
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
4947
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
50-
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
51-
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
52-
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
53-
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
48+
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
49+
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
50+
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
51+
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
5452
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
5553
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

manager/defaults.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package manager
33
import (
44
"errors"
55
"fmt"
6-
"math"
76
"net"
87
"os"
98
"time"
@@ -177,6 +176,6 @@ func (*defaultIdentityProviderResponseParser) ParseResponse(response shared.Iden
177176
rawToken,
178177
expiresOn,
179178
now,
180-
int64(math.Ceil(time.Until(expiresOn).Seconds())),
179+
time.Until(expiresOn).Milliseconds(),
181180
), nil
182181
}

manager/entraid_manager.go

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"github.com/redis/go-redis-entraid/token"
1212
)
1313

14+
const RefreshRationPrecision = 10000
15+
1416
// entraidTokenManager is a struct that implements the TokenManager interface.
1517
type entraidTokenManager struct {
1618
// idp is the identity provider used to obtain the token.
@@ -20,7 +22,7 @@ type entraidTokenManager struct {
2022
token *token.Token
2123

2224
// tokenRWLock is a read-write lock used to protect the token from concurrent access.
23-
tokenRWLock sync.RWMutex
25+
tokenRWLock *sync.RWMutex
2426

2527
// identityProviderResponseParser is the parser used to parse the response from the identity provider.
2628
// It`s ParseResponse method will be called to parse the response and return the token.
@@ -40,7 +42,7 @@ type entraidTokenManager struct {
4042
listener TokenListener
4143

4244
// lock locks the listener to prevent concurrent access.
43-
lock sync.Mutex
45+
lock *sync.Mutex
4446

4547
// expirationRefreshRatio is the ratio of the token expiration time to refresh the token.
4648
// It is used to determine when to refresh the token.
@@ -221,12 +223,14 @@ func (e *entraidTokenManager) stop() (err error) {
221223
err = fmt.Errorf("failed to stop token manager: %s", r)
222224
}
223225
}()
226+
if e.ctxCancel != nil {
227+
e.ctxCancel()
228+
}
224229

225230
if e.closedChan == nil || e.listener == nil {
226231
return ErrTokenManagerAlreadyStopped
227232
}
228233

229-
e.ctxCancel()
230234
e.listener = nil
231235
close(e.closedChan)
232236

@@ -238,30 +242,63 @@ func (e *entraidTokenManager) stop() (err error) {
238242
// If the token is nil, it returns 0.
239243
// If the time till expiration is less than the lower bound duration, it returns 0 to renew the token now.
240244
func (e *entraidTokenManager) durationToRenewal(t *token.Token) time.Duration {
245+
// Fast path: nil token check
241246
if t == nil {
242247
return 0
243248
}
244-
expirationRefreshTime := t.ReceivedAt().Add(time.Duration(float64(t.TTL()) * float64(time.Second) * e.expirationRefreshRatio))
245-
timeTillExpiration := time.Until(t.ExpirationOn())
246-
now := time.Now().UTC()
247249

248-
if expirationRefreshTime.Before(now) {
250+
// Get current time in milliseconds (UTC)
251+
nowMillis := time.Now().UnixMilli()
252+
253+
// Get expiration time in milliseconds
254+
expMillis := t.ExpirationOn().UnixMilli()
255+
256+
// Fast path: token already expired
257+
if expMillis <= nowMillis {
258+
return 0
259+
}
260+
261+
// Calculate time until expiration in milliseconds
262+
timeTillExpiration := expMillis - nowMillis
263+
264+
// Get lower bound in milliseconds
265+
lowerBoundMillis := e.lowerBoundDuration.Milliseconds()
266+
267+
// Fast path: time until expiration is less than lower bound
268+
if timeTillExpiration <= lowerBoundMillis {
249269
return 0
250270
}
251271

252-
// if the timeTillExpiration is less than the lower bound (or 0), return 0 to renew the token NOW
253-
if timeTillExpiration <= e.lowerBoundDuration || timeTillExpiration <= 0 {
272+
// Calculate refresh time using integer math with higher precision
273+
// example tests use 0.001, which would be lost with lower precision
274+
// Example:
275+
// ttlMillis = 10000
276+
// e.expirationRefreshRatio = 0.001
277+
// - with int math and 100 precision: 10000 * (0.001*100) = 0ms
278+
// - with int math and 10000 precision: 10000 * (0.001*10000) = 100ms
279+
precision := int64(RefreshRationPrecision)
280+
receivedAtMillis := t.ReceivedAt().UnixMilli()
281+
ttlMillis := t.TTL() // Already in milliseconds
282+
refreshRatioInt := int64(e.expirationRefreshRatio * float64(precision))
283+
refreshMillis := ttlMillis * refreshRatioInt / precision
284+
refreshTimeMillis := receivedAtMillis + refreshMillis
285+
286+
// Calculate time until refresh
287+
timeUntilRefresh := refreshTimeMillis - nowMillis
288+
289+
// Fast path: refresh time is in the past
290+
if timeUntilRefresh <= 0 {
254291
return 0
255292
}
256293

257-
// Calculate the time to renew the token based on the expiration refresh ratio
258-
duration := time.Until(expirationRefreshTime)
294+
// Calculate time until lower bound
295+
timeUntilLowerBound := timeTillExpiration - lowerBoundMillis
259296

260-
// if the duration will take us past the lower bound, return the duration to lower bound
261-
if timeTillExpiration-e.lowerBoundDuration < duration {
262-
return timeTillExpiration - e.lowerBoundDuration
297+
// If refresh would occur after lower bound, use time until lower bound
298+
if timeUntilRefresh > timeUntilLowerBound {
299+
return time.Duration(timeUntilLowerBound) * time.Millisecond
263300
}
264301

265-
// return the calculated duration
266-
return duration
302+
// Otherwise use time until refresh
303+
return time.Duration(timeUntilRefresh) * time.Millisecond
267304
}

0 commit comments

Comments
 (0)