Skip to content

EffectFlock heartbeat never refreshes, so locks held over 60 s can be broken #51179

Closed

Description

@iceteaSA

Description

EffectFlock.acquire never refreshes its heartbeat, so any lock held longer than 60 s looks abandoned and another process can break it while the holder is still working.

The heartbeat is started like this (packages/core/src/util/effect-flock.ts):

yield* fs
  .utimes(handle.heartbeatPath, new Date(), new Date())
  .pipe(Effect.ignore, Effect.repeat(Schedule.spaced(HEARTBEAT_MS)), Effect.forkScoped)

Effect.repeat runs the same effect again on every tick, and the two Dates were created once when that effect was built. So every tick sets the heartbeat back to the acquisition time. isStale treats a heartbeat older than STALE_MS (60 s) as a dead holder, so from 60 s after acquisition the lock can be taken by the next process that asks for it.

Holders that can run past 60 s include the npm install lock (npm-install:<dir> in packages/core/src/npm.ts) and the repository cache clone lock (packages/core/src/repository-cache.ts). If a second process asks for the same key during a slow install or clone, both proceed in the same directory.

Plugins

None needed.

OpenCode version

dev at 0f54984 (also 1.18.32)

Steps to reproduce

  1. Hold a lock with flock.acquire(key) for more than 20 s.
  2. Watch <lock dir>/heartbeat: its mtime never moves past the time the lock was taken, while HEARTBEAT_MS is 20 s.
  3. After 60 s, isStale reports the held lock as stale, so a second acquire(key) from another process breaks it and succeeds.

A regression test for step 2 is in the linked PR: with the lock held, backdate the heartbeat, advance the clock one interval, and check the heartbeat's age. Today it keeps the acquisition time.

Screenshot and/or share link

n/a

Operating System

Linux

Terminal

n/a

Activity

  1. neriousy commented on Oct 19, 2026

    @neriousy
    Member

    Fixed since v2.0.0: the file-lock heartbeat creates a fresh timestamp on every tick, so a long-running holder no longer looks stale because it keeps reusing the acquisition time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions