1import * as AWS from "alchemy/AWS";
2import * as Cloudflare from "alchemy/Cloudflare";
3import * as Effect from "effect/Effect";
4import * as Layer from "effect/Layer";
5import type * as Redacted from "effect/Redacted";
6import * as Schedule from "effect/Schedule";
7import * as FetchHttpClient from "effect/http/FetchHttpClient";
8import * as HttpBody from "effect/http/HttpBody";
9import * as HttpClient from "effect/http/HttpClient";
10import { HttpServerRequest } from "effect/http/HttpServerRequest";
11import * as HttpServerResponse from "effect/http/HttpServerResponse";
12import { BunMicrovm } from "./bun-image.ts";
13import { ExternalMicrovm } from "./external-image.ts";
14import { EffectfulBun } from "./effectful-bun.ts";
15import { EffectfulNode } from "./effectful-node.ts";
16import { NodeMicrovm } from "./node-image.ts";
17import { OpencodeMicrovm } from "./opencode-image.ts";
18
19/**
20 * Cloudflare Worker host for the MicroVM cold-start benchmark — the cross-cloud
21 * analog of the Lambda {@link import("./orchestrator.ts")}. Same boot/shutdown
22 * lifecycle and `readyMs` (time to usable service), but driven from a Worker:
23 * binding the AWS MicroVM ops provisions an IAM User + AccessKey + assume-role
24 * Role once per worker (see `MicrovmBinding.ts`), measuring the Worker → MicroVM
25 * cold-start path.
26 */
27type Variant = {
28 readonly run: (
29 req: AWS.Lambda.RunMicrovmRequest,
30 ) => Effect.Effect<{ microvmId: string; endpoint: string }, any>;
31 readonly get: (req: {
32 microvmIdentifier: string;
33 }) => Effect.Effect<{ state: string }, any>;
34 readonly auth: (req: {
35 microvmIdentifier: string;
36 expirationInMinutes: number;
37 allowedPorts: { port: number }[];
38 }) => Effect.Effect<
39 {
40 authToken: Record<string, string | Redacted.Redacted<string> | undefined>;
41 },
42 any
43 >;
44 readonly term: (req: {
45 microvmIdentifier: string;
46 }) => Effect.Effect<unknown, any>;
47 readonly reachable: (
48 endpoint: string,
49 authToken: Record<string, string | Redacted.Redacted<string> | undefined>,
50 ) => Effect.Effect<unknown, any, any>;
51};
52
53export default Cloudflare.Worker(
54 "MicrovmBenchWorker",
55 { main: import.meta.filename },
56 Effect.gen(function* () {
57 const rawReachable = (
58 endpoint: string,
59 authToken: Record<
60 string,
61 string | Redacted.Redacted<string> | undefined
62 >,
63 ) =>
64 Effect.gen(function* () {
65 const client = yield* HttpClient.HttpClient;
66 const res = yield* client.get(`https://${endpoint}/`, {
67 headers: AWS.Lambda.microvmAuthHeaders(authToken),
68 });
69 return yield* res.text;
70 });
71
72 const effectfulBun: Variant = {
73 run: yield* AWS.Lambda.RunMicrovm(EffectfulBun),
74 get: yield* AWS.Lambda.GetMicrovm(EffectfulBun),
75 auth: yield* AWS.Lambda.CreateAuthToken(EffectfulBun),
76 term: yield* AWS.Lambda.TerminateMicrovm(EffectfulBun),
77 reachable: (endpoint, authToken) =>
78 Effect.gen(function* () {
79 const sandbox = yield* AWS.Lambda.connectMicrovm(EffectfulBun, {
80 endpoint,
81 authToken,
82 });
83 return yield* sandbox.hello("bench");
84 }),
85 };
86 const effectfulNode: Variant = {
87 run: yield* AWS.Lambda.RunMicrovm(EffectfulNode),
88 get: yield* AWS.Lambda.GetMicrovm(EffectfulNode),
89 auth: yield* AWS.Lambda.CreateAuthToken(EffectfulNode),
90 term: yield* AWS.Lambda.TerminateMicrovm(EffectfulNode),
91 reachable: (endpoint, authToken) =>
92 Effect.gen(function* () {
93 const sandbox = yield* AWS.Lambda.connectMicrovm(EffectfulNode, {
94 endpoint,
95 authToken,
96 });
97 return yield* sandbox.hello("bench");
98 }),
99 };
100 const bun: Variant = {
101 run: yield* AWS.Lambda.RunMicrovm(BunMicrovm),
102 get: yield* AWS.Lambda.GetMicrovm(BunMicrovm),
103 auth: yield* AWS.Lambda.CreateAuthToken(BunMicrovm),
104 term: yield* AWS.Lambda.TerminateMicrovm(BunMicrovm),
105 reachable: rawReachable,
106 };
107 const node: Variant = {
108 run: yield* AWS.Lambda.RunMicrovm(NodeMicrovm),
109 get: yield* AWS.Lambda.GetMicrovm(NodeMicrovm),
110 auth: yield* AWS.Lambda.CreateAuthToken(NodeMicrovm),
111 term: yield* AWS.Lambda.TerminateMicrovm(NodeMicrovm),
112 reachable: rawReachable,
113 };
114 const external: Variant = {
115 run: yield* AWS.Lambda.RunMicrovm(ExternalMicrovm),
116 get: yield* AWS.Lambda.GetMicrovm(ExternalMicrovm),
117 auth: yield* AWS.Lambda.CreateAuthToken(ExternalMicrovm),
118 term: yield* AWS.Lambda.TerminateMicrovm(ExternalMicrovm),
119 reachable: rawReachable,
120 };
121 // opencode is "usable" when its health endpoint answers healthy AND a
122 // session can actually be created — a real write through the app, which
123 // proves the server is functional after the snapshot resume, not merely
124 // listening. The server requires basic auth (see
125 // contexts/microvm-opencode/Dockerfile), stacked on the MicroVM proxy
126 // auth headers.
127 const opencode: Variant = {
128 run: yield* AWS.Lambda.RunMicrovm(OpencodeMicrovm),
129 get: yield* AWS.Lambda.GetMicrovm(OpencodeMicrovm),
130 auth: yield* AWS.Lambda.CreateAuthToken(OpencodeMicrovm),
131 term: yield* AWS.Lambda.TerminateMicrovm(OpencodeMicrovm),
132 reachable: (endpoint, authToken) =>
133 Effect.gen(function* () {
134 const client = yield* HttpClient.HttpClient;
135 const headers = {
136 ...AWS.Lambda.microvmAuthHeaders(authToken),
137 // base64("opencode:bench")
138 authorization: "Basic b3BlbmNvZGU6YmVuY2g=",
139 };
140 const health = yield* client.get(
141 `https://${endpoint}/global/health`,
142 { headers },
143 );
144 const healthBody = yield* health.text;
145 if (health.status !== 200 || !healthBody.includes('"healthy":true')) {
146 return yield* Effect.fail(
147 new Error(
148 `opencode health ${health.status}: ${healthBody.slice(0, 120)}`,
149 ),
150 );
151 }
152 const session = yield* client.post(`https://${endpoint}/session`, {
153 headers,
154 body: HttpBody.text("{}", "application/json"),
155 });
156 const sessionBody = yield* session.text;
157 if (session.status !== 200 || !sessionBody.includes('"id"')) {
158 return yield* Effect.fail(
159 new Error(
160 `opencode session ${session.status}: ${sessionBody.slice(0, 120)}`,
161 ),
162 );
163 }
164 return sessionBody;
165 }),
166 };
167 const variants: Record<string, Variant> = {
168 "effectful-bun": effectfulBun,
169 "effectful-node": effectfulNode,
170 bun,
171 node,
172 external,
173 opencode,
174 };
175 const pick = (v: string | null): Variant => variants[v ?? ""] ?? effectfulBun;
176
177 const boot = (v: Variant) =>
178 Effect.gen(function* () {
179 const start = yield* Effect.sync(() => Date.now());
180 const vm = yield* v.run({
181 idlePolicy: {
182 maxIdleDurationSeconds: 900,
183 suspendedDurationSeconds: 300,
184 autoResumeEnabled: true,
185 },
186 });
187 return yield* Effect.gen(function* () {
188 yield* v.get({ microvmIdentifier: vm.microvmId }).pipe(
189 Effect.flatMap((m) =>
190 m.state === "RUNNING"
191 ? Effect.void
192 : Effect.fail(new Error(`microvm ${m.state}`)),
193 ),
194 Effect.retry({
195 schedule: Schedule.spaced("500 millis"),
196 times: 180,
197 }),
198 );
199 const { authToken } = yield* v.auth({
200 microvmIdentifier: vm.microvmId,
201 expirationInMinutes: 5,
202 allowedPorts: [{ port: 8080 }],
203 });
204 yield* v.reachable(vm.endpoint, authToken).pipe(
205 Effect.retry({
206 schedule: Schedule.exponential("250 millis"),
207 times: 14,
208 }),
209 );
210 const readyMs = (yield* Effect.sync(() => Date.now())) - start;
211 return yield* HttpServerResponse.json({ id: vm.microvmId, readyMs });
212 }).pipe(
213 Effect.onError(() =>
214 v.term({ microvmIdentifier: vm.microvmId }).pipe(Effect.ignore),
215 ),
216 Effect.provide(FetchHttpClient.layer),
217 );
218 });
219
220 return {
221 fetch: Effect.gen(function* () {
222 const request = yield* HttpServerRequest;
223 const url = new URL(request.url, "http://microvm");
224 const v = pick(url.searchParams.get("variant"));
225
226 if (url.pathname === "/boot") {
227 return yield* boot(v);
228 }
229 if (url.pathname === "/shutdown") {
230 const id = url.searchParams.get("id")!;
231 yield* v.term({ microvmIdentifier: id }).pipe(Effect.ignore);
232 return yield* HttpServerResponse.json({ ok: true });
233 }
234 return HttpServerResponse.text("ok");
235 }).pipe(Effect.orDie),
236 };
237 }).pipe(
238 Effect.provide(
239 Layer.mergeAll(
240 AWS.Lambda.RunMicrovmHttp,
241 AWS.Lambda.GetMicrovmHttp,
242 AWS.Lambda.CreateAuthTokenHttp,
243 AWS.Lambda.TerminateMicrovmHttp,
244 ),
245 ),
246 ),
247);