History

Defer MCP session tool syncs to waitUntil and pin the HTTP plane as inline

Rhys Sullivan committed 2 months ago

Commit 2f2c2b01 parent 936f95e4 files · +190 −23Browse files

Showing the changes to apps/cloud/src/api.request-scope.node.test.ts (1 of 4 files). Show all

Unified · Split

apps/cloud/src/api.request-scope.node.test.ts+54−0
@@ -154,6 +154,60 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 153 unmodified lines
154 });154 });
155});155});
156 156
157// ---------------------------------------------------------------------------
158// WHEN the per-request resource dies, relative to the response bytes.
159//
160// `requestScopedMiddleware` wraps the ROUTE HANDLER in `Effect.scoped`, and the
161// response is written later still, by `HttpEffect.toHandled`'s callback. So the
162// postgres socket is already closed by the time the client has anything. That
163// is why cloud's HTTP plane leaves `ExecutorConfig.deferToolSync` unset (see
164// `engine/execution-stack.ts`): there is no window on this plane that is both
165// after the response and before the socket closes, so `waitUntil` would only
166// ever hand a dead pool to the deferred batch.
167//
168// Pinned rather than assumed. If the request lifecycle is ever restructured so
169// the scope outlives the written response, this test fails and the deferral
170// becomes available — which is a thing to be told, not to rediscover.
171// ---------------------------------------------------------------------------
172
173describe("per-request resource lifetime vs the written response", () => {
174 it("releases the request-scoped resource before the response resolves", async () => {
175 const order: string[] = [];
176 const Tracked = Layer.effectDiscard(
177 Effect.acquireRelease(
178 Effect.sync(() => {
179 order.push("acquire");
180 }),
181 () =>
182 Effect.sync(() => {
183 order.push("release");
184 }),
185 ),
186 );
187 const Route = HttpRouter.add(
188 "GET",
189 "/",
190 Effect.sync(() => {
191 order.push("handler");
192 return HttpServerResponse.jsonUnsafe({ ok: true });
193 }),
194 );
195 const handler = HttpRouter.toWebHandler(
196 Route.pipe(
197 Layer.provide(requestScopedMiddleware(Tracked).layer),
198 Layer.provideMerge(HttpServer.layerServices),
199 ),
200 { disableLogger: true },
201 ).handler;
202
203 const response = await handler(new Request("http://test.local/"));
204 order.push("response");
205
206 expect(response.status).toBe(200);
207 expect(order).toEqual(["acquire", "handler", "release", "response"]);
208 });
209});
210
157// ---------------------------------------------------------------------------211// ---------------------------------------------------------------------------
158// Regression test against the prod handler factory. If anyone reverts212// Regression test against the prod handler factory. If anyone reverts
159// `makeApiLive` back to wiring `RequestScopedServicesLive` via213// `makeApiLive` back to wiring `RequestScopedServicesLive` via