( versionFilePath: string, callback: () => void | Promise<void>, retries = 0, )
| 179 | // Execute a callback while holding a lock on a version file |
| 180 | // Returns false if the file is already locked, true if callback executed |
| 181 | async function tryWithVersionLock( |
| 182 | versionFilePath: string, |
| 183 | callback: () => void | Promise<void>, |
| 184 | retries = 0, |
| 185 | ): Promise<boolean> { |
| 186 | const dirs = getBaseDirectories() |
| 187 | |
| 188 | const lockfilePath = getLockFilePathFromVersionPath(dirs, versionFilePath) |
| 189 | |
| 190 | // Ensure the locks directory exists |
| 191 | await mkdir(dirs.locks, { recursive: true }) |
| 192 | |
| 193 | if (isPidBasedLockingEnabled()) { |
| 194 | // Use PID-based locking with optional retries |
| 195 | let attempts = 0 |
| 196 | const maxAttempts = retries + 1 |
| 197 | const minTimeout = retries > 0 ? 1000 : 100 |
| 198 | const maxTimeout = retries > 0 ? 5000 : 500 |
| 199 | |
| 200 | while (attempts < maxAttempts) { |
| 201 | const success = await withLock( |
| 202 | versionFilePath, |
| 203 | lockfilePath, |
| 204 | async () => { |
| 205 | try { |
| 206 | await callback() |
| 207 | } catch (error) { |
| 208 | logError(error) |
| 209 | throw error |
| 210 | } |
| 211 | }, |
| 212 | ) |
| 213 | |
| 214 | if (success) { |
| 215 | logEvent('tengu_version_lock_acquired', { |
| 216 | is_pid_based: true, |
| 217 | is_lifetime_lock: false, |
| 218 | attempts: attempts + 1, |
| 219 | }) |
| 220 | return true |
| 221 | } |
| 222 | |
| 223 | attempts++ |
| 224 | if (attempts < maxAttempts) { |
| 225 | // Wait before retrying with exponential backoff |
| 226 | const timeout = Math.min( |
| 227 | minTimeout * Math.pow(2, attempts - 1), |
| 228 | maxTimeout, |
| 229 | ) |
| 230 | await sleep(timeout) |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | logEvent('tengu_version_lock_failed', { |
| 235 | is_pid_based: true, |
| 236 | is_lifetime_lock: false, |
| 237 | attempts: maxAttempts, |
| 238 | }) |
no test coverage detected