From 2e5353837a961751635f4109fae4b2d27f2bce71 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Sun, 21 Jun 2026 13:29:34 +0000 Subject: [PATCH] refactor(lbug): extract isSharedSingletonConn predicate for the lock gate (#2264) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The targetConn === conn object-identity gate (decides whether an op takes withConnLock) was duplicated inline in queryAndDrain and deleteNodesForFile with its own explanatory comments. Extract a single isSharedSingletonConn(c) predicate with the rationale in one place; both sites route through it. Behavior unchanged — covered by the lock-routing tests' positive (singleton locks) and negative (temp-conn skips) cases. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01JBJomjoTdBV2eveDVq4JMm --- gitnexus/src/core/lbug/lbug-adapter.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/gitnexus/src/core/lbug/lbug-adapter.ts b/gitnexus/src/core/lbug/lbug-adapter.ts index cf5f972ea..8b92ac032 100644 --- a/gitnexus/src/core/lbug/lbug-adapter.ts +++ b/gitnexus/src/core/lbug/lbug-adapter.ts @@ -190,6 +190,13 @@ let conn: lbug.Connection | null = null; // deliberately NOT wrapped — its per-row callback can re-enter the adapter and // it only runs on the read path where the checkpoint driver is inactive. // See conn-lock.ts for the full rationale. +// +// The gate that decides whether an op must take withConnLock: only operations on +// the shared singleton `conn` serialize. Per-file / temp connections (distinct +// native objects with no shared engine state) must NOT block on — or be blocked +// by — the singleton's lock. Reads the live `conn` binding at call time (it's +// reassigned only at open/close, never mid-load). +const isSharedSingletonConn = (c: lbug.Connection): boolean => c === conn; let currentDbPath: string | null = null; let currentDbReadOnly = false; @@ -490,10 +497,9 @@ const queryAndDrain = async (targetConn: lbug.Connection, cypher: string): Promi await drainQueryResult(queryResult); }; // Serialize only when this runs on the shared singleton connection (the bulk - // node/relationship COPY captures `writeConn = conn`). Per-file / temp - // connections are distinct objects with no shared native state, so they must - // not block on — or be blocked by — the singleton's lock. - return targetConn === conn ? withConnLock(run) : run(); + // node/relationship COPY captures `writeConn = conn`); per-file / temp + // connections skip the lock — see isSharedSingletonConn. + return isSharedSingletonConn(targetConn) ? withConnLock(run) : run(); }; const READ_ONLY_SHADOW_REPLAY_PROBE = 'MATCH (n) RETURN n LIMIT 1'; @@ -1992,7 +1998,9 @@ export const deleteNodesForFile = async ( const tn = escapeTableName(tableName); const countCypher = `MATCH (n:${tn}) WHERE n.filePath = '${escapedPath}' RETURN count(n) AS cnt`; const runCount = async () => readQueryRows(await targetConn!.query(countCypher)); - const rows = targetConn === conn ? await withConnLock(runCount) : await runCount(); + const rows = isSharedSingletonConn(targetConn!) + ? await withConnLock(runCount) + : await runCount(); const count = Number(rows[0]?.cnt ?? rows[0]?.[0] ?? 0); if (count > 0) {