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
- Retry once the current lock holder finishes; check the daemon logs to confirm the holder is actually progressing (usually transient contention)
- Reduce overlap: pause the repository's daemons or scheduled jobs before running manual bin/repository update or indexing
- 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
- Stagger daemon schedules so discovery and pull for one repository do not overlap
- Run manual bin/repository maintenance with that repository's daemons paused
- Monitor lock wait durations: sustained waits indicate a stuck holder, not normal contention
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
- Failed to acquire write lock after waiting %s second(s). You
- Trying to refund a charge which is already refunding!
- This host has device ID "%s", but there is no corresponding
- Configuration file specifies cluster peer ("%s", at index "%
- Configuration file specifies cluster peer "%s" more than onc
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/57aefad202355bd1.
Report an issue: GitHub.