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;
 }