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
154154 });
155155 });
156156
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+
173+describe("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+
157211 // ---------------------------------------------------------------------------
158212 // Regression test against the prod handler factory. If anyone reverts
159213 // `makeApiLive` back to wiring `RequestScopedServicesLive` via