From fe2f2f6b2ad94a77c9efe6ebeeede5cb6a9a9abe Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 23 Jul 2026 11:24:25 -0700 Subject: [PATCH] Revert "fix: clarify PGLite data-dir lock contention (#2658)" This reverts commit 0556dbdc2ca0dee18d1d015d073f889d2b1c83c6. --- src/core/pglite-lock.ts | 47 ++++++++++++++-------------------------- test/pglite-lock.test.ts | 24 ++------------------ 2 files changed, 18 insertions(+), 53 deletions(-) diff --git a/src/core/pglite-lock.ts b/src/core/pglite-lock.ts index d2f0a5a43..9b00bfe7b 100644 --- a/src/core/pglite-lock.ts +++ b/src/core/pglite-lock.ts @@ -108,35 +108,6 @@ function isProcessAlive(pid: number): boolean { } } -function formatLockTimestamp(value: unknown): string { - return typeof value === 'number' && Number.isFinite(value) - ? new Date(value).toISOString() - : 'unknown time'; -} - -function pgliteLockTimeoutError(lockDir: string): Error { - const lockPath = join(lockDir, LOCK_FILE); - try { - const lockData = JSON.parse(readFileSync(lockPath, 'utf-8')); - const pid = String(lockData.pid ?? 'unknown'); - const command = String(lockData.command ?? 'unknown'); - const serveHint = command.includes('gbrain serve') - ? ' The holder looks like `gbrain serve`, so this is probably serve↔sync contention from an MCP/HTTP server; stop that server/client and rerun the command.' - : ''; - - return new Error( - `GBrain: Timed out waiting for PGLite data-dir lock. Process ${pid} has held it since ${formatLockTimestamp(lockData.acquired_at)} (command: ${command}). ` + - `Lock directory: ${lockDir}. If that process is dead, remove the lock directory and try again. ` + - `This is a PGLite data-dir lock, not the \`gbrain-sync:*\` advisory lock; \`gbrain sync --break-lock\` will not clear a live PGLite holder.` + - serveHint, - ); - } catch { - return new Error( - `GBrain: Timed out waiting for PGLite lock. Remove ${lockDir} and try again.` - ); - } -} - /** * Attempt to acquire an exclusive lock on the PGLite data directory. * Returns { acquired: true } if the lock was obtained, { acquired: false } otherwise. @@ -206,14 +177,28 @@ export async function acquireLock(dataDir: string | undefined, opts?: { timeoutM // mkdir failed — someone else grabbed it between our check and mkdir // This is fine, we'll retry if (Date.now() - startTime >= timeoutMs) { - throw pgliteLockTimeoutError(lockDir); + // Timeout — report which process holds the lock + const lockPath = join(lockDir, LOCK_FILE); + try { + const lockData = JSON.parse(readFileSync(lockPath, 'utf-8')); + throw new Error( + `GBrain: Timed out waiting for PGLite lock. Process ${lockData.pid} has held it since ${new Date(lockData.acquired_at).toISOString()} (command: ${lockData.command}). ` + + `If that process is dead, remove ${lockDir} and try again.` + ); + } catch (readErr) { + if (readErr instanceof Error && readErr.message.startsWith('GBrain')) throw readErr; + throw new Error( + `GBrain: Timed out waiting for PGLite lock. Remove ${lockDir} and try again.` + ); + } } // Brief wait before retry await new Promise(r => setTimeout(r, 500)); } } - throw pgliteLockTimeoutError(lockDir); + // Should not reach here, but just in case + throw new Error(`GBrain: Timed out waiting for PGLite lock.`); } /** diff --git a/test/pglite-lock.test.ts b/test/pglite-lock.test.ts index b32f09102..5d2f472b8 100644 --- a/test/pglite-lock.test.ts +++ b/test/pglite-lock.test.ts @@ -109,7 +109,7 @@ describe('pglite-lock #2058 heartbeat + steal-grace', () => { if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true, force: true }); }); - function writeHolder(fields: { pid: number; acquiredAgoMs: number; refreshedAgoMs: number; command?: string }) { + function writeHolder(fields: { pid: number; acquiredAgoMs: number; refreshedAgoMs: number }) { const lockDir = join(TEST_DIR, '.gbrain-lock'); mkdirSync(lockDir, { recursive: true }); const now = Date.now(); @@ -117,7 +117,7 @@ describe('pglite-lock #2058 heartbeat + steal-grace', () => { pid: fields.pid, acquired_at: now - fields.acquiredAgoMs, refreshed_at: now - fields.refreshedAgoMs, - command: fields.command ?? 'test holder', + command: 'test holder', })); } @@ -146,26 +146,6 @@ describe('pglite-lock #2058 heartbeat + steal-grace', () => { expect(existsSync(join(TEST_DIR, '.gbrain-lock'))).toBe(true); }); - test('explains live gbrain serve contention is not a sync advisory lock', async () => { - writeHolder({ - pid: process.pid, - acquiredAgoMs: 60_000, - refreshedAgoMs: 0, - command: 'bun /Users/master/.bun/bin/gbrain serve', - }); - - let message = ''; - try { - await acquireLock(TEST_DIR, { timeoutMs: 100 }); - } catch (error) { - message = error instanceof Error ? error.message : String(error); - } - expect(message).toContain('serve↔sync contention'); - expect(message).toContain('not the `gbrain-sync:*` advisory lock'); - expect(message).toContain('`gbrain sync --break-lock` will not clear a live PGLite holder'); - expect(existsSync(join(TEST_DIR, '.gbrain-lock'))).toBe(true); - }); - test('[REGRESSION] releaseLock does NOT remove a lock that was stolen + re-acquired by another process', async () => { // We acquire, then simulate a steal: another process reaped us past grace // and now owns the lock (different pid + acquired_at). Our releaseLock must