Skip to content

Commit

Permalink
Implement Cache simulator as Durable Object
Browse files Browse the repository at this point in the history
  • Loading branch information
mrbbot committed Aug 17, 2023
1 parent b14845c commit 6fd68bf
Show file tree
Hide file tree
Showing 15 changed files with 526 additions and 534 deletions.
1 change: 0 additions & 1 deletion packages/miniflare/src/plugins/cache/constants.ts

This file was deleted.

171 changes: 107 additions & 64 deletions packages/miniflare/src/plugins/cache/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import fs from "fs/promises";
import SCRIPT_CACHE_OBJECT from "worker:cache/cache";
import SCRIPT_CACHE_ENTRY from "worker:cache/cache-entry";
import SCRIPT_CACHE_ENTRY_NOOP from "worker:cache/cache-entry-noop";
import { z } from "zod";
import { CoreBindings } from "../../workers";
import {
BINDING_TEXT_PERSIST,
BINDING_TEXT_PLUGIN,
CfHeader,
HEADER_PERSIST,
Service,
Worker,
Worker_Binding_DurableObjectNamespaceDesignator,
} from "../../runtime";
import { CacheBindings, SharedBindings } from "../../workers";
import {
PersistenceSchema,
Plugin,
WORKER_BINDING_SERVICE_LOOPBACK,
encodePersist,
SERVICE_LOOPBACK,
getPersistPath,
} from "../shared";
import { HEADER_CACHE_WARN_USAGE } from "./constants";
import { CacheGateway } from "./gateway";
import { CacheRouter } from "./router";

export const CacheOptionsSchema = z.object({
cache: z.boolean().optional(),
Expand All @@ -22,46 +24,24 @@ export const CacheSharedOptionsSchema = z.object({
cachePersist: PersistenceSchema,
});

const BINDING_JSON_CACHE_WARN_USAGE = "MINIFLARE_CACHE_WARN_USAGE";

const CACHE_SCRIPT_COMPAT_DATE = "2022-09-01";
export const CACHE_LOOPBACK_SCRIPT = `addEventListener("fetch", (event) => {
const request = new Request(event.request);
const url = new URL(request.url);
url.pathname = \`/\${${BINDING_TEXT_PLUGIN}}/\${encodeURIComponent(request.url)}\`;
if (globalThis.${BINDING_TEXT_PERSIST} !== undefined) request.headers.set("${HEADER_PERSIST}", ${BINDING_TEXT_PERSIST});
if (globalThis.${BINDING_JSON_CACHE_WARN_USAGE}) request.headers.set("${HEADER_CACHE_WARN_USAGE}", "true");
event.respondWith(${CoreBindings.SERVICE_LOOPBACK}.fetch(url, request));
});`;
// Cache service script that doesn't do any caching
export const NOOP_CACHE_SCRIPT = `addEventListener("fetch", (event) => {
const request = event.request;
if (request.method === "GET") {
event.respondWith(new Response(null, { status: 504, headers: { [${JSON.stringify(
CfHeader.CacheStatus
)}]: "MISS" } }));
} else if (request.method === "PUT") {
// Must consume request body, otherwise get "disconnected: read end of pipe was aborted" error from workerd
event.respondWith(request.arrayBuffer().then(() => new Response(null, { status: 204 })));
} else if (request.method === "PURGE") {
event.respondWith(new Response(null, { status: 404 }));
} else {
event.respondWith(new Response(null, { status: 405 }));
}
});`;
export const CACHE_PLUGIN_NAME = "cache";
const CACHE_STORAGE_SERVICE_NAME = `${CACHE_PLUGIN_NAME}:storage`;
const CACHE_SERVICE_PREFIX = `${CACHE_PLUGIN_NAME}:cache`;

const CACHE_OBJECT_CLASS_NAME = "CacheObject";
const CACHE_OBJECT: Worker_Binding_DurableObjectNamespaceDesignator = {
serviceName: CACHE_SERVICE_PREFIX,
className: CACHE_OBJECT_CLASS_NAME,
};

export function getCacheServiceName(workerIndex: number) {
return `${CACHE_PLUGIN_NAME}:${workerIndex}`;
}

export const CACHE_PLUGIN: Plugin<
typeof CacheOptionsSchema,
typeof CacheSharedOptionsSchema,
CacheGateway
typeof CacheSharedOptionsSchema
> = {
gateway: CacheGateway,
router: CacheRouter,
options: CacheOptionsSchema,
sharedOptions: CacheSharedOptionsSchema,
getBindings() {
Expand All @@ -70,30 +50,93 @@ export const CACHE_PLUGIN: Plugin<
getNodeBindings() {
return {};
},
getServices({ sharedOptions, options, workerIndex }) {
const persistBinding = encodePersist(sharedOptions.cachePersist);
return [
{
name: getCacheServiceName(workerIndex),
worker: {
serviceWorkerScript:
// If options.cache is undefined, default to enabling cache
options.cache === false ? NOOP_CACHE_SCRIPT : CACHE_LOOPBACK_SCRIPT,
bindings: [
...persistBinding,
{ name: BINDING_TEXT_PLUGIN, text: CACHE_PLUGIN_NAME },
{
name: BINDING_JSON_CACHE_WARN_USAGE,
json: JSON.stringify(options.cacheWarnUsage ?? false),
},
WORKER_BINDING_SERVICE_LOOPBACK,
],
compatibilityDate: CACHE_SCRIPT_COMPAT_DATE,
},
async getServices({ sharedOptions, options, workerIndex, tmpPath }) {
// If options.cache is undefined, default to enabling cache
const cache = options.cache !== false;
const cacheWarnUsage = options.cacheWarnUsage ?? false;

let entryWorker: Worker;
if (cache) {
entryWorker = {
compatibilityDate: "2023-07-24",
compatibilityFlags: ["nodejs_compat", "experimental"],
modules: [
{ name: "cache-entry.worker.js", esModule: SCRIPT_CACHE_ENTRY() },
],
bindings: [
{
name: SharedBindings.DURABLE_OBJECT_NAMESPACE_OBJECT,
durableObjectNamespace: CACHE_OBJECT,
},
{
name: CacheBindings.MAYBE_JSON_CACHE_WARN_USAGE,
json: JSON.stringify(cacheWarnUsage),
},
],
};
} else {
entryWorker = {
compatibilityDate: "2023-07-24",
compatibilityFlags: ["nodejs_compat", "experimental"],
modules: [
{
name: "cache-entry-noop.worker.js",
esModule: SCRIPT_CACHE_ENTRY_NOOP(),
},
],
};
}

const uniqueKey = `miniflare-${CACHE_OBJECT_CLASS_NAME}`;

const persist = sharedOptions.cachePersist;
const persistPath = getPersistPath(CACHE_PLUGIN_NAME, tmpPath, persist);
await fs.mkdir(persistPath, { recursive: true });
const storageService: Service = {
name: CACHE_STORAGE_SERVICE_NAME,
disk: { path: persistPath, writable: true },
};
const objectService: Service = {
name: CACHE_SERVICE_PREFIX,
worker: {
compatibilityDate: "2023-07-24",
compatibilityFlags: ["nodejs_compat", "experimental"],
modules: [
{
name: "cache.worker.js",
esModule: SCRIPT_CACHE_OBJECT(),
},
],
durableObjectNamespaces: [
{
className: CACHE_OBJECT_CLASS_NAME,
uniqueKey,
},
],
// Store Durable Object SQL databases in persist path
durableObjectStorage: { localDisk: CACHE_STORAGE_SERVICE_NAME },
// Bind blob disk directory service to object
bindings: [
{
name: SharedBindings.MAYBE_SERVICE_BLOBS,
service: { name: CACHE_STORAGE_SERVICE_NAME },
},
{
name: SharedBindings.MAYBE_SERVICE_LOOPBACK,
service: { name: SERVICE_LOOPBACK },
},
],
},
};

// NOTE: not migrating here as applications should be able to recover from
// cache evictions, and we'd need to locate all named caches

const services: Service[] = [
{ name: getCacheServiceName(workerIndex), worker: entryWorker },
storageService,
objectService,
];
return services;
},
};

export * from "./gateway";
export { _RemoveTransformEncodingChunkedStream } from "./router";
179 changes: 0 additions & 179 deletions packages/miniflare/src/plugins/cache/router.ts

This file was deleted.

Loading

0 comments on commit 6fd68bf

Please sign in to comment.