1import * as AWS from "alchemy/AWS";
2import type * as microvms from "@distilled.cloud/aws/lambda-microvms";
3import * as Duration from "effect/Duration";
4import * as Effect from "effect/Effect";
5import * as Layer from "effect/Layer";
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 type * as HttpClientError from "effect/http/HttpClientError";
11import { HttpServerRequest } from "effect/http/HttpServerRequest";
12import * as HttpServerResponse from "effect/http/HttpServerResponse";
13import { BunMicrovm } from "./bun-image.ts";
14import { ExternalMicrovm } from "./external-image.ts";
15import { EffectfulBun } from "./effectful-bun.ts";
16import { EffectfulNode } from "./effectful-node.ts";
17import { NodeMicrovm } from "./node-image.ts";
18import { OpencodeMicrovm } from "./opencode-image.ts";
19
20/**
21 * Lambda host for the MicroVM cold-start benchmark. Exposes a boot/shutdown
22 * lifecycle so the driver can time each cold start from the OUTSIDE:
23 *
24 * - `GET /boot?variant=effectful|external&key=K` launches ONE fresh MicroVM and
25 * blocks until its in-VM service answers, returning `{ id, readyMs }` where
26 * `readyMs` = `RunMicrovm` → service-available (time to usable service). It
27 * does NOT terminate.
28 * - `GET /shutdown?variant=…&id=ID` terminates the MicroVM.
29 */
30type Variant = {
31 readonly run: (
32 req: AWS.Lambda.RunMicrovmRequest,
33 ) => Effect.Effect<microvms.RunMicrovmResponse, microvms.RunMicrovmError>;
34 readonly get: (
35 req: AWS.Lambda.GetMicrovmRequest,
36 ) => Effect.Effect<microvms.GetMicrovmResponse, microvms.GetMicrovmError>;
37 readonly auth: (
38 req: AWS.Lambda.CreateAuthTokenRequest,
39 ) => Effect.Effect<
40 microvms.CreateMicrovmAuthTokenResponse,
41 microvms.CreateMicrovmAuthTokenError
42 >;
43 readonly term: (
44 req: AWS.Lambda.TerminateMicrovmRequest,
45 ) => Effect.Effect<
46 microvms.TerminateMicrovmResponse,
47 microvms.TerminateMicrovmError
48 >;
49 readonly reachable: (
50 endpoint: string,
51 authToken: AWS.Lambda.MicrovmConnection["authToken"],
52 ) => Effect.Effect<
53 unknown,
54 HttpClientError.HttpClientError | Error,
55 HttpClient.HttpClient
56 >;
57};
58
59export default class Orchestrator extends AWS.Lambda.Function<Orchestrator>()(
60 "MicrovmBenchOrchestrator",
61 {
62 main: import.meta.filename,
63 functionUrl: true,
64 timeout: Duration.seconds(120),
65 },
66 Effect.gen(function* () {
67 // Plain-HTTP reachable check shared by every non-effectful (raw) image:
68 // hit `GET /` with the MicroVM auth headers and read the body.
69 const rawReachable =
70 (endpoint: string, authToken: AWS.Lambda.MicrovmConnection["authToken"]) =>
71 Effect.gen(function* () {
72 const client = yield* HttpClient.HttpClient;
73 const res = yield* client.get(`https://${endpoint}/`, {
74 headers: AWS.Lambda.microvmAuthHeaders(authToken),
75 });
76 return yield* res.text;
77 });
78
79 const effectfulBun: Variant = {
80 run: yield* AWS.Lambda.RunMicrovm(EffectfulBun),
81 get: yield* AWS.Lambda.GetMicrovm(EffectfulBun),
82 auth: yield* AWS.Lambda.CreateAuthToken(EffectfulBun),
83 term: yield* AWS.Lambda.TerminateMicrovm(EffectfulBun),
84 reachable: (endpoint, authToken) =>
85 Effect.gen(function* () {
86 const sandbox = yield* AWS.Lambda.connectMicrovm(EffectfulBun, {
87 endpoint,
88 authToken,
89 });
90 return yield* sandbox.hello("bench");
91 }),
92 };
93 const effectfulNode: Variant = {
94 run: yield* AWS.Lambda.RunMicrovm(EffectfulNode),
95 get: yield* AWS.Lambda.GetMicrovm(EffectfulNode),
96 auth: yield* AWS.Lambda.CreateAuthToken(EffectfulNode),
97 term: yield* AWS.Lambda.TerminateMicrovm(EffectfulNode),
98 reachable: (endpoint, authToken) =>
99 Effect.gen(function* () {
100 const sandbox = yield* AWS.Lambda.connectMicrovm(EffectfulNode, {
101 endpoint,
102 authToken,
103 });
104 return yield* sandbox.hello("bench");
105 }),
106 };
107 // Raw baselines: same image-build path + `GET /` reachable as each other,
108 // differing only by runtime (bun / node) or language (Python external).
109 const bun: Variant = {
110 run: yield* AWS.Lambda.RunMicrovm(BunMicrovm),
111 get: yield* AWS.Lambda.GetMicrovm(BunMicrovm),
112 auth: yield* AWS.Lambda.CreateAuthToken(BunMicrovm),
113 term: yield* AWS.Lambda.TerminateMicrovm(BunMicrovm),
114 reachable: rawReachable,
115 };
116 const node: Variant = {
117 run: yield* AWS.Lambda.RunMicrovm(NodeMicrovm),
118 get: yield* AWS.Lambda.GetMicrovm(NodeMicrovm),
119 auth: yield* AWS.Lambda.CreateAuthToken(NodeMicrovm),
120 term: yield* AWS.Lambda.TerminateMicrovm(NodeMicrovm),
121 reachable: rawReachable,
122 };
123 const external: Variant = {
124 run: yield* AWS.Lambda.RunMicrovm(ExternalMicrovm),
125 get: yield* AWS.Lambda.GetMicrovm(ExternalMicrovm),
126 auth: yield* AWS.Lambda.CreateAuthToken(ExternalMicrovm),
127 term: yield* AWS.Lambda.TerminateMicrovm(ExternalMicrovm),
128 reachable: rawReachable,
129 };
130 // opencode is "usable" when its health endpoint answers healthy AND a
131 // session can actually be created — a real write through the app, which
132 // proves the server is functional after the snapshot resume, not merely
133 // listening. The server requires basic auth (see
134 // contexts/microvm-opencode/Dockerfile), stacked on the MicroVM proxy
135 // auth headers.
136 const opencode: Variant = {
137 run: yield* AWS.Lambda.RunMicrovm(OpencodeMicrovm),
138 get: yield* AWS.Lambda.GetMicrovm(OpencodeMicrovm),
139 auth: yield* AWS.Lambda.CreateAuthToken(OpencodeMicrovm),
140 term: yield* AWS.Lambda.TerminateMicrovm(OpencodeMicrovm),
141 reachable: (endpoint, authToken) =>
142 Effect.gen(function* () {
143 const client = yield* HttpClient.HttpClient;
144 const headers = {
145 ...AWS.Lambda.microvmAuthHeaders(authToken),
146 // base64("opencode:bench")
147 authorization: "Basic b3BlbmNvZGU6YmVuY2g=",
148 };
149 const health = yield* client.get(
150 `https://${endpoint}/global/health`,
151 { headers },
152 );
153 const healthBody = yield* health.text;
154 if (health.status !== 200 || !healthBody.includes('"healthy":true')) {
155 return yield* Effect.fail(
156 new Error(
157 `opencode health ${health.status}: ${healthBody.slice(0, 120)}`,
158 ),
159 );
160 }
161 const session = yield* client.post(`https://${endpoint}/session`, {
162 headers,
163 body: HttpBody.text("{}", "application/json"),
164 });
165 const sessionBody = yield* session.text;
166 if (session.status !== 200 || !sessionBody.includes('"id"')) {
167 return yield* Effect.fail(
168 new Error(
169 `opencode session ${session.status}: ${sessionBody.slice(0, 120)}`,
170 ),
171 );
172 }
173 return sessionBody;
174 }),
175 };
176 const variants: Record<string, Variant> = {
177 "effectful-bun": effectfulBun,
178 "effectful-node": effectfulNode,
179 bun,
180 node,
181 external,
182 opencode,
183 };
184 const pick = (v: string | null): Variant => variants[v ?? ""] ?? effectfulBun;
185
186 // Run one fresh MicroVM and time RunMicrovm → service-reachable (readyMs).
187 // Leaves it running for an explicit /shutdown; self-terminates only if boot
188 // itself fails.
189 const boot = (v: Variant) =>
190 Effect.gen(function* () {
191 const start = yield* Effect.sync(() => Date.now());
192 const vm = yield* v.run({
193 idlePolicy: {
194 maxIdleDurationSeconds: 900,
195 suspendedDurationSeconds: 300,
196 autoResumeEnabled: true,
197 },
198 });
199 return yield* Effect.gen(function* () {
200 yield* v.get({ microvmIdentifier: vm.microvmId }).pipe(
201 Effect.flatMap((m) =>
202 m.state === "RUNNING"
203 ? Effect.void
204 : Effect.fail(new Error(`microvm ${m.state}`)),
205 ),
206 Effect.retry({
207 schedule: Schedule.spaced("500 millis"),
208 times: 180,
209 }),
210 );
211 const { authToken } = yield* v.auth({
212 microvmIdentifier: vm.microvmId,
213 expirationInMinutes: 5,
214 allowedPorts: [{ port: 8080 }],
215 });
216 yield* v.reachable(vm.endpoint, authToken).pipe(
217 Effect.retry({
218 schedule: Schedule.exponential("250 millis"),
219 times: 14,
220 }),
221 );
222 const readyMs = (yield* Effect.sync(() => Date.now())) - start;
223 return yield* HttpServerResponse.json({ id: vm.microvmId, readyMs });
224 }).pipe(
225 Effect.onError(() =>
226 v.term({ microvmIdentifier: vm.microvmId }).pipe(Effect.ignore),
227 ),
228 Effect.provide(FetchHttpClient.layer),
229 );
230 });
231
232 return {
233 fetch: Effect.gen(function* () {
234 const request = yield* HttpServerRequest;
235 const url = new URL(request.originalUrl);
236 const v = pick(url.searchParams.get("variant"));
237
238 if (request.method === "GET" && url.pathname === "/boot") {
239 return yield* boot(v);
240 }
241 if (request.method === "GET" && url.pathname === "/shutdown") {
242 const id = url.searchParams.get("id")!;
243 yield* v.term({ microvmIdentifier: id }).pipe(Effect.ignore);
244 return yield* HttpServerResponse.json({ ok: true });
245 }
246 return HttpServerResponse.text("ok");
247 }).pipe(Effect.orDie),
248 };
249 }).pipe(
250 Effect.provide(
251 Layer.mergeAll(
252 AWS.Lambda.RunMicrovmHttp,
253 AWS.Lambda.GetMicrovmHttp,
254 AWS.Lambda.CreateAuthTokenHttp,
255 AWS.Lambda.TerminateMicrovmHttp,
256 ),
257 ),
258 ),
259) {}