diff --git a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php index 3e5b70cc05..f5960e9504 100644 --- a/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php +++ b/src/infrastructure/daemon/garbagecollector/PhabricatorGarbageCollector.php @@ -1,155 +1,175 @@ hasAutomaticPolicy()) { throw new Exception( pht( 'Can not get retention policy of collector with automatic '. 'policy.')); } $config = PhabricatorEnv::getEnvConfig('phd.garbage-collection'); $const = $this->getCollectorConstant(); return idx($config, $const, $this->getDefaultRetentionPolicy()); } /** * Get a unique string constant identifying this collector. * * @return string Collector constant. * @task info */ final public function getCollectorConstant() { return $this->getPhobjectClassConstant('COLLECTORCONST', 64); } /* -( Collecting Garbage )------------------------------------------------- */ /** * Run the collector. * * @return bool True if there is more garbage to collect. * @task collect */ final public function runCollector() { // Don't do anything if this collector is configured with an indefinite // retention policy. if (!$this->hasAutomaticPolicy()) { $policy = $this->getRetentionPolicy(); if (!$policy) { return false; } } - return $this->collectGarbage(); + // Hold a lock while performing collection to avoid racing other daemons + // running the same collectors. + $lock_name = 'gc:'.$this->getCollectorConstant(); + $lock = PhabricatorGlobalLock::newLock($lock_name); + + try { + $lock->lock(5); + } catch (PhutilLockException $ex) { + return false; + } + + try { + $result = $this->collectGarbage(); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + + return $result; } /** * Collect garbage from whatever source this GC handles. * * @return bool True if there is more garbage to collect. * @task collect */ abstract protected function collectGarbage(); /** * Get the most recent epoch timestamp that is considered garbage. * * Records older than this should be collected. * * @return int Most recent garbage timestamp. * @task collect */ final protected function getGarbageEpoch() { if ($this->hasAutomaticPolicy()) { throw new Exception( pht( 'Can not get garbage epoch for a collector with an automatic '. 'collection policy.')); } $ttl = $this->getRetentionPolicy(); if (!$ttl) { throw new Exception( pht( 'Can not get garbage epoch for a collector with an indefinite '. 'retention policy.')); } return (PhabricatorTime::getNow() - $ttl); } /** * Load all of the available garbage collectors. * * @return list Garbage collectors. * @task collect */ final public static function getAllCollectors() { return id(new PhutilClassMapQuery()) ->setAncestorClass(__CLASS__) ->setUniqueMethod('getCollectorConstant') ->execute(); } }