-
Notifications
You must be signed in to change notification settings - Fork 8
Add timeouts and bounded reconnect to Redis client #120
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,9 +9,22 @@ if (redisTlsServerName && parsedRedisUrl?.protocol !== "rediss:") { | |
| throw new Error("REDIS_TLS_SERVER_NAME requires REDIS_URL to use rediss://"); | ||
| } | ||
|
|
||
| // Modest backoff to smooth over first-hit cold connections | ||
| const reconnectStrategy = (retries: number) => | ||
| Math.min(500 + retries * 100, 2000); | ||
| // Upper bound on connecting and on any single command, so an unreachable Redis | ||
| // surfaces as an error instead of blocking the caller (e.g. OAuth token exchange). | ||
| const CONNECT_TIMEOUT_MS = 5000; | ||
| const COMMAND_TIMEOUT_MS = 5000; | ||
|
|
||
| // Modest backoff to smooth over first-hit cold connections, but give up after a | ||
| // bounded number of attempts rather than retrying forever while Redis is down. | ||
| const MAX_RECONNECT_ATTEMPTS = 10; | ||
| const reconnectStrategy = (retries: number) => { | ||
| if (retries > MAX_RECONNECT_ATTEMPTS) { | ||
| return new Error( | ||
| `Redis unavailable after ${MAX_RECONNECT_ATTEMPTS} reconnect attempts`, | ||
| ); | ||
| } | ||
| return Math.min(500 + retries * 100, 2000); | ||
| }; | ||
|
|
||
| // Connect on first use | ||
| let isConnected = false; | ||
|
|
@@ -24,9 +37,11 @@ const client = createClient({ | |
| host: parsedRedisUrl!.hostname, | ||
| tls: true, | ||
| servername: redisTlsServerName, | ||
| connectTimeout: CONNECT_TIMEOUT_MS, | ||
| reconnectStrategy, | ||
| } | ||
| : { | ||
| connectTimeout: CONNECT_TIMEOUT_MS, | ||
| reconnectStrategy, | ||
| }, | ||
| }); | ||
|
|
@@ -179,14 +194,32 @@ function isTransientSocketError(error: unknown): boolean { | |
| ); | ||
| } | ||
|
|
||
| async function withTimeout<T>(operation: () => Promise<T>): Promise<T> { | ||
| let timer: ReturnType<typeof setTimeout> | undefined; | ||
| const timeout = new Promise<never>((_, reject) => { | ||
| timer = setTimeout( | ||
| () => | ||
| reject( | ||
| new Error(`Redis command timed out after ${COMMAND_TIMEOUT_MS}ms`), | ||
| ), | ||
| COMMAND_TIMEOUT_MS, | ||
| ); | ||
| }); | ||
| try { | ||
| return await Promise.race([operation(), timeout]); | ||
| } finally { | ||
| if (timer) clearTimeout(timer); | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Timeout leaves unhandled rejectionMedium Severity The Reviewed by Cursor Bugbot for commit 3397557. Configure here. |
||
|
|
||
| async function withReconnect<T>(operation: () => Promise<T>): Promise<T> { | ||
| try { | ||
| return await operation(); | ||
| return await withTimeout(operation); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Connect await exceeds command timeoutHigh Severity The new Additional Locations (2)Reviewed by Cursor Bugbot for commit 3397557. Configure here. |
||
| } catch (err) { | ||
| if (isTransientSocketError(err)) { | ||
| isConnected = false; | ||
| await ensureConnected(); | ||
| return await operation(); | ||
| return await withTimeout(operation); | ||
| } | ||
| throw err; | ||
| } | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reconnect limit off by one
Low Severity
reconnectStrategystops only whenretries > MAX_RECONNECT_ATTEMPTS(10), so node-redis can still schedule backoff forretries0 through 10 before returning an error. That is one more delayed reconnect cycle thanMAX_RECONNECT_ATTEMPTSand the error text imply.Reviewed by Cursor Bugbot for commit 3397557. Configure here.