* Acquire the lock. Throws if the lock is held by another live process.
()
| 235 | * Acquire the lock. Throws if the lock is held by another live process. |
| 236 | */ |
| 237 | acquire(): void { |
| 238 | // Check for existing lock |
| 239 | if (fs.existsSync(this.lockPath)) { |
| 240 | try { |
| 241 | const content = fs.readFileSync(this.lockPath, 'utf-8').trim(); |
| 242 | const pid = parseInt(content, 10); |
| 243 | const stat = fs.statSync(this.lockPath); |
| 244 | const lockAge = Date.now() - stat.mtimeMs; |
| 245 | |
| 246 | // Treat locks older than the timeout as stale, regardless of PID |
| 247 | if (lockAge < FileLock.STALE_TIMEOUT_MS && !isNaN(pid) && this.isProcessAlive(pid)) { |
| 248 | throw new Error( |
| 249 | `CodeGraph database is locked by another process (PID ${pid}). ` + |
| 250 | `If this is stale, run 'codegraph unlock' or delete ${this.lockPath}` |
| 251 | ); |
| 252 | } |
| 253 | |
| 254 | // Stale lock (dead process or timed out) - remove it |
| 255 | fs.unlinkSync(this.lockPath); |
| 256 | } catch (err) { |
| 257 | if (err instanceof Error && err.message.includes('locked by another')) { |
| 258 | throw err; |
| 259 | } |
| 260 | // Other errors reading lock file - try to remove it |
| 261 | try { fs.unlinkSync(this.lockPath); } catch { /* ignore */ } |
| 262 | } |
| 263 | } |
| 264 | |
| 265 | // Write our PID to the lock file using exclusive create flag |
| 266 | try { |
| 267 | fs.writeFileSync(this.lockPath, String(process.pid), { flag: 'wx' }); |
| 268 | this.held = true; |
| 269 | } catch (err: any) { |
| 270 | if (err.code === 'EEXIST') { |
| 271 | // Race condition: another process grabbed the lock between our check and write |
| 272 | throw new Error( |
| 273 | 'CodeGraph database is locked by another process. ' + |
| 274 | `If this is stale, run 'codegraph unlock' or delete ${this.lockPath}` |
| 275 | ); |
| 276 | } |
| 277 | throw err; |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | /** |
| 282 | * Release the lock |
no test coverage detected