Skip to content

Commit c0a3621

Browse files
committed
fix: handle NOSCRIPT errors in Redis Cluster with EVAL fallback
1 parent 70e6107 commit c0a3621

File tree

1 file changed

+29
-8
lines changed

1 file changed

+29
-8
lines changed

source/lib.ts

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export class RedisStore implements Store {
155155
*/
156156
async retryableIncrement(_key: string): Promise<RedisReply> {
157157
const key = this.prefixKey(_key)
158-
const evalCommand = async () =>
158+
const evalShaCommand = async () =>
159159
this.sendCommand({
160160
key,
161161
isReadOnly: false,
@@ -169,12 +169,28 @@ export class RedisStore implements Store {
169169
],
170170
})
171171

172+
const evalCommand = async () =>
173+
this.sendCommand({
174+
key,
175+
isReadOnly: false,
176+
command: [
177+
'EVAL',
178+
scripts.increment,
179+
'1',
180+
key,
181+
this.resetExpiryOnChange ? '1' : '0',
182+
this.windowMs.toString(),
183+
],
184+
})
185+
172186
try {
173-
const result = await evalCommand()
187+
const result = await evalShaCommand()
174188
return result
175189
} catch {
176-
// TODO: distinguish different error types
177-
this.incrementScriptSha = this.loadIncrementScript(key)
190+
// In Redis Cluster, scripts loaded on one node may not be available
191+
// on the node where the key is located. Fall back to EVAL.
192+
// We don't need to check for NOSCRIPT explicitly because if EVALSHA fails
193+
// for ANY reason, EVAL is a safe fallback that guarantees execution.
178194
return evalCommand()
179195
}
180196
}
@@ -209,17 +225,22 @@ export class RedisStore implements Store {
209225
async get(_key: string): Promise<ClientRateLimitInfo | undefined> {
210226
const key = this.prefixKey(_key)
211227
let results
212-
const evalCommand = async () =>
228+
const evalShaCommand = async () =>
213229
this.sendCommand({
214230
key,
215231
isReadOnly: true,
216232
command: ['EVALSHA', await this.getScriptSha, '1', key],
217233
})
234+
const evalCommand = async () =>
235+
this.sendCommand({
236+
key,
237+
isReadOnly: true,
238+
command: ['EVAL', scripts.get, '1', key],
239+
})
218240
try {
219-
results = await evalCommand()
241+
results = await evalShaCommand()
220242
} catch {
221-
// TODO: distinguish different error types
222-
this.getScriptSha = this.loadGetScript(key)
243+
// Fallback to EVAL if EVALSHA fails
223244
results = await evalCommand()
224245
}
225246

0 commit comments

Comments
 (0)