mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-11 03:38:07 +00:00
fix: sequential enrichment queries + stale data detection
Fixes three related issues that cause SIGSEGV crashes and stale data: 1. Impact enrichment queries (Promise.all → sequential await) The impact() method ran 3 enrichment queries concurrently via Promise.all against the same LadybugDB connection pool. On arm64 macOS, concurrent native DB access triggers SIGSEGV. Changed to sequential await. Also caps IN-clause to 100 IDs to prevent oversized queries. (#285, #290, #292) 2. Silence stdout during query execution silenceStdout()/restoreStdout() only wrapped createConnection() and initLbug(). Now also wraps executeQuery() and executeParameterized() to prevent native stdout writes from corrupting the MCP stdio stream during all DB operations. (#285) 3. Stale data after re-index ensureInitialized() checked pool existence but never verified whether the underlying index was rebuilt. Now reads meta.json's indexedAt timestamp on each call and closes/re-opens the pool when the index has changed. (#297)
This commit is contained in:
parent
c14a78a341
commit
999fbf5b11
2 changed files with 54 additions and 27 deletions
|
|
@ -458,12 +458,14 @@ export const executeQuery = async (repoId: string, cypher: string): Promise<any[
|
|||
entry.lastUsed = Date.now();
|
||||
|
||||
const conn = await checkout(entry);
|
||||
silenceStdout();
|
||||
try {
|
||||
const queryResult = await withTimeout(conn.query(cypher), QUERY_TIMEOUT_MS, 'Query');
|
||||
const result = Array.isArray(queryResult) ? queryResult[0] : queryResult;
|
||||
const rows = await result.getAll();
|
||||
return rows;
|
||||
} finally {
|
||||
restoreStdout();
|
||||
checkin(entry, conn);
|
||||
}
|
||||
};
|
||||
|
|
@ -485,6 +487,7 @@ export const executeParameterized = async (
|
|||
entry.lastUsed = Date.now();
|
||||
|
||||
const conn = await checkout(entry);
|
||||
silenceStdout();
|
||||
try {
|
||||
const stmt = await withTimeout(conn.prepare(cypher), QUERY_TIMEOUT_MS, 'Prepare');
|
||||
if (!stmt.isSuccess()) {
|
||||
|
|
@ -496,6 +499,7 @@ export const executeParameterized = async (
|
|||
const rows = await result.getAll();
|
||||
return rows;
|
||||
} finally {
|
||||
restoreStdout();
|
||||
checkin(entry, conn);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -246,12 +246,30 @@ export class LocalBackend {
|
|||
// ─── Lazy LadybugDB Init ────────────────────────────────────────────
|
||||
|
||||
private async ensureInitialized(repoId: string): Promise<void> {
|
||||
// Always check the actual pool — the idle timer may have evicted the connection
|
||||
if (this.initializedRepos.has(repoId) && isLbugReady(repoId)) return;
|
||||
|
||||
const handle = this.repos.get(repoId);
|
||||
if (!handle) throw new Error(`Unknown repo: ${repoId}`);
|
||||
|
||||
// Check if the index was rebuilt since we opened the connection (#297).
|
||||
// Read meta.json's indexedAt and compare to the cached value — if it
|
||||
// changed, close the stale pool and re-initialize with the fresh index.
|
||||
if (this.initializedRepos.has(repoId) && isLbugReady(repoId)) {
|
||||
try {
|
||||
const metaPath = path.join(handle.storagePath, 'meta.json');
|
||||
const metaRaw = await fs.readFile(metaPath, 'utf-8');
|
||||
const meta = JSON.parse(metaRaw);
|
||||
if (meta.indexedAt && meta.indexedAt !== handle.indexedAt) {
|
||||
// Index was rebuilt — close stale connection and re-init
|
||||
await closeLbug(repoId);
|
||||
this.initializedRepos.delete(repoId);
|
||||
handle.indexedAt = meta.indexedAt;
|
||||
} else {
|
||||
return; // Pool is current
|
||||
}
|
||||
} catch {
|
||||
return; // Can't read meta — assume pool is fine
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await initLbug(repoId, handle.lbugPath);
|
||||
this.initializedRepos.add(repoId);
|
||||
|
|
@ -1438,31 +1456,36 @@ export class LocalBackend {
|
|||
let affectedModules: any[] = [];
|
||||
|
||||
if (impacted.length > 0) {
|
||||
const allIds = impacted.map(i => `'${i.id.replace(/'/g, "''")}'`).join(', ');
|
||||
const d1Ids = (grouped[1] || []).map((i: any) => `'${i.id.replace(/'/g, "''")}'`).join(', ');
|
||||
// Cap IN-clause to 100 IDs to prevent oversized queries that crash
|
||||
// the native DB engine on arm64 macOS (#292)
|
||||
const cappedImpacted = impacted.slice(0, 100);
|
||||
const allIds = cappedImpacted.map(i => `'${i.id.replace(/'/g, "''")}'`).join(', ');
|
||||
const d1Items = (grouped[1] || []).slice(0, 100);
|
||||
const d1Ids = d1Items.map((i: any) => `'${i.id.replace(/'/g, "''")}'`).join(', ');
|
||||
|
||||
// Affected processes: which execution flows are broken and at which step
|
||||
const [processRows, moduleRows, directModuleRows] = await Promise.all([
|
||||
executeQuery(repo.id, `
|
||||
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
||||
WHERE s.id IN [${allIds}]
|
||||
RETURN p.heuristicLabel AS name, COUNT(DISTINCT s.id) AS hits, MIN(r.step) AS minStep, p.stepCount AS stepCount
|
||||
ORDER BY hits DESC
|
||||
LIMIT 20
|
||||
`).catch(() => []),
|
||||
executeQuery(repo.id, `
|
||||
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
||||
WHERE s.id IN [${allIds}]
|
||||
RETURN c.heuristicLabel AS name, COUNT(DISTINCT s.id) AS hits
|
||||
ORDER BY hits DESC
|
||||
LIMIT 20
|
||||
`).catch(() => []),
|
||||
d1Ids ? executeQuery(repo.id, `
|
||||
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
||||
WHERE s.id IN [${d1Ids}]
|
||||
RETURN DISTINCT c.heuristicLabel AS name
|
||||
`).catch(() => []) : Promise.resolve([]),
|
||||
]);
|
||||
// Run enrichment queries sequentially to avoid concurrent native DB
|
||||
// access that causes SIGSEGV on arm64 macOS (#285, #290, #292)
|
||||
const processRows = await executeQuery(repo.id, `
|
||||
MATCH (s)-[r:CodeRelation {type: 'STEP_IN_PROCESS'}]->(p:Process)
|
||||
WHERE s.id IN [${allIds}]
|
||||
RETURN p.heuristicLabel AS name, COUNT(DISTINCT s.id) AS hits, MIN(r.step) AS minStep, p.stepCount AS stepCount
|
||||
ORDER BY hits DESC
|
||||
LIMIT 20
|
||||
`).catch(() => []);
|
||||
const moduleRows = await executeQuery(repo.id, `
|
||||
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
||||
WHERE s.id IN [${allIds}]
|
||||
RETURN c.heuristicLabel AS name, COUNT(DISTINCT s.id) AS hits
|
||||
ORDER BY hits DESC
|
||||
LIMIT 20
|
||||
`).catch(() => []);
|
||||
const directModuleRows = d1Ids
|
||||
? await executeQuery(repo.id, `
|
||||
MATCH (s)-[:CodeRelation {type: 'MEMBER_OF'}]->(c:Community)
|
||||
WHERE s.id IN [${d1Ids}]
|
||||
RETURN DISTINCT c.heuristicLabel AS name
|
||||
`).catch(() => [])
|
||||
: [];
|
||||
|
||||
affectedProcesses = processRows.map((r: any) => ({
|
||||
name: r.name || r[0],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue