I am caching some global variables in memory across multiple requests in a cloudflare worker. I want to flush those variables into a persistent storage like KV cache or redis just before the worker is scheduled to be spun down.
Example:
const validationCache = new Map<string, {
counter: int;
}>();
export async function somemiddleware(c: Context, next: Next) {
...
return response;
}
addEventListener('wokerSpinningDown', saveCache());
I have tried to use waitUntil(). But setTimeout() isn't very reliable in workers. What are my options?
There is no way to hook shutdown directly, and it would be difficult for the Workers Runtime to provide such a feature, because generally isolates are only evicted when the runtime needs the memory back immediately, and so there is no time to stop and wait for the app to run some code first.
What you can do instead is, on each request (or maybe just on any request that adds things to cache), schedule to flush the cache 15 seconds later in a waitUntil() task. If a new request comes in, cancel the previous flush and schedule a new one. Note that waitUntil() tasks have a time limit of 30 seconds after the response is returned, so I chose a 15-second timeout to leave plenty of headroom to complete the flush.
// WARNING: This code is not tested.
export default {
async fetch(req, env, ctx) {
ctx.waitUntil(flushCacheLater());
// ... handle request ...
}
}
let cancelPreviousFlush = null;
async function flushCacheLater() {
// If a previous flush is scheduled, cancel it.
if (cancelPreviousFlush) {
cancelPreviousFlush();
}
// Register ourselves as the scheduled flush.
let cancelPromise = new Promise(resolve => { cancelPreviousFlush = resolve; });
// Wait 15 seconds.
let timeoutPromise = scheduler.wait(15000);
let canceled = await Promise.race([
timeoutPromise.then(() => false),
cancelPromise.then(() => true),
]);
if (!canceled) {
// ... perform the flush...
}
}
HOWEVER, if you go this route, please keep in mind: This is not 100% reliable. It's always possible that a machine failure or a network failure or a bug in your code will cause the cache flush not to actually happen. If it is critical that the cache flush actually happens, then you must do it before returning the response to the client, so that you can return an error if it fails. This is a fundamental nature of distributed systems: You can never assume that code will actually complete. The best you can do is make sure that someone is listening for errors and can respond appropriately.