fix: advisory lock in initSchema() prevents deadlock on concurrent DDL

When multiple processes call initSchema() concurrently (e.g., test setup +
CLI subprocess, or parallel workers during E2E tests), the schema SQL's
DROP TRIGGER + CREATE TRIGGER statements acquire AccessExclusiveLock on
different tables, causing deadlocks.

Fix: pg_advisory_lock(42) serializes all initSchema() calls within the
same database. The lock is session-scoped and released in a finally block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-04-10 11:37:59 -10:00
co-authored by Claude Opus 4.6
parent 37f512297a
commit 3fc6f8b943
2 changed files with 19 additions and 6 deletions
+7 -1
View File
@@ -60,7 +60,13 @@ export async function disconnect(): Promise<void> {
export async function initSchema(): Promise<void> {
const conn = getConnection();
await conn.unsafe(SCHEMA_SQL);
// Advisory lock prevents concurrent initSchema() calls from deadlocking
await conn`SELECT pg_advisory_lock(42)`;
try {
await conn.unsafe(SCHEMA_SQL);
} finally {
await conn`SELECT pg_advisory_unlock(42)`;
}
}
export async function withTransaction<T>(fn: (tx: ReturnType<typeof postgres>) => Promise<T>): Promise<T> {
+12 -5
View File
@@ -57,12 +57,19 @@ export class PostgresEngine implements BrainEngine {
async initSchema(): Promise<void> {
const conn = this.sql;
await conn.unsafe(SCHEMA_SQL);
// Advisory lock prevents concurrent initSchema() calls from deadlocking
// on DDL statements (DROP TRIGGER + CREATE TRIGGER acquire AccessExclusiveLock)
await conn`SELECT pg_advisory_lock(42)`;
try {
await conn.unsafe(SCHEMA_SQL);
// Run any pending migrations automatically
const { applied } = await runMigrations(this);
if (applied > 0) {
console.log(` ${applied} migration(s) applied`);
// Run any pending migrations automatically
const { applied } = await runMigrations(this);
if (applied > 0) {
console.log(` ${applied} migration(s) applied`);
}
} finally {
await conn`SELECT pg_advisory_unlock(42)`;
}
}