test(api): let a shutdown's closed connections leave pg_stat_activity #100
@@ -208,6 +208,29 @@ async function activeConnectionCount(databaseName: string): Promise<number> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** How long a shutdown's closed connections may take to leave `pg_stat_activity`. Far below pg's
|
||||||
|
* 10 s idle timeout, so a connection that is genuinely still open is still caught. */
|
||||||
|
const SHUTDOWN_SETTLE_MS = 3_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connections left on `databaseName` once a shutdown has had a moment to land.
|
||||||
|
*
|
||||||
|
* PostgreSQL ends a backend asynchronously: `pool.end()` resolves once the client side has closed,
|
||||||
|
* and the server can still list that backend for a moment afterwards. On a loaded CI runner that
|
||||||
|
* moment was long enough for an immediate count to see one. So this waits, briefly, for the count to
|
||||||
|
* reach zero; a leaked pooled client would stay idle and listed far past the deadline.
|
||||||
|
*/
|
||||||
|
async function connectionsLeftAfterShutdown(databaseName: string): Promise<number> {
|
||||||
|
const deadline = Date.now() + SHUTDOWN_SETTLE_MS;
|
||||||
|
for (;;) {
|
||||||
|
const count = await activeConnectionCount(databaseName);
|
||||||
|
if (count === 0 || Date.now() >= deadline) {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function createDisposableDatabase(label: string): Promise<{ name: string; url: string }> {
|
async function createDisposableDatabase(label: string): Promise<{ name: string; url: string }> {
|
||||||
const name = makeDatabaseName(label);
|
const name = makeDatabaseName(label);
|
||||||
await createDatabase(name);
|
await createDatabase(name);
|
||||||
@@ -1349,7 +1372,7 @@ describe.sequential("live API — signup, sign-in, principal, sign-out", () => {
|
|||||||
await running.shutdown();
|
await running.shutdown();
|
||||||
|
|
||||||
await expect(fetch(origin, { signal: AbortSignal.timeout(1_000) })).rejects.toBeTruthy();
|
await expect(fetch(origin, { signal: AbortSignal.timeout(1_000) })).rejects.toBeTruthy();
|
||||||
const remainingConnections = await activeConnectionCount(database.name);
|
const remainingConnections = await connectionsLeftAfterShutdown(database.name);
|
||||||
expect(remainingConnections).toBe(0);
|
expect(remainingConnections).toBe(0);
|
||||||
|
|
||||||
await dropDatabase(database.name);
|
await dropDatabase(database.name);
|
||||||
@@ -2230,7 +2253,7 @@ describe.sequential("live API — platform owner and delegated grant management"
|
|||||||
|
|
||||||
it("shuts down cleanly, closing the one shared pool", async () => {
|
it("shuts down cleanly, closing the one shared pool", async () => {
|
||||||
await running.shutdown();
|
await running.shutdown();
|
||||||
const remainingConnections = await activeConnectionCount(database.name);
|
const remainingConnections = await connectionsLeftAfterShutdown(database.name);
|
||||||
expect(remainingConnections).toBe(0);
|
expect(remainingConnections).toBe(0);
|
||||||
await dropDatabase(database.name);
|
await dropDatabase(database.name);
|
||||||
});
|
});
|
||||||
@@ -2837,7 +2860,7 @@ describe.sequential("live API — installed module permission end-to-end", () =>
|
|||||||
|
|
||||||
it("shuts down cleanly, then restarting with the module absent rejects new grants for its former permission, returns NOT_FOUND for its former route, while the historical grant remains listable", async () => {
|
it("shuts down cleanly, then restarting with the module absent rejects new grants for its former permission, returns NOT_FOUND for its former route, while the historical grant remains listable", async () => {
|
||||||
await running.shutdown();
|
await running.shutdown();
|
||||||
const remainingConnections = await activeConnectionCount(database.name);
|
const remainingConnections = await connectionsLeftAfterShutdown(database.name);
|
||||||
expect(remainingConnections).toBe(0);
|
expect(remainingConnections).toBe(0);
|
||||||
// Exactly one stop, matching the exactly-one register/start already asserted above.
|
// Exactly one stop, matching the exactly-one register/start already asserted above.
|
||||||
expect(recorder.events.filter((event) => event === "calendar:stop")).toHaveLength(1);
|
expect(recorder.events.filter((event) => event === "calendar:stop")).toHaveLength(1);
|
||||||
|
|||||||
@@ -195,6 +195,29 @@ async function activeConnectionCount(databaseName: string): Promise<number> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** How long a shutdown's closed connections may take to leave `pg_stat_activity`. Far below pg's
|
||||||
|
* 10 s idle timeout, so a connection that is genuinely still open is still caught. */
|
||||||
|
const SHUTDOWN_SETTLE_MS = 3_000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connections left on `databaseName` once a shutdown has had a moment to land.
|
||||||
|
*
|
||||||
|
* PostgreSQL ends a backend asynchronously: `pool.end()` resolves once the client side has closed,
|
||||||
|
* and the server can still list that backend for a moment afterwards. On a loaded CI runner that
|
||||||
|
* moment was long enough for an immediate count to see one. So this waits, briefly, for the count to
|
||||||
|
* reach zero; a leaked pooled client would stay idle and listed far past the deadline.
|
||||||
|
*/
|
||||||
|
async function connectionsLeftAfterShutdown(databaseName: string): Promise<number> {
|
||||||
|
const deadline = Date.now() + SHUTDOWN_SETTLE_MS;
|
||||||
|
for (;;) {
|
||||||
|
const count = await activeConnectionCount(databaseName);
|
||||||
|
if (count === 0 || Date.now() >= deadline) {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function createDisposableDatabase(label: string): Promise<{ name: string; url: string }> {
|
async function createDisposableDatabase(label: string): Promise<{ name: string; url: string }> {
|
||||||
const name = makeDatabaseName(label);
|
const name = makeDatabaseName(label);
|
||||||
await createDatabase(name);
|
await createDatabase(name);
|
||||||
@@ -760,7 +783,7 @@ describe.sequential("live API — module database access through ModuleRuntimeSe
|
|||||||
|
|
||||||
await running.shutdown();
|
await running.shutdown();
|
||||||
|
|
||||||
const remainingConnections = await activeConnectionCount(database.name);
|
const remainingConnections = await connectionsLeftAfterShutdown(database.name);
|
||||||
expect(remainingConnections).toBe(0);
|
expect(remainingConnections).toBe(0);
|
||||||
|
|
||||||
const eventsAfterShutdown = readRecorder();
|
const eventsAfterShutdown = readRecorder();
|
||||||
|
|||||||
Reference in New Issue
Block a user