(lockDir: string, key: string)
| 167 | type Handle = { token: string; metaPath: string; heartbeatPath: string; lockDir: string } |
| 168 | |
| 169 | const tryAcquireLockDir = (lockDir: string, key: string) => |
| 170 | Effect.gen(function* () { |
| 171 | const token = randomUUID() |
| 172 | const metaPath = path.join(lockDir, "meta.json") |
| 173 | const heartbeatPath = path.join(lockDir, "heartbeat") |
| 174 | |
| 175 | // Atomic mkdir — the POSIX lock primitive |
| 176 | const created = yield* atomicMkdir(lockDir) |
| 177 | |
| 178 | if (!created) { |
| 179 | if (!(yield* isStale(lockDir, heartbeatPath, metaPath))) return yield* new NotAcquired() |
| 180 | |
| 181 | // Stale — race for breaker ownership |
| 182 | const breakerPath = lockDir + ".breaker" |
| 183 | |
| 184 | const claimed = yield* fs.makeDirectory(breakerPath, { mode: 0o700 }).pipe( |
| 185 | Effect.as(true), |
| 186 | Effect.catchIf( |
| 187 | (e) => e.reason._tag === "AlreadyExists", |
| 188 | () => cleanStaleBreaker(breakerPath), |
| 189 | ), |
| 190 | Effect.catchIf(isPathGone, () => Effect.succeed(false)), |
| 191 | Effect.orDie, |
| 192 | ) |
| 193 | |
| 194 | if (!claimed) return yield* new NotAcquired() |
| 195 | |
| 196 | // We own the breaker — double-check staleness, nuke, recreate |
| 197 | const recreated = yield* Effect.gen(function* () { |
| 198 | if (!(yield* isStale(lockDir, heartbeatPath, metaPath))) return false |
| 199 | yield* forceRemove(lockDir) |
| 200 | return yield* atomicMkdir(lockDir) |
| 201 | }).pipe(Effect.ensuring(forceRemove(breakerPath))) |
| 202 | |
| 203 | if (!recreated) return yield* new NotAcquired() |
| 204 | } |
| 205 | |
| 206 | // We own the lock dir — write heartbeat + meta with exclusive create |
| 207 | yield* exclusiveWrite(heartbeatPath, "", lockDir, "heartbeat already existed") |
| 208 | |
| 209 | const metaJson = encodeMeta({ token, pid: process.pid, hostname, createdAt: new Date().toISOString() }) |
| 210 | yield* exclusiveWrite(metaPath, metaJson, lockDir, "meta.json already existed") |
| 211 | |
| 212 | return { token, metaPath, heartbeatPath, lockDir } satisfies Handle |
| 213 | }).pipe( |
| 214 | Effect.withSpan("EffectFlock.tryAcquire", { |
| 215 | attributes: { key }, |
| 216 | }), |
| 217 | ) |
| 218 | |
| 219 | // -- retry wrapper (preserves Handle type) -- |
| 220 |
no test coverage detected