From 3fc6f8b943cf2f1e1a550c136643cf5919ca5414 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Fri, 10 Apr 2026 11:37:59 -1000 Subject: [PATCH] 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) --- src/core/db.ts | 8 +++++++- src/core/postgres-engine.ts | 17 ++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/core/db.ts b/src/core/db.ts index 36b4a6c..69f8ecd 100644 --- a/src/core/db.ts +++ b/src/core/db.ts @@ -60,7 +60,13 @@ export async function disconnect(): Promise { export async function initSchema(): Promise { 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(fn: (tx: ReturnType) => Promise): Promise { diff --git a/src/core/postgres-engine.ts b/src/core/postgres-engine.ts index eb9ca7c..c1332f6 100644 --- a/src/core/postgres-engine.ts +++ b/src/core/postgres-engine.ts @@ -57,12 +57,19 @@ export class PostgresEngine implements BrainEngine { async initSchema(): Promise { 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)`; } }