fix(util): refresh flock heartbeat time (#45720)

This commit is contained in:
Kit Langton 2026-08-28 17:18:32 -04:00 committed by GitHub
parent d837ffe70f
commit b0c8a8c827
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 31 additions and 7 deletions

View file

@ -156,6 +156,29 @@ describe("util.effect-flock", () => {
}),
)
it.live(
"advances the heartbeat timestamp while the lock is held",
Effect.gen(function* () {
const flock = yield* EffectFlock.Service
const tmp = yield* Effect.promise(() => fs.mkdtemp(path.join(os.tmpdir(), "eflock-test-")))
const dir = path.join(tmp, "locks")
const heartbeat = path.join(lock(dir, "eflock:heartbeat"), "heartbeat")
yield* Effect.addFinalizer(() => Effect.promise(() => fs.rm(tmp, { recursive: true, force: true })))
yield* Effect.scoped(
Effect.gen(function* () {
yield* flock.acquire("eflock:heartbeat", dir, { staleMs: 300 })
yield* Effect.sleep(150)
const first = yield* Effect.promise(() => fs.stat(heartbeat))
yield* Effect.sleep(250)
const second = yield* Effect.promise(() => fs.stat(heartbeat))
expect(second.mtimeMs).toBeGreaterThan(first.mtimeMs)
}),
)
}),
)
it.live(
"breaks stale lock dirs",
Effect.gen(function* () {

View file

@ -284,13 +284,14 @@ export namespace EffectFlock {
)
// Heartbeat fiber — scoped, so it's interrupted before release runs
yield* fs
.utimes(handle.heartbeatPath, new Date(), new Date())
.pipe(
Effect.ignore,
Effect.repeat(Schedule.spaced(Math.max(100, Math.floor(staleMs / 3)))),
Effect.forkScoped,
)
yield* Effect.suspend(() => {
const now = new Date()
return fs.utimes(handle.heartbeatPath, now, now)
}).pipe(
Effect.ignore,
Effect.repeat(Schedule.spaced(Math.max(100, Math.floor(staleMs / 3)))),
Effect.forkScoped,
)
})
const withLock: Interface["withLock"] = Function.dual(