Skip to content

Commit dddf87c

Browse files
committed
Fix memory leak in background token refresh
- Replace `AbortSignal.any()` with `linkSignals()` to properly clean up event listeners - Pass `signal` from retry callback into `#client.login()` for proper RPC cancellation - Update `@ydbjs/retry` dependency to ^6.2.0 for signal-handling fixes - Add `@ydbjs/abortable` ^6.1.0 dependency Signed-off-by: Vladislav Polyakov <polRk@ydb.tech>
1 parent 31d6f01 commit dddf87c

File tree

3 files changed

+19
-8
lines changed

3 files changed

+19
-8
lines changed

.changeset/pcrhshgrdb.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
'@ydbjs/auth': minor
3+
---
4+
5+
Fix memory leak in background token refresh caused by `AbortSignal.any()`
6+
7+
`AbortSignal.any()` registers event listeners on source signals but never removes them, causing a listener leak each time background token refresh is started. Replace it with `linkSignals` from `@ydbjs/abortable@^6.1.0`, which uses `Symbol.dispose` to clean up listeners when the refresh loop exits.
8+
9+
Additional fixes:
10+
11+
- Pass `signal` from the retry callback into `#client.login()` so the login RPC is properly cancelled on abort instead of running unchecked
12+
- Update `@ydbjs/retry` dependency to `^6.2.0` to pick up the same signal-handling fixes in the retry loop

packages/auth/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@
5555
"@bufbuild/protobuf": "2.10.1",
5656
"@grpc/grpc-js": "^1.14.0",
5757
"@ydbjs/api": "^6.0.0",
58+
"@ydbjs/abortable": "^6.1.0",
5859
"@ydbjs/debug": "^6.0.0",
5960
"@ydbjs/error": "^6.0.0",
60-
"@ydbjs/retry": "^6.0.0",
61+
"@ydbjs/retry": "^6.2.0",
6162
"nice-grpc": "^2.1.13"
6263
},
6364
"engines": {

packages/auth/src/static.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { defaultRetryConfig, retry } from '@ydbjs/retry'
1010
import { type Client, ClientError, Status, createChannel, createClient } from 'nice-grpc'
1111

1212
import { CredentialsProvider } from './index.js'
13+
import { linkSignals } from '@ydbjs/abortable'
1314

1415
let debug = loggers.auth.extend('static')
1516

@@ -79,6 +80,7 @@ export class StaticCredentialsProvider extends CredentialsProvider {
7980
let channelCredentials = secureOptions
8081
? credentials.createFromSecureContext(tls.createSecureContext(secureOptions))
8182
: credentials.createInsecure()
83+
8284
this.#client = createClient(
8385
AuthServiceDefinition,
8486
createChannel(address, channelCredentials, channelOptions)
@@ -148,13 +150,9 @@ export class StaticCredentialsProvider extends CredentialsProvider {
148150
}
149151

150152
debug.log('starting background token refresh')
151-
// Combine user signal with timeout signal
152-
let combinedSignal = AbortSignal.any([
153-
signal,
154-
AbortSignal.timeout(BACKGROUND_REFRESH_TIMEOUT_MS),
155-
])
153+
using linkedSignal = linkSignals(signal, AbortSignal.timeout(BACKGROUND_REFRESH_TIMEOUT_MS))
156154

157-
void this.#refreshToken(combinedSignal)
155+
void this.#refreshToken(linkedSignal.signal)
158156
}
159157

160158
/**
@@ -172,7 +170,7 @@ export class StaticCredentialsProvider extends CredentialsProvider {
172170
debug.log('retry attempt #%d after error: %s', ctx.attempt, ctx.error)
173171
},
174172
},
175-
async () => {
173+
async (signal) => {
176174
debug.log('attempting login with user: %s', this.#username)
177175

178176
let response = await this.#client.login(

0 commit comments

Comments
 (0)