UNPKG

@moonwall/cli

Version:

Testing framework for the Moon family of projects

84 lines (81 loc) 3.21 kB
// src/internal/effect/FileLock.ts import { FileSystem } from "@effect/platform"; import { Duration, Effect, Schedule } from "effect"; import * as os from "os"; // src/internal/effect/errors.ts import { Data } from "effect"; var PortDiscoveryError = class extends Data.TaggedError("PortDiscoveryError") { }; var NodeLaunchError = class extends Data.TaggedError("NodeLaunchError") { }; var NodeReadinessError = class extends Data.TaggedError("NodeReadinessError") { }; var ProcessError = class extends Data.TaggedError("ProcessError") { }; var StartupCacheError = class extends Data.TaggedError("StartupCacheError") { }; var FileLockError = class extends Data.TaggedError("FileLockError") { }; // src/internal/effect/FileLock.ts var LOCK_MAX_AGE = Duration.minutes(2); var LOCK_POLL_INTERVAL = Duration.millis(500); var isProcessAlive = (pid) => Effect.try(() => { process.kill(pid, 0); return true; }).pipe(Effect.orElseSucceed(() => false)); var isLockStale = (info) => Effect.gen(function* () { const isTimedOut = Date.now() - info.timestamp > Duration.toMillis(LOCK_MAX_AGE); if (isTimedOut) return true; const isSameHost = info.hostname === os.hostname(); if (!isSameHost) return false; const alive = yield* isProcessAlive(info.pid); return !alive; }); var cleanupStaleLock = (lockPath) => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const infoPath = `${lockPath}/lock.json`; const exists = yield* fs.exists(infoPath).pipe(Effect.orElseSucceed(() => false)); if (!exists) return; const content = yield* fs.readFileString(infoPath).pipe(Effect.orElseSucceed(() => "")); const info = yield* Effect.try(() => JSON.parse(content)).pipe( Effect.orElseSucceed(() => null) ); if (!info) return; const stale = yield* isLockStale(info); if (stale) { yield* fs.remove(lockPath, { recursive: true }).pipe(Effect.ignore); } }); var writeLockInfo = (lockPath) => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const info = { pid: process.pid, timestamp: Date.now(), hostname: os.hostname() }; yield* fs.writeFileString(`${lockPath}/lock.json`, JSON.stringify(info)).pipe(Effect.ignore); }); var tryAcquireLock = (lockPath) => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; yield* cleanupStaleLock(lockPath); yield* fs.makeDirectory(lockPath).pipe(Effect.mapError(() => new FileLockError({ reason: "acquisition_failed", lockPath }))); yield* writeLockInfo(lockPath); }); var acquireFileLock = (lockPath, timeout = Duration.minutes(2)) => tryAcquireLock(lockPath).pipe( Effect.retry(Schedule.fixed(LOCK_POLL_INTERVAL).pipe(Schedule.upTo(timeout))), Effect.catchAll(() => Effect.fail(new FileLockError({ reason: "timeout", lockPath }))) ); var releaseFileLock = (lockPath) => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; yield* fs.remove(lockPath, { recursive: true }).pipe(Effect.ignore); }); var withFileLock = (lockPath, effect, timeout = Duration.minutes(2)) => Effect.acquireUseRelease( acquireFileLock(lockPath, timeout), () => effect, () => releaseFileLock(lockPath) ); export { acquireFileLock, releaseFileLock, withFileLock };