@trieb.work/nextjs-turbo-redis-cache 1.16.1 → 1.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/dist/index.js +20 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +20 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/RedisStringsHandler.ts +26 -15
- package/test/vitest/unit/index.test.ts +28 -0
|
@@ -444,21 +444,32 @@ export default class RedisStringsHandler {
|
|
|
444
444
|
'assertClientIsReady called more than 10 times without being ready.',
|
|
445
445
|
);
|
|
446
446
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
447
|
+
let readyTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
448
|
+
try {
|
|
449
|
+
await Promise.race([
|
|
450
|
+
Promise.all([
|
|
451
|
+
this.sharedTagsMap.waitUntilReady(),
|
|
452
|
+
this.revalidatedTagsMap.waitUntilReady(),
|
|
453
|
+
]),
|
|
454
|
+
new Promise((_, reject) => {
|
|
455
|
+
readyTimeout = setTimeout(() => {
|
|
456
|
+
reject(
|
|
457
|
+
new Error(
|
|
458
|
+
'assertClientIsReady: Timeout waiting for Redis maps to be ready',
|
|
459
|
+
),
|
|
460
|
+
);
|
|
461
|
+
}, 30_000);
|
|
462
|
+
}),
|
|
463
|
+
]);
|
|
464
|
+
} finally {
|
|
465
|
+
// Always clear the timeout: once the race is won (the common case after
|
|
466
|
+
// the initial sync), the timer would otherwise stay pending for the full
|
|
467
|
+
// 30s. Timers capture the ambient async context (AsyncLocalStorage),
|
|
468
|
+
// so in a Next.js server every leaked timer pins the request's store —
|
|
469
|
+
// and with it the whole response object graph — for at least 30s on
|
|
470
|
+
// every single cache operation.
|
|
471
|
+
clearTimeout(readyTimeout);
|
|
472
|
+
}
|
|
462
473
|
this.clientReadyCalls = 0;
|
|
463
474
|
if (!this.client.isReady) {
|
|
464
475
|
throw new Error(
|
|
@@ -85,6 +85,34 @@ describe('RedisStringsHandler', () => {
|
|
|
85
85
|
expect(res).toBeNull();
|
|
86
86
|
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
87
87
|
});
|
|
88
|
+
|
|
89
|
+
it('does not leave the readiness timeout timer pending after an operation', async () => {
|
|
90
|
+
vi.useFakeTimers();
|
|
91
|
+
try {
|
|
92
|
+
const handler = new RedisStringsHandler({
|
|
93
|
+
redisUrl: 'redis://localhost:6379',
|
|
94
|
+
keyPrefix: 'test:',
|
|
95
|
+
database: 0,
|
|
96
|
+
getTimeoutMs: 1,
|
|
97
|
+
redisGetDeduplication: false,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const timersBefore = vi.getTimerCount();
|
|
101
|
+
await handler.get('missing-key', {
|
|
102
|
+
kind: 'APP_PAGE',
|
|
103
|
+
isRoutePPREnabled: false,
|
|
104
|
+
isFallback: false,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// assertClientIsReady() races waitUntilReady() against a 30s timeout;
|
|
108
|
+
// the timer must not stay pending once the race is settled, because
|
|
109
|
+
// pending timers retain the ambient async context (and with it, in a
|
|
110
|
+
// Next.js server, the whole per-request object graph).
|
|
111
|
+
expect(vi.getTimerCount()).toBe(timersBefore);
|
|
112
|
+
} finally {
|
|
113
|
+
vi.useRealTimers();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
88
116
|
});
|
|
89
117
|
|
|
90
118
|
describe('Public exports', () => {
|