phacility/phabricator · error · PhutilProxyException

Failed to acquire read lock after waiting %s second(s). You

Error message

Failed to acquire read lock after waiting %s second(s). You may be able to retry later. (%s)

What it means

Before reading a clustered working copy, the engine acquires a per-repository global read lock with a bounded wait; PhutilLockException on timeout is re-thrown as a PhutilProxyException carrying both the wait budget and the underlying lock hint. Another process or device held the repository's read lock longer than lock_wait seconds, so this reader gave up before starting.

Source

Thrown at src/applications/diffusion/protocol/DiffusionRepositoryClusterEngine.php:192

        $device->getName()));

    try {
      $start = PhabricatorTime::getNow();
      $read_lock->lock($lock_wait);
      $waited = (PhabricatorTime::getNow() - $start);

      if ($waited) {
        $this->logLine(
          pht(
            'Acquired read lock after %s second(s).',
            new PhutilNumber($waited)));
      } else {
        $this->logLine(
          pht(
            'Acquired read lock immediately.'));
      }
    } catch (PhutilLockException $ex) {
      throw new PhutilProxyException(
        pht(
          'Failed to acquire read lock after waiting %s second(s). You '.
          'may be able to retry later. (%s)',
          new PhutilNumber($lock_wait),
          $ex->getHint()),
        $ex);
    }

    $versions = PhabricatorRepositoryWorkingCopyVersion::loadVersions(
      $repository_phid);
    $versions = mpull($versions, null, 'getDevicePHID');

    $this_version = idx($versions, $device_phid);
    if ($this_version) {
      $this_version = (int)$this_version->getRepositoryVersion();
    } else {
      $this_version = null;
    }

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Retry once the current lock holder finishes; check the daemon logs to confirm the holder is actually progressing (usually transient contention)
  2. Reduce overlap: pause the repository's daemons or scheduled jobs before running manual bin/repository update or indexing
  3. If contention never clears, find the stuck holder (bin/phd status, daemon logs) and restart that daemon to release a leaked lock
Defensive patterns

Strategy: retry

Validate before calling

// reduce contention before dispatch: skip if a sync job for this repo is already running
$running = id(new PhabricatorWorkerActiveTaskQuery())
  ->setViewer(PhabricatorUser::getOmnipotentUser())
  ->withClass('RepositoryUpdateWorker')
  ->execute();

Try / catch

$attempts = 3;
for ($i = 0; $i < $attempts; $i++) {
  try {
    return $engine->synchronizeWorkingCopyBeforeRead();
  } catch (PhutilProxyException $ex) {
    if (!($ex->getPrevious() instanceof PhutilLockException)) {
      throw $ex;
    }
    sleep(5 * (2 ** $i)); // exponential backoff around lock contention
  }
}
throw $ex;

Prevention

When it happens

Trigger: Two or more actors contend for the same repository lock: pull daemons, discovery, a push on another cluster device, or a manual 'bin/repository update' run while a big clone/sync holds the lock past the wait budget.

Common situations: Initial imports or very large repositories where sync takes hours; overlapping daemon jobs; stale locks left by killed daemons; operators running maintenance commands during busy periods.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/57aefad202355bd1. Report an issue: GitHub.