{"record":{"id":"15a9fe615421f0ef","repo":"phacility/phabricator","slug":"unable-to-acquire-slot-locks-s","errorCode":null,"errorMessage":"Unable to acquire slot locks: %s.","messagePattern":"Unable to acquire slot locks: (.+?)\\.","errorType":"exception","errorClass":"DrydockSlotLockException","httpStatus":null,"severity":"warning","filePath":"src/applications/drydock/storage/DrydockSlotLock.php","lineNumber":153,"sourceCode":"        $owner_phid,\n        PhabricatorHash::digestForIndex($lock),\n        $lock);\n    }\n\n    try {\n      queryfx(\n        $conn_w,\n        'INSERT INTO %T (ownerPHID, lockIndex, lockKey) VALUES %LQ',\n        $table->getTableName(),\n        $sql);\n    } catch (AphrontDuplicateKeyQueryException $ex) {\n      // Try to improve the readability of the exception. We might miss on\n      // this query if the lock has already been released, but most of the\n      // time we should be able to figure out which locks are already held.\n      $held = self::loadHeldLocks($locks);\n      $held = mpull($held, 'getOwnerPHID', 'getLockKey');\n\n      throw new DrydockSlotLockException($held);\n    }\n  }\n\n\n  /**\n   * Release all locks held by an owner.\n   *\n   * @param phid Lock owner PHID.\n   * @return void\n   * @task locks\n   */\n  public static function releaseLocks($owner_phid) {\n    $table = new DrydockSlotLock();\n    $conn_w = $table->establishConnection('w');\n\n    queryfx(\n      $conn_w,\n      'DELETE FROM %T WHERE ownerPHID = %s',","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/drydock/storage/DrydockSlotLock.php#L135-L171","documentation":"DrydockSlotLock::acquireLocks() inserts one row per lock into the slot-lock table; a duplicate key means another owner already holds at least one of the requested locks. It then loads the held locks and throws DrydockSlotLockException, whose message lists each lockKey and its owner PHID. Slot locks serialize exclusive operations like 'one allocation per blueprint' or 'N resources per host', so this is expected contention, not corruption.","triggerScenarios":"Two leases triggering concurrent allocations on the same blueprint (lock key like 'resource(<type>,<blueprint>)'); a resource hitting a per-host or per-allocation slot limit via needSlotLock(); an update worker and an allocation racing on the same resource PHID.","commonSituations":"Blueprint slot limits set too tight for real concurrency; many DrydockLeaseUpdateWorkers fanning out at once; stale locks left behind because a resource or lease was never destroyed (releaseLocks only runs on owner destruction); importing a large number of repositories simultaneously.","solutions":["Treat it as transient: yield and retry, the standard workers wait ~15 seconds and re-run.","Inspect the lock map in the exception message to see which lockKey and ownerPHID collide, then check that owner's state (is it a live allocation or a leaked lock?).","Raise or relax the blueprint's slot limits so concurrent allocations do not overlap on one lock.","Destroy abandoned resources/leases so their locks are released via DrydockSlotLock::releaseLocks()."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  DrydockSlotLock::acquireLocks($phid, $locks);\n} catch (DrydockSlotLockException $ex) {\n  $held = $ex->getLockMap(); // lockKey => ownerPHID\n  phlog(sprintf('Slot locks held: %s', implode(', ', array_keys($held))));\n  throw new PhabricatorWorkerYieldException(15); // retry later\n}","preventionTips":["Size blueprint slot limits for your real concurrency.","Destroy unused resources and leases so their slot locks are released.","Log getLockMap() whenever this exception fires to distinguish contention from leaked locks."],"tags":["drydock","php","locking","concurrency","contention"],"backgroundTag":"lock-contention","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}