* Create a pool when the compiled worker exists (absent when running from * source in tests → callers use the sequential path), the kill switch is * off, and the machine has the cores AND memory to carry it. Returns null * otherwise. `CODEGRAPH_RESOLVE_WORKERS` overrides the computed size
(dbPath: string, projectRoot: string)
| 116 | * (0 disables the pool; values are capped at 16). |
| 117 | */ |
| 118 | static tryCreate(dbPath: string, projectRoot: string): ResolverPool | null { |
| 119 | if (process.env.CODEGRAPH_NO_PARALLEL_RESOLVE === '1') return null; |
| 120 | const workerScript = path.join(__dirname, 'resolver-worker.js'); |
| 121 | if (!fs.existsSync(workerScript)) return null; |
| 122 | let dbSizeBytes = 0; |
| 123 | try { |
| 124 | dbSizeBytes = fs.statSync(dbPath).size; |
| 125 | } catch { /* fresh/missing file — the 256MB per-worker floor applies */ } |
| 126 | const ap = os.availableParallelism(); |
| 127 | const budget = memoryBudgetBytes(); |
| 128 | const size = ResolverPool.resolvePoolSize({ |
| 129 | explicit: process.env.CODEGRAPH_RESOLVE_WORKERS, |
| 130 | availableParallelism: ap, |
| 131 | memoryBudget: budget, |
| 132 | dbSizeBytes, |
| 133 | }); |
| 134 | // Both outcomes log under SYNTH_TIMINGS — a silent null is how §7a.1's |
| 135 | // diagnostic run hid the memory-term misfire for a whole 25-minute cycle. |
| 136 | if (process.env.CODEGRAPH_SYNTH_TIMINGS) { |
| 137 | console.error( |
| 138 | `[pool-timing] pool ${size === null ? 'disabled' : `size=${size}`} (ap=${ap} budget=${Math.round(budget / 1024 / 1024)}MB db=${Math.round(dbSizeBytes / 1024 / 1024)}MB)` |
| 139 | ); |
| 140 | } |
| 141 | if (size === null) return null; |
| 142 | try { |
| 143 | return new ResolverPool(workerScript, dbPath, projectRoot, size); |
| 144 | } catch { |
| 145 | return null; |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | private constructor(workerScript: string, dbPath: string, projectRoot: string, size: number) { |
| 150 | for (let i = 0; i < size; i++) { |
no test coverage detected