109 lines · 4.4 KB · TypeScript
RawDownload
// Contract: the postgres pool finalizer must AWAIT the connection teardown.//// The cloud MCP auth seam (`makeMcpOrganizationAuthServices`) builds a fresh// postgres pool on EVERY `/mcp` request and closes it in its `acquireRelease`// finalizer. That finalizer used to be fire-and-forget (`Effect.runFork(// sql.end({ timeout: 0 }))`): it returned before the socket was torn down, so// under sustained MCP load closed-but-unreaped sockets piled up against the// dev PGlite server (effectively single-connection) faster than it reaped// them. New connects queued behind the backlog, request latency climbed into// the tens of seconds, and the e2e cloud dev stack hung after a few minutes:// the CI cascade flake.//// These tests characterize the contract the old fire-and-forget close violated// (`closePostgres` itself is new alongside them): it must (a) call `sql.end`// with a NON-zero drain window (a clean Terminate, not an abandon) and (b)// return an Effect that does not complete until `sql.end` has resolved, even// when the teardown takes real wall-clock time. All asserted with a fake `sql`// whose `end()` completion is observable, so the tests are fast and need no// live database. import { describe, expect, it } from "@effect/vitest";import { Effect, Exit } from "effect"; import { POSTGRES_END_TIMEOUT_SECONDS, closePostgres } from "./db"; describe("closePostgres", () => {  it.effect("passes a non-zero drain window to sql.end (clean Terminate, not abandon)", () =>    Effect.gen(function* () {      let received: { timeout?: number } | undefined;      const fakeSql = {        end: (options?: { timeout?: number }) => {          received = options;          return Promise.resolve();        },      };       yield* closePostgres(fakeSql);       expect(received?.timeout).toBe(POSTGRES_END_TIMEOUT_SECONDS);      expect(POSTGRES_END_TIMEOUT_SECONDS).toBeGreaterThan(0);    }),  );   it.effect("does not complete until sql.end resolves (awaits the teardown)", () =>    Effect.gen(function* () {      // `end` records an ordering marker only after an async tick. If      // `closePostgres` awaits it, the "close completed" marker lands AFTER the      // "end resolved" marker. The old fire-and-forget close returned before      // `end` ran, so "close completed" would land FIRST.      const order: string[] = [];      const fakeSql = {        end: () =>          // Defer resolution across a microtask so a non-awaiting close would          // observably finish before this runs.          Promise.resolve()            .then(() => Promise.resolve())            .then(() => {              order.push("end-resolved");            }),      };       yield* closePostgres(fakeSql);      order.push("close-completed");       // Awaiting the teardown means end resolved strictly before close returned.      expect(order).toEqual(["end-resolved", "close-completed"]);    }),  );   it.effect("awaits a teardown that takes real wall-clock time (bounded by the ceiling)", () =>    Effect.gen(function* () {      // `end` resolves only after a real timer delay, not just a microtask.      // This pins the "we await to completion, the timeout is only a ceiling"      // contract: closePostgres must stay suspended across the delay rather      // than resolving early.      const order: string[] = [];      const fakeSql = {        end: () =>          new Promise<void>((resolve) => {            setTimeout(() => {              order.push("end-resolved");              resolve();            }, 75);          }),      };       const startedAt = Date.now();      yield* closePostgres(fakeSql);      order.push("close-completed");       expect(order).toEqual(["end-resolved", "close-completed"]);      // Slack of a few ms: platform timers may fire marginally early.      expect(Date.now() - startedAt).toBeGreaterThanOrEqual(70);    }),  );   it.effect("swallows sql.end failures (a teardown error must not fail the request scope)", () =>    Effect.gen(function* () {      const fakeSql = {        // A rejected teardown (connection already gone) must not surface as a        // scope failure.        // oxlint-disable-next-line executor/no-promise-reject -- test fake: model `sql.end` (a raw postgres.js promise) rejecting        end: () => new Promise<void>((_resolve, reject) => reject("connection already gone")),      };      const exit = yield* Effect.exit(closePostgres(fakeSql));      expect(Exit.isSuccess(exit)).toBe(true);    }),  );});