| 1 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import { HttpServerRequest } from "effect/http/HttpServerRequest"; |
| 4 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 5 | import { BunObject } from "./bun-object.ts"; |
| 6 | import { EffectfulObject } from "./effectful-object.ts"; |
| 7 | import { OpencodeObject } from "./opencode-object.ts"; |
| 8 | import { RemoteObject } from "./remote-object.ts"; |
| 9 | |
| 10 | /** |
| 11 | * Cloudflare Worker host for the container cold-start benchmark. Each request |
| 12 | * names a fresh DO instance (`?name=`), which boots its own container. |
| 13 | * |
| 14 | * - `GET /boot?variant=effectful|bun|remote|opencode&name=K` boots one |
| 15 | * container and returns `{ readyMs }`. It does NOT shut down. |
| 16 | * - `GET /shutdown?variant=…&name=K` tears the container down so the next boot |
| 17 | * is an independent cold start and the account's container cap isn't exhausted. |
| 18 | * |
| 19 | * TIMING: the clock runs HERE in the Worker, around the entire DO call — not |
| 20 | * inside the DO. The container layer eagerly calls `container.start()` during |
| 21 | * DO construction, so a clock started inside `boot()` would miss instance |
| 22 | * allocation and start entirely, undercounting the cold start. Wrapping the |
| 23 | * `getByName(name).boot()` call captures DO creation + container start + |
| 24 | * readiness probe, while staying inside Cloudflare (no client network in the |
| 25 | * number) — symmetric with the MicroVM hosts, which start the clock before |
| 26 | * `RunMicrovm`. |
| 27 | */ |
| 28 | export default class ContainerWorker extends Cloudflare.Worker<ContainerWorker>()( |
| 29 | "BenchContainerWorker", |
| 30 | { |
| 31 | main: import.meta.filename, |
| 32 | }, |
| 33 | Effect.gen(function* () { |
| 34 | const effectful = yield* EffectfulObject; |
| 35 | const remote = yield* RemoteObject; |
| 36 | const bun = yield* BunObject; |
| 37 | const opencode = yield* OpencodeObject; |
| 38 | |
| 39 | const objectFor = (variant: string) => |
| 40 | variant === "remote" |
| 41 | ? remote |
| 42 | : variant === "bun" |
| 43 | ? bun |
| 44 | : variant === "opencode" |
| 45 | ? opencode |
| 46 | : effectful; |
| 47 | |
| 48 | return { |
| 49 | fetch: Effect.gen(function* () { |
| 50 | const request = yield* HttpServerRequest; |
| 51 | const url = new URL(request.url, "http://x"); |
| 52 | const name = url.searchParams.get("name") ?? "default"; |
| 53 | const variant = url.searchParams.get("variant") ?? "effectful"; |
| 54 | |
| 55 | if (url.pathname === "/boot") { |
| 56 | // Time the WHOLE thing: DO creation, eager container start during |
| 57 | // layer construction, and the readiness probe inside boot(). |
| 58 | const start = yield* Effect.sync(() => Date.now()); |
| 59 | yield* objectFor(variant).getByName(name).boot(); |
| 60 | const readyMs = (yield* Effect.sync(() => Date.now())) - start; |
| 61 | return yield* HttpServerResponse.json({ readyMs }); |
| 62 | } |
| 63 | if (url.pathname === "/shutdown") { |
| 64 | yield* objectFor(variant).getByName(name).shutdown(); |
| 65 | return yield* HttpServerResponse.json({ ok: true }); |
| 66 | } |
| 67 | return HttpServerResponse.text("ok"); |
| 68 | }).pipe( |
| 69 | Effect.catch((err) => |
| 70 | Effect.succeed(HttpServerResponse.text(String(err), { status: 500 })), |
| 71 | ), |
| 72 | ), |
| 73 | }; |
| 74 | }), |
| 75 | ) {} |