protected function doWork() { $data = $this->getTaskData(); $object_phid = idx($data, 'documentPHID'); $object = $this->loadObjectForIndexing($object_phid); $engine = id(new PhabricatorIndexEngine())->setObject($object); $parameters = idx($data, 'parameters', array()); $engine->setParameters($parameters); if (!$engine->shouldIndexObject()) { return; } $key = "index.{$object_phid}"; $lock = PhabricatorGlobalLock::newLock($key); $lock->lock(1); try { // Reload the object now that we have a lock, to make sure we have the // most current version. $object = $this->loadObjectForIndexing($object->getPHID()); $engine->setObject($object); $engine->indexObject(); } catch (Exception $ex) { $lock->unlock(); if (!$ex instanceof PhabricatorWorkerPermanentFailureException) { $ex = new PhabricatorWorkerPermanentFailureException(pht('Failed to update search index for document "%s": %s', $object_phid, $ex->getMessage())); } throw $ex; } $lock->unlock(); }
public function publishNotifications() { $cursor = $this->getCursor(); $now = PhabricatorTime::getNow(); if ($cursor > $now) { return; } $calendar_class = 'PhabricatorCalendarApplication'; if (!PhabricatorApplication::isClassInstalled($calendar_class)) { return; } try { $lock = PhabricatorGlobalLock::newLock('calendar.notify')->lock(5); } catch (PhutilLockException $ex) { return; } $caught = null; try { $this->sendNotifications(); } catch (Exception $ex) { $caught = $ex; } $lock->unlock(); // Wait a little while before checking for new notifications to send. $this->setCursor($cursor + phutil_units('1 minute in seconds')); if ($caught) { throw $caught; } }
protected function doWork() { $resource_phid = $this->getTaskDataValue('resourcePHID'); $hash = PhabricatorHash::digestForIndex($resource_phid); $lock_key = 'drydock.resource:' . $hash; $lock = PhabricatorGlobalLock::newLock($lock_key)->lock(1); $resource = $this->loadResource($resource_phid); $this->updateResource($resource); $lock->unlock(); }
protected function newRepositoryLock(PhabricatorRepository $repository, $lock_key, $lock_device_only) { $lock_parts = array(); $lock_parts[] = $lock_key; $lock_parts[] = $repository->getID(); if ($lock_device_only) { $device = AlmanacKeys::getLiveDevice(); if ($device) { $lock_parts[] = $device->getID(); } } $lock_name = implode(':', $lock_parts); return PhabricatorGlobalLock::newLock($lock_name); }
protected function doWork() { $lease_phid = $this->getTaskDataValue('leasePHID'); $hash = PhabricatorHash::digestForIndex($lease_phid); $lock_key = 'drydock.lease:' . $hash; $lock = PhabricatorGlobalLock::newLock($lock_key)->lock(1); try { $lease = $this->loadLease($lease_phid); $this->handleUpdate($lease); } catch (Exception $ex) { $lock->unlock(); throw $ex; } $lock->unlock(); }
protected function doWork() { $resource_phid = $this->getTaskDataValue('resourcePHID'); $hash = PhabricatorHash::digestForIndex($resource_phid); $lock_key = 'drydock.resource:' . $hash; $lock = PhabricatorGlobalLock::newLock($lock_key)->lock(1); try { $resource = $this->loadResource($resource_phid); $this->handleUpdate($resource); } catch (Exception $ex) { $lock->unlock(); $this->flushDrydockTaskQueue(); throw $ex; } $lock->unlock(); }
public function execute(PhutilArgumentParser $args) { $this->setVerbose($args->getArg('verbose')); $console = PhutilConsole::getConsole(); $repos = $this->loadRepositories($args, 'repos'); if (count($repos) !== 1) { throw new PhutilArgumentUsageException(pht('Specify exactly one repository to update.')); } $repository = head($repos); try { $lock_name = 'repository.update:' . $repository->getID(); $lock = PhabricatorGlobalLock::newLock($lock_name); try { $lock->lock(); } catch (PhutilLockException $ex) { throw new PhutilProxyException(pht('Another process is currently holding the update lock for ' . 'repository "%s". Repositories may only be updated by one ' . 'process at a time. This can happen if you are running multiple ' . 'copies of the daemons. This can also happen if you manually ' . 'update a repository while the daemons are also updating it ' . '(in this case, just try again in a few moments).', $repository->getMonogram()), $ex); } try { $no_discovery = $args->getArg('no-discovery'); id(new PhabricatorRepositoryPullEngine())->setRepository($repository)->setVerbose($this->getVerbose())->pullRepository(); if ($no_discovery) { $lock->unlock(); return; } // TODO: It would be nice to discover only if we pulled something, but // this isn't totally trivial. It's slightly more complicated with // hosted repositories, too. $repository->writeStatusMessage(PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE, null); $this->discoverRepository($repository); $this->checkIfRepositoryIsFullyImported($repository); $this->updateRepositoryRefs($repository); $this->mirrorRepository($repository); $repository->writeStatusMessage(PhabricatorRepositoryStatusMessage::TYPE_FETCH, PhabricatorRepositoryStatusMessage::CODE_OKAY); } catch (Exception $ex) { $lock->unlock(); throw $ex; } } catch (Exception $ex) { $repository->writeStatusMessage(PhabricatorRepositoryStatusMessage::TYPE_FETCH, PhabricatorRepositoryStatusMessage::CODE_ERROR, array('message' => pht('Error updating working copy: %s', $ex->getMessage()))); throw $ex; } $lock->unlock(); $console->writeOut(pht('Updated repository **%s**.', $repository->getMonogram()) . "\n"); return 0; }
protected function doWork() { $item_phid = $this->getTaskDataValue('itemPHID'); $hash = PhabricatorHash::digestForIndex($item_phid); $lock_key = "nuance.item.{$hash}"; $lock = PhabricatorGlobalLock::newLock($lock_key); $lock->lock(1); try { $item = $this->loadItem($item_phid); $this->updateItem($item); $this->routeItem($item); $this->applyCommands($item); } catch (Exception $ex) { $lock->unlock(); throw $ex; } $lock->unlock(); }
public final function importFromSource() { if (!$this->shouldPullDataFromSource()) { return false; } $source = $this->getSource(); $key = $this->getCursorKey(); $parts = array('nsc', $source->getID(), PhabricatorHash::digestToLength($key, 20)); $lock_name = implode('.', $parts); $lock = PhabricatorGlobalLock::newLock($lock_name); $lock->lock(1); try { $more_data = $this->pullDataFromSource(); } catch (Exception $ex) { $lock->unlock(); throw $ex; } $lock->unlock(); return $more_data; }
public function execute(PhutilArgumentParser $args) { $this->setVerbose($args->getArg('verbose')); $console = PhutilConsole::getConsole(); $repos = $this->loadRepositories($args, 'repos'); if (count($repos) !== 1) { throw new PhutilArgumentUsageException(pht('Specify exactly one repository to update, by callsign.')); } $repository = head($repos); $callsign = $repository->getCallsign(); $no_discovery = $args->getArg('no-discovery'); id(new PhabricatorRepositoryPullEngine())->setRepository($repository)->setVerbose($this->getVerbose())->pullRepository(); if ($no_discovery) { return; } // TODO: It would be nice to discover only if we pulled something, but this // isn't totally trivial. It's slightly more complicated with hosted // repositories, too. $lock_name = get_class($this) . ':' . $callsign; $lock = PhabricatorGlobalLock::newLock($lock_name); $lock->lock(); try { $repository->writeStatusMessage(PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE, null); $this->discoverRepository($repository); $this->checkIfRepositoryIsFullyImported($repository); $this->updateRepositoryRefs($repository); $this->mirrorRepository($repository); $repository->writeStatusMessage(PhabricatorRepositoryStatusMessage::TYPE_FETCH, PhabricatorRepositoryStatusMessage::CODE_OKAY); } catch (Exception $ex) { $repository->writeStatusMessage(PhabricatorRepositoryStatusMessage::TYPE_FETCH, PhabricatorRepositoryStatusMessage::CODE_ERROR, array('message' => pht('Error updating working copy: %s', $ex->getMessage()))); $lock->unlock(); throw $ex; } $lock->unlock(); $console->writeOut(pht('Updated repository **%s**.', $repository->getMonogram()) . "\n"); return 0; }
protected function run() { // The trigger daemon is a low-level infrastructure daemon which schedules // and executes chronological events. Examples include a subscription which // generates a bill on the 12th of every month, or a reminder email 15 // minutes before a meeting. // Only one trigger daemon can run at a time, and very little work should // happen in the daemon process. In general, triggered events should // just schedule a task into the normal daemon worker queue and then // return. This allows the real work to take longer to execute without // disrupting other triggers. // The trigger mechanism guarantees that events will execute exactly once, // but does not guarantee that they will execute at precisely the specified // time. Under normal circumstances, they should execute within a minute or // so of the desired time, so this mechanism can be used for things like // meeting reminders. // If the trigger queue backs up (for example, because it is overwhelmed by // trigger updates, doesn't run for a while, or a trigger action is written // inefficiently) or the daemon queue backs up (usually for similar // reasons), events may execute an arbitrarily long time after they were // scheduled to execute. In some cases (like billing a subscription) this // may be desirable; in other cases (like sending a meeting reminder) the // action may want to check the current time and see if the event is still // relevant. // The trigger daemon works in two phases: // // 1. A scheduling phase processes recently updated triggers and // schedules them for future execution. For example, this phase would // see that a meeting trigger had been changed recently, determine // when the reminder for it should execute, and then schedule the // action to execute at that future date. // 2. An execution phase runs the actions for any scheduled events which // are due to execute. // // The major goal of this design is to deliver on the guarantee that events // will execute exactly once. It prevents race conditions in scheduling // and execution by ensuring there is only one writer for either of these // phases. Without this separation of responsibilities, web processes // trying to reschedule events after an update could race with other web // processes or the daemon. // We want to start the first GC cycle right away, not wait 4 hours. $this->nextCollection = PhabricatorTime::getNow(); do { PhabricatorCaches::destroyRequestCache(); $lock = PhabricatorGlobalLock::newLock('trigger'); try { $lock->lock(5); } catch (PhutilLockException $ex) { throw new PhutilProxyException(pht('Another process is holding the trigger lock. Usually, this ' . 'means another copy of the trigger daemon is running elsewhere. ' . 'Multiple processes are not permitted to update triggers ' . 'simultaneously.'), $ex); } // Run the scheduling phase. This finds updated triggers which we have // not scheduled yet and schedules them. $last_version = $this->loadCurrentCursor(); $head_version = $this->loadCurrentVersion(); // The cursor points at the next record to process, so we can only skip // this step if we're ahead of the version number. if ($last_version <= $head_version) { $this->scheduleTriggers($last_version); } // Run the execution phase. This finds events which are due to execute // and runs them. $this->executeTriggers(); $lock->unlock(); $sleep_duration = $this->getSleepDuration(); $sleep_duration = $this->runNuanceImportCursors($sleep_duration); $sleep_duration = $this->runGarbageCollection($sleep_duration); $sleep_duration = $this->runCalendarNotifier($sleep_duration); $this->sleep($sleep_duration); } while (!$this->shouldExit()); }
/** * Apply edge additions and removals queued by @{method:addEdge} and * @{method:removeEdge}. Note that transactions are opened, all additions and * removals are executed, and then transactions are saved. Thus, in some cases * it may be slightly more efficient to perform multiple edit operations * (e.g., adds followed by removals) if their outcomes are not dependent, * since transactions will not be held open as long. * * @return this * @task edit */ public function save() { $cycle_types = $this->getPreventCyclesEdgeTypes(); $locks = array(); $caught = null; try { // NOTE: We write edge data first, before doing any transactions, since // it's OK if we just leave it hanging out in space unattached to // anything. $this->writeEdgeData(); // If we're going to perform cycle detection, lock the edge type before // doing edits. if ($cycle_types) { $src_phids = ipull($this->addEdges, 'src'); foreach ($cycle_types as $cycle_type) { $key = 'edge.cycle:' . $cycle_type; $locks[] = PhabricatorGlobalLock::newLock($key)->lock(15); } } static $id = 0; $id++; $this->sendEvent($id, PhabricatorEventType::TYPE_EDGE_WILLEDITEDGES); // NOTE: Removes first, then adds, so that "remove + add" is a useful // operation meaning "overwrite". $this->executeRemoves(); $this->executeAdds(); foreach ($cycle_types as $cycle_type) { $this->detectCycles($src_phids, $cycle_type); } $this->sendEvent($id, PhabricatorEventType::TYPE_EDGE_DIDEDITEDGES); $this->saveTransactions(); } catch (Exception $ex) { $caught = $ex; } if ($caught) { $this->killTransactions(); } foreach ($locks as $lock) { $lock->unlock(); } if ($caught) { throw $caught; } }
private function lockRepository($repository) { $lock_name = __CLASS__ . ':' . $repository->getCallsign(); $lock = PhabricatorGlobalLock::newLock($lock_name); $lock->lock(); return $lock; }
/** * Acquires a @{class:PhabricatorGlobalLock}. * * @return PhabricatorGlobalLock */ protected final function lock(PhabricatorStorageManagementAPI $api) { // Although we're holding this lock on different databases so it could // have the same name on each as far as the database is concerned, the // locks would be the same within this process. $lock_name = 'adjust/' . $api->getRef()->getRefKey(); return PhabricatorGlobalLock::newLock($lock_name)->useSpecificConnection($api->getConn(null))->lock(); }
protected final function acquireTaskLock() { return PhabricatorGlobalLock::newLock('bulktask.' . $this->getTaskID())->lock(15); }
public static function getWriteLock($repository_phid) { $repository_hash = PhabricatorHash::digestForIndex($repository_phid); $lock_key = "repo.write({$repository_hash})"; return PhabricatorGlobalLock::newLock($lock_key); }
/** * Update the overall status of the buildable this build is attached to. * * After a build changes state (for example, passes or fails) it may affect * the overall state of the associated buildable. Compute the new aggregate * state and save it on the buildable. * * @param HarbormasterBuild The buildable to update. * @return void */ private function updateBuildable(HarbormasterBuildable $buildable) { $viewer = $this->getViewer(); $lock_key = 'harbormaster.buildable:' . $buildable->getID(); $lock = PhabricatorGlobalLock::newLock($lock_key)->lock(15); $buildable = id(new HarbormasterBuildableQuery())->setViewer($viewer)->withIDs(array($buildable->getID()))->needBuilds(true)->executeOne(); $all_pass = true; $any_fail = false; foreach ($buildable->getBuilds() as $build) { if ($build->getBuildStatus() != HarbormasterBuild::STATUS_PASSED) { $all_pass = false; } if ($build->getBuildStatus() == HarbormasterBuild::STATUS_FAILED || $build->getBuildStatus() == HarbormasterBuild::STATUS_ERROR || $build->getBuildStatus() == HarbormasterBuild::STATUS_DEADLOCKED) { $any_fail = true; } } if ($any_fail) { $new_status = HarbormasterBuildable::STATUS_FAILED; } else { if ($all_pass) { $new_status = HarbormasterBuildable::STATUS_PASSED; } else { $new_status = HarbormasterBuildable::STATUS_BUILDING; } } $old_status = $buildable->getBuildableStatus(); $did_update = $old_status != $new_status; if ($did_update) { $buildable->setBuildableStatus($new_status); $buildable->save(); } $lock->unlock(); // If we changed the buildable status, try to post a transaction to the // object about it. We can safely do this outside of the locked region. // NOTE: We only post transactions for automatic buildables, not for // manual ones: manual builds are test builds, whoever is doing tests // can look at the results themselves, and other users generally don't // care about the outcome. $should_publish = $did_update && $new_status != HarbormasterBuildable::STATUS_BUILDING && !$buildable->getIsManualBuildable(); if (!$should_publish) { return; } $object = id(new PhabricatorObjectQuery())->setViewer($viewer)->withPHIDs(array($buildable->getBuildablePHID()))->executeOne(); if (!$object) { return; } if (!$object instanceof PhabricatorApplicationTransactionInterface) { return; } // TODO: Publishing these transactions is causing a race. See T8650. // We shouldn't be publishing to diffs anyway. if ($object instanceof DifferentialDiff) { return; } $template = $object->getApplicationTransactionTemplate(); if (!$template) { return; } $template->setTransactionType(PhabricatorTransactions::TYPE_BUILDABLE)->setMetadataValue('harbormaster:buildablePHID', $buildable->getPHID())->setOldValue($old_status)->setNewValue($new_status); $harbormaster_phid = id(new PhabricatorHarbormasterApplication())->getPHID(); $daemon_source = PhabricatorContentSource::newForSource(PhabricatorContentSource::SOURCE_DAEMON, array()); $editor = $object->getApplicationTransactionEditor()->setActor($viewer)->setActingAsPHID($harbormaster_phid)->setContentSource($daemon_source)->setContinueOnNoEffect(true)->setContinueOnMissingFields(true); $editor->applyTransactions($object->getApplicationTransactionObject(), array($template)); }
/** * Acquires a @{class:PhabricatorGlobalLock}. * * @return PhabricatorGlobalLock */ protected final function lock() { return PhabricatorGlobalLock::newLock(__CLASS__)->useSpecificConnection($this->getApi()->getConn(null))->lock(); }
/** * @task pull */ public function run() { $argv = $this->getArgv(); array_unshift($argv, __CLASS__); $args = new PhutilArgumentParser($argv); $args->parse(array(array('name' => 'no-discovery', 'help' => 'Pull only, without discovering commits.'), array('name' => 'not', 'param' => 'repository', 'repeat' => true, 'help' => 'Do not pull __repository__.'), array('name' => 'repositories', 'wildcard' => true, 'help' => 'Pull specific __repositories__ instead of all.'))); $no_discovery = $args->getArg('no-discovery'); $repo_names = $args->getArg('repositories'); $exclude_names = $args->getArg('not'); // Each repository has an individual pull frequency; after we pull it, // wait that long to pull it again. When we start up, try to pull everything // serially. $retry_after = array(); $min_sleep = 15; while (true) { $repositories = $this->loadRepositories($repo_names); if ($exclude_names) { $exclude = $this->loadRepositories($exclude_names); $repositories = array_diff_key($repositories, $exclude); } // Shuffle the repositories, then re-key the array since shuffle() // discards keys. This is mostly for startup, we'll use soft priorities // later. shuffle($repositories); $repositories = mpull($repositories, null, 'getID'); // If any repositories were deleted, remove them from the retry timer map // so we don't end up with a retry timer that never gets updated and // causes us to sleep for the minimum amount of time. $retry_after = array_select_keys($retry_after, array_keys($repositories)); // Assign soft priorities to repositories based on how frequently they // should pull again. asort($retry_after); $repositories = array_select_keys($repositories, array_keys($retry_after)) + $repositories; foreach ($repositories as $id => $repository) { $after = idx($retry_after, $id, 0); if ($after > time()) { continue; } $tracked = $repository->isTracked(); if (!$tracked) { continue; } try { $callsign = $repository->getCallsign(); $this->log("Updating repository '{$callsign}'."); $this->pullRepository($repository); if (!$no_discovery) { // TODO: It would be nice to discover only if we pulled something, // but this isn't totally trivial. $lock_name = get_class($this) . ':' . $callsign; $lock = PhabricatorGlobalLock::newLock($lock_name); $lock->lock(); try { $this->discoverRepository($repository); } catch (Exception $ex) { $lock->unlock(); throw $ex; } $lock->unlock(); } $sleep_for = $repository->getDetail('pull-frequency', $min_sleep); $retry_after[$id] = time() + $sleep_for; } catch (PhutilLockException $ex) { $retry_after[$id] = time() + $min_sleep; $this->log("Failed to acquire lock."); } catch (Exception $ex) { $retry_after[$id] = time() + $min_sleep; phlog($ex); } $this->stillWorking(); } if ($retry_after) { $sleep_until = max(min($retry_after), time() + $min_sleep); } else { $sleep_until = time() + $min_sleep; } $this->sleep($sleep_until - time()); } }
protected final function updateCommitData($author, $message, $committer = null) { $commit = $this->commit; $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere('commitID = %d', $commit->getID()); if (!$data) { $data = new PhabricatorRepositoryCommitData(); } $data->setCommitID($commit->getID()); $data->setAuthorName($author); $data->setCommitMessage($message); if ($committer) { $data->setCommitDetail('committer', $committer); } $repository = $this->repository; $detail_parser = $repository->getDetail('detail-parser', 'PhabricatorRepositoryDefaultCommitMessageDetailParser'); if ($detail_parser) { $parser_obj = newv($detail_parser, array($commit, $data)); $parser_obj->parseCommitDetails(); } $author_phid = $this->lookupUser($commit, $data->getAuthorName(), $data->getCommitDetail('authorPHID')); $data->setCommitDetail('authorPHID', $author_phid); $committer_phid = $this->lookupUser($commit, $data->getCommitDetail('committer'), $data->getCommitDetail('committerPHID')); $data->setCommitDetail('committerPHID', $committer_phid); if ($author_phid != $commit->getAuthorPHID()) { $commit->setAuthorPHID($author_phid); $commit->save(); } $conn_w = id(new DifferentialRevision())->establishConnection('w'); // NOTE: The `differential_commit` table has a unique ID on `commitPHID`, // preventing more than one revision from being associated with a commit. // Generally this is good and desirable, but with the advent of hash // tracking we may end up in a situation where we match several different // revisions. We just kind of ignore this and pick one, we might want to // revisit this and do something differently. (If we match several revisions // someone probably did something very silly, though.) $revision = null; $should_autoclose = $repository->shouldAutocloseCommit($commit, $data); $revision_id = $data->getCommitDetail('differential.revisionID'); if (!$revision_id) { $hashes = $this->getCommitHashes($this->repository, $this->commit); if ($hashes) { $query = new DifferentialRevisionQuery(); $query->withCommitHashes($hashes); $revisions = $query->execute(); if (!empty($revisions)) { $revision = $this->identifyBestRevision($revisions); $revision_id = $revision->getID(); } } } if ($revision_id) { $lock = PhabricatorGlobalLock::newLock(get_class($this) . ':' . $revision_id); $lock->lock(5 * 60); $revision = id(new DifferentialRevision())->load($revision_id); if ($revision) { $revision->loadRelationships(); queryfx($conn_w, 'INSERT IGNORE INTO %T (revisionID, commitPHID) VALUES (%d, %s)', DifferentialRevision::TABLE_COMMIT, $revision->getID(), $commit->getPHID()); $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; $should_close = $revision->getStatus() != $status_closed && $should_autoclose; if ($should_close) { $actor_phid = nonempty($committer_phid, $author_phid, $revision->getAuthorPHID()); $diff = $this->attachToRevision($revision, $actor_phid); $revision->setDateCommitted($commit->getEpoch()); $editor = new DifferentialCommentEditor($revision, $actor_phid, DifferentialAction::ACTION_CLOSE); $editor->setIsDaemonWorkflow(true); $vs_diff = $this->loadChangedByCommit($diff); if ($vs_diff) { $data->setCommitDetail('vsDiff', $vs_diff->getID()); $changed_by_commit = PhabricatorEnv::getProductionURI('/D' . $revision->getID() . '?vs=' . $vs_diff->getID() . '&id=' . $diff->getID() . '#toc'); $editor->setChangedByCommit($changed_by_commit); } $commit_name = $repository->formatCommitName($commit->getCommitIdentifier()); $committer_name = $this->loadUserName($committer_phid, $data->getCommitDetail('committer')); $author_name = $this->loadUserName($author_phid, $data->getAuthorName()); $info = array(); $info[] = "authored by {$author_name}"; if ($committer_name && $committer_name != $author_name) { $info[] = "committed by {$committer_name}"; } $info = implode(', ', $info); $editor->setMessage("Closed by commit {$commit_name} ({$info}).")->save(); } } $lock->unlock(); } if ($should_autoclose && $author_phid) { $user = id(new PhabricatorUser())->loadOneWhere('phid = %s', $author_phid); $call = new ConduitCall('differential.parsecommitmessage', array('corpus' => $message, 'partial' => true)); $call->setUser($user); $result = $call->execute(); $field_values = $result['fields']; $fields = DifferentialFieldSelector::newSelector()->getFieldSpecifications(); foreach ($fields as $key => $field) { if (!$field->shouldAppearOnCommitMessage()) { continue; } $field->setUser($user); $value = idx($field_values, $field->getCommitMessageKey()); $field->setValueFromParsedCommitMessage($value); if ($revision) { $field->setRevision($revision); } $field->didParseCommit($repository, $commit, $data); } } $data->save(); }
/** * Run the collector. * * @return bool True if there is more garbage to collect. * @task collect */ public final 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; } } // 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; }