fix(db): widen the teardown regression test's timing budget

The reserved-connection test set an explicit 2000 ms timeout. Its
measured body cost is about 1027 ms, so the margin was 1.95 times.
Remove the explicit timeout and let the suite default (5000 ms)
apply. This raises the margin to about 4.9 times and keeps the
failure deterministic when the fix regresses.

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Priya Raman 2026-08-27 20:02:14 +00:00
parent f160e5b6dc
commit 93e85d2ba1
No known key found for this signature in database
GPG Key ID: 4861541D36B2037E
1 changed files with 40 additions and 45 deletions

View File

@ -50,57 +50,52 @@ describe("closeRegisteredClients", () => {
server = null;
});
it(
"ends a reserved connection before its backend dies, so no query can reach a null socket",
async () => {
const started = await startFakePostgresServer();
server = started.server;
const url = `postgres://test:test@127.0.0.1:${started.port}/test`;
it("ends a reserved connection before its backend dies, so no query can reach a null socket", async () => {
const started = await startFakePostgresServer();
server = started.server;
const url = `postgres://test:test@127.0.0.1:${started.port}/test`;
const db = createDb(url, { connectTimeoutSeconds: 5 });
// `sql.reserve()` pins one physical connection. Drizzle `db.transaction()`
// reaches the same surface through `sql.begin()`, so this stands in for a
// suite that left a transaction connection open.
const reserved = await db.$client.reserve();
const db = createDb(url, { connectTimeoutSeconds: 5 });
// `sql.reserve()` pins one physical connection. Drizzle `db.transaction()`
// reaches the same surface through `sql.begin()`, so this stands in for a
// suite that left a transaction connection open.
const reserved = await db.$client.reserve();
// The driver calls this only after it has fully processed a connection
// close: its socket reference cleared and any in-flight query failed.
// Waiting for it, instead of a fixed number of ticks, is what the
// historical crash reproduction does — it is real observed state from
// the driver, not a guess at timing.
const driverProcessedClose = new Promise<void>((resolve) => {
db.$client.options.onclose = () => resolve();
});
// The driver calls this only after it has fully processed a connection
// close: its socket reference cleared and any in-flight query failed.
// Waiting for it, instead of a fixed number of ticks, is what the
// historical crash reproduction does — it is real observed state from
// the driver, not a guess at timing.
const driverProcessedClose = new Promise<void>((resolve) => {
db.$client.options.onclose = () => resolve();
});
// This is the order our fixture owns: end every registered client for
// this host and port before a caller stops the cluster it points at.
await closeRegisteredClients(url);
// This is the order our fixture owns: end every registered client for
// this host and port before a caller stops the cluster it points at.
await closeRegisteredClients(url);
// Simulate the cluster stop that follows in the real fixture. Before the
// fix, killing the backend here while a client still held the reserved
// connection open crashed the process on a later deferred write.
for (const socket of started.backendSockets) socket.destroy();
await driverProcessedClose;
// Simulate the cluster stop that follows in the real fixture. Before the
// fix, killing the backend here while a client still held the reserved
// connection open crashed the process on a later deferred write.
for (const socket of started.backendSockets) socket.destroy();
await driverProcessedClose;
// A query sent only after the driver finished processing the close still
// buffers its frame for a deferred flush one tick later. If the fix let
// the reserved connection outlive the backend, that flush reaches a
// cleared socket reference and throws from inside the timer callback —
// this specific promise then never settles, because nothing on that
// path ever calls its resolve or reject. This test's own timeout (set
// below, on the `it` call) is what turns that hang into a reported
// failure, alongside the unhandled exception the crash raises
// separately.
const settled = await reserved`select 1`.catch((error: unknown) => error);
expect(settled).toBeInstanceOf(Error);
// A query sent only after the driver finished processing the close still
// buffers its frame for a deferred flush one tick later. If the fix let
// the reserved connection outlive the backend, that flush reaches a
// cleared socket reference and throws from inside the timer callback —
// this specific promise then never settles, because nothing on that
// path ever calls its resolve or reject. This test's own timeout (the
// suite default) is what turns that hang into a reported failure,
// alongside the unhandled exception the crash raises separately.
const settled = await reserved`select 1`.catch((error: unknown) => error);
expect(settled).toBeInstanceOf(Error);
// Let the deferred flush actually run. If it still fires against a null
// socket, it surfaces here as an unhandled error and fails this test
// file — the exact signature the fix protects against.
await new Promise((resolve) => setImmediate(() => setImmediate(resolve)));
},
2_000,
);
// Let the deferred flush actually run. If it still fires against a null
// socket, it surfaces here as an unhandled error and fails this test
// file — the exact signature the fix protects against.
await new Promise((resolve) => setImmediate(() => setImmediate(resolve)));
});
it("does nothing when no client is registered for a host and port", async () => {
await expect(closeRegisteredClients("postgres://test:test@127.0.0.1:1/test")).resolves.toBeUndefined();