( versionPath: string, lockFilePath: string, )
| 236 | * Returns a release function if successful, null if the lock is already held |
| 237 | */ |
| 238 | export async function tryAcquireLock( |
| 239 | versionPath: string, |
| 240 | lockFilePath: string, |
| 241 | ): Promise<(() => void) | null> { |
| 242 | const fs = getFsImplementation() |
| 243 | const versionName = basename(versionPath) |
| 244 | |
| 245 | // Check if there's an existing active lock (including by our own process) |
| 246 | // Use isLockActive for consistency with cleanup - it checks both PID running AND |
| 247 | // validates it's actually a Claude process (to handle PID reuse scenarios) |
| 248 | if (isLockActive(lockFilePath)) { |
| 249 | const existingContent = readLockContent(lockFilePath) |
| 250 | logForDebugging( |
| 251 | `Cannot acquire lock for ${versionName} - held by PID ${existingContent?.pid}`, |
| 252 | ) |
| 253 | return null |
| 254 | } |
| 255 | |
| 256 | // Try to acquire the lock |
| 257 | const lockContent: VersionLockContent = { |
| 258 | pid: process.pid, |
| 259 | version: versionName, |
| 260 | execPath: process.execPath, |
| 261 | acquiredAt: Date.now(), |
| 262 | } |
| 263 | |
| 264 | try { |
| 265 | writeLockFile(lockFilePath, lockContent) |
| 266 | |
| 267 | // Verify we actually got the lock (race condition check) |
| 268 | const verifyContent = readLockContent(lockFilePath) |
| 269 | if (verifyContent?.pid !== process.pid) { |
| 270 | // Another process won the race |
| 271 | return null |
| 272 | } |
| 273 | |
| 274 | logForDebugging(`Acquired PID lock for ${versionName} (PID ${process.pid})`) |
| 275 | |
| 276 | // Return release function |
| 277 | return () => { |
| 278 | try { |
| 279 | // Only release if we still own the lock |
| 280 | const currentContent = readLockContent(lockFilePath) |
| 281 | if (currentContent?.pid === process.pid) { |
| 282 | fs.unlinkSync(lockFilePath) |
| 283 | logForDebugging(`Released PID lock for ${versionName}`) |
| 284 | } |
| 285 | } catch (error) { |
| 286 | logForDebugging(`Failed to release lock for ${versionName}: ${error}`) |
| 287 | } |
| 288 | } |
| 289 | } catch (error) { |
| 290 | logForDebugging(`Failed to acquire lock for ${versionName}: ${error}`) |
| 291 | return null |
| 292 | } |
| 293 | } |
| 294 | |
| 295 | /** |
no test coverage detected