Stop using shared promises (#114)

* Try cf.cacheTags

* Remove shared promises

* Use a pendings set per context
This commit is contained in:
Samy Pessé
2024-01-23 23:19:23 +01:00
committed by GitHub
parent 22a68a71c4
commit aec74d68fd
3 changed files with 24 additions and 3 deletions
+11 -2
View File
@@ -2,7 +2,7 @@ import hash from 'object-hash';
import { cacheBackends } from './backends';
import { CacheEntry } from './types';
import { waitUntil } from './waitUntil';
import { getGlobalContext, waitUntil } from './waitUntil';
export type CacheFunction<Args extends any[], Result> = ((...args: Args) => Promise<Result>) & {
/**
@@ -106,12 +106,21 @@ export function cache<Args extends any[], Result>(
// During development, for now it fetches data twice between the middleware and the handler.
// TODO: find a way to share the cache between the two.
const pendings = new Map<string, Promise<any>>();
// On Cloudflare Workers, we can't share promises between requests:
// > Cannot perform I/O on behalf of a different request. I/O objects (such as streams, request/response bodies, and others)
// > created in the context of one request handler cannot be accessed from a different request's handler.
//
// To avoid this limitation and still avoid concurrent requests, we use a WeakMap to store the pending requests.
const contextPendings = new WeakMap<object, Map<string, Promise<any>>>();
const cacheFn = async (...args: Args) => {
const cacheArgs = options.extractArgs ? options.extractArgs(args) : args;
const key = getCacheKey(cacheName, cacheArgs);
const context = getGlobalContext();
const pendings = contextPendings.get(context) ?? new Map<string, Promise<any>>();
contextPendings.set(context, pendings);
// If a pending request exists, wait for it
if (pendings.has(key)) {
return await pendings.get(key);
+5 -1
View File
@@ -79,17 +79,21 @@ async function serializeKey(key: string): Promise<string> {
function serializeEntry(entry: CacheEntry): WorkerResponse {
const headers = new Headers();
headers.set('Content-Type', 'application/json');
const cacheTags = ['gitbook-open', ...entry.meta.tags];
// Limit the cloudflare cache to 5 minutes
headers.set(
'Cache-Control',
`public, max-age=${Math.min((entry.meta.expiresAt - Date.now()) / 1000, 5 * 60)}`,
);
headers.set('Cache-Tag', ['gitbook-open', ...entry.meta.tags].join(','));
headers.set('Cache-Tag', cacheTags.join(','));
// @ts-ignore
return new Response(JSON.stringify(entry), {
headers,
cf: {
cacheTags,
},
});
}
+8
View File
@@ -1,5 +1,13 @@
import { getRequestCloudflareContext } from '@cloudflare/next-on-pages/helpers';
/**
* Get the global context object for the current request.
*/
export function getGlobalContext(): object {
const cloudflare = getRequestCloudflareContext();
return cloudflare ?? globalThis;
}
/**
* Extend the lifetime of the event handler until the promise is resolved.
* https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/#contextwaituntil