Changeset View
Changeset View
Standalone View
Standalone View
src/applications/herald/engine/HeraldEngine.php
| Show All 10 Lines | final class HeraldEngine extends Phobject { | ||||
| protected $fieldCache = array(); | protected $fieldCache = array(); | ||||
| protected $object; | protected $object; | ||||
| private $dryRun; | private $dryRun; | ||||
| private $forbiddenFields = array(); | private $forbiddenFields = array(); | ||||
| private $forbiddenActions = array(); | private $forbiddenActions = array(); | ||||
| private $skipEffects = array(); | private $skipEffects = array(); | ||||
| private $profilerStack = array(); | |||||
| private $profilerFrames = array(); | |||||
| public function setDryRun($dry_run) { | public function setDryRun($dry_run) { | ||||
| $this->dryRun = $dry_run; | $this->dryRun = $dry_run; | ||||
| return $this; | return $this; | ||||
| } | } | ||||
| public function getDryRun() { | public function getDryRun() { | ||||
| return $this->dryRun; | return $this->dryRun; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 105 Lines • ▼ Show 20 Lines | if ($xactions !== null) { | ||||
| $xaction_phids = mpull($xactions, 'getPHID'); | $xaction_phids = mpull($xactions, 'getPHID'); | ||||
| } | } | ||||
| $object_transcript = id(new HeraldObjectTranscript()) | $object_transcript = id(new HeraldObjectTranscript()) | ||||
| ->setPHID($object->getPHID()) | ->setPHID($object->getPHID()) | ||||
| ->setName($object->getHeraldName()) | ->setName($object->getHeraldName()) | ||||
| ->setType($object->getAdapterContentType()) | ->setType($object->getAdapterContentType()) | ||||
| ->setFields($this->fieldCache) | ->setFields($this->fieldCache) | ||||
| ->setAppliedTransactionPHIDs($xaction_phids); | ->setAppliedTransactionPHIDs($xaction_phids) | ||||
| ->setProfile($this->getProfile()); | |||||
| $this->transcript->setObjectTranscript($object_transcript); | $this->transcript->setObjectTranscript($object_transcript); | ||||
| $t_end = microtime(true); | $t_end = microtime(true); | ||||
| $this->transcript->setDuration($t_end - $t_start); | $this->transcript->setDuration($t_end - $t_start); | ||||
| return $effects; | return $effects; | ||||
| ▲ Show 20 Lines • Show All 175 Lines • ▼ Show 20 Lines | if ($rule->getConfigVersion() > $local_version) { | ||||
| } catch (Exception $ex) { | } catch (Exception $ex) { | ||||
| $reason = pht( | $reason = pht( | ||||
| 'Field "%s" does not exist!', | 'Field "%s" does not exist!', | ||||
| $condition->getFieldName()); | $condition->getFieldName()); | ||||
| $result = false; | $result = false; | ||||
| break; | break; | ||||
| } | } | ||||
| // Here, we're profiling the cost to match the condition value against | |||||
| // whatever test is configured. Normally, this cost should be very | |||||
| // small (<<1ms) since it amounts to a single comparison: | |||||
| // | |||||
| // [ Task author ][ is any of ][ alice ] | |||||
| // | |||||
| // However, it may be expensive in some cases, particularly if you | |||||
| // write a rule with a very creative regular expression that backtracks | |||||
| // explosively. | |||||
| // | |||||
| // At time of writing, the "Another Herald Rule" field is also | |||||
| // evaluated inside the matching function. This may be arbitrarily | |||||
| // expensive (it can prompt us to execute any finite number of other | |||||
| // Herald rules), although we'll push the profiler stack appropriately | |||||
| // so we don't count the evaluation time against this rule in the final | |||||
| // profile. | |||||
| $caught = null; | |||||
| $this->pushProfilerRule($rule); | |||||
| try { | |||||
| $match = $this->doesConditionMatch($rule, $condition, $object); | $match = $this->doesConditionMatch($rule, $condition, $object); | ||||
| } catch (Exception $ex) { | |||||
amckinley: We don't expect to ever hit this; this is just to prevent the profiler from losing its mind if… | |||||
Done Inline ActionsRight. There might be some crazy pathway where we can hit this upstream -- maybe "Another Herald Rule" causing initial evaluation of a rule with a "Content added" rule on a commit which hits a Conduit exception because of a bad repository cluster configuration -- but this isn't routine and the exception handling is so the profiler doesn't explode when it exits with a nonempty stack or report something wildly misleading that throws us off the scent of the actual issue. epriestley: Right. There might be some crazy pathway where we can hit this upstream -- maybe "Another… | |||||
| $caught = $ex; | |||||
| } | |||||
| $this->popProfilerRule($rule); | |||||
| if ($caught) { | |||||
| throw $ex; | |||||
| } | |||||
| if (!$all && $match) { | if (!$all && $match) { | ||||
| $reason = pht('Any condition matched.'); | $reason = pht('Any condition matched.'); | ||||
| $result = true; | $result = true; | ||||
| break; | break; | ||||
| } | } | ||||
| if ($all && !$match) { | if ($all && !$match) { | ||||
| ▲ Show 20 Lines • Show All 75 Lines • ▼ Show 20 Lines | protected function getConditionObjectValue( | ||||
| $field = $condition->getFieldName(); | $field = $condition->getFieldName(); | ||||
| return $this->getObjectFieldValue($field); | return $this->getObjectFieldValue($field); | ||||
| } | } | ||||
| public function getObjectFieldValue($field) { | public function getObjectFieldValue($field) { | ||||
| if (!array_key_exists($field, $this->fieldCache)) { | if (!array_key_exists($field, $this->fieldCache)) { | ||||
| $this->fieldCache[$field] = $this->object->getHeraldField($field); | $adapter = $this->object; | ||||
| $adapter->willGetHeraldField($field); | |||||
| $caught = null; | |||||
| $this->pushProfilerField($field); | |||||
| try { | |||||
| $value = $adapter->getHeraldField($field); | |||||
| } catch (Exception $ex) { | |||||
| $caught = $ex; | |||||
| } | |||||
| $this->popProfilerField($field); | |||||
| if ($caught) { | |||||
| throw $caught; | |||||
| } | |||||
| $this->fieldCache[$field] = $value; | |||||
| } | } | ||||
| return $this->fieldCache[$field]; | return $this->fieldCache[$field]; | ||||
| } | } | ||||
| protected function getRuleEffects( | protected function getRuleEffects( | ||||
| HeraldRule $rule, | HeraldRule $rule, | ||||
| HeraldAdapter $object) { | HeraldAdapter $object) { | ||||
| ▲ Show 20 Lines • Show All 199 Lines • ▼ Show 20 Lines | foreach ($rule->getActions() as $action_record) { | ||||
| $is_forbidden = true; | $is_forbidden = true; | ||||
| } | } | ||||
| } | } | ||||
| return $is_forbidden; | return $is_forbidden; | ||||
| } | } | ||||
| /* -( Profiler )----------------------------------------------------------- */ | |||||
| private function pushProfilerField($field_key) { | |||||
| return $this->pushProfilerStack('field', $field_key); | |||||
| } | |||||
| private function popProfilerField($field_key) { | |||||
| return $this->popProfilerStack('field', $field_key); | |||||
| } | |||||
| private function pushProfilerRule(HeraldRule $rule) { | |||||
| return $this->pushProfilerStack('rule', $rule->getPHID()); | |||||
| } | |||||
| private function popProfilerRule(HeraldRule $rule) { | |||||
| return $this->popProfilerStack('rule', $rule->getPHID()); | |||||
| } | |||||
| private function pushProfilerStack($type, $key) { | |||||
| $this->profilerStack[] = array( | |||||
| 'type' => $type, | |||||
| 'key' => $key, | |||||
| 'start' => microtime(true), | |||||
| ); | |||||
| return $this; | |||||
| } | |||||
| private function popProfilerStack($type, $key) { | |||||
| if (!$this->profilerStack) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Unable to pop profiler stack: profiler stack is empty.')); | |||||
| } | |||||
| $frame = last($this->profilerStack); | |||||
| if (($frame['type'] !== $type) || ($frame['key'] !== $key)) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Unable to pop profiler stack: expected frame of type "%s" with '. | |||||
| 'key "%s", but found frame of type "%s" with key "%s".', | |||||
| $type, | |||||
| $key, | |||||
| $frame['type'], | |||||
| $frame['key'])); | |||||
| } | |||||
| // Accumulate the new timing information into the existing profile. If this | |||||
| // is the first time we've seen this particular rule or field, we'll | |||||
| // create a new empty frame first. | |||||
| $elapsed = microtime(true) - $frame['start']; | |||||
| $frame_key = sprintf('%s/%s', $type, $key); | |||||
| if (!isset($this->profilerFrames[$frame_key])) { | |||||
| $current = array( | |||||
| 'type' => $type, | |||||
| 'key' => $key, | |||||
| 'elapsed' => 0, | |||||
| 'count' => 0, | |||||
| ); | |||||
| } else { | |||||
| $current = $this->profilerFrames[$frame_key]; | |||||
| } | |||||
| $current['elapsed'] += $elapsed; | |||||
| $current['count']++; | |||||
| $this->profilerFrames[$frame_key] = $current; | |||||
| array_pop($this->profilerStack); | |||||
| } | |||||
| private function getProfile() { | |||||
| if ($this->profilerStack) { | |||||
| $frame = last($this->profilerStack); | |||||
| $frame_type = $frame['type']; | |||||
| $frame_key = $frame['key']; | |||||
| $frame_count = count($this->profilerStack); | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Unable to retrieve profile: profiler stack is not empty. The '. | |||||
| 'stack has %s frame(s); the final frame has type "%s" and key '. | |||||
| '"%s".', | |||||
| new PhutilNumber($frame_count), | |||||
| $frame_type, | |||||
| $frame_key)); | |||||
| } | |||||
| return array_values($this->profilerFrames); | |||||
| } | |||||
| } | } | ||||
We don't expect to ever hit this; this is just to prevent the profiler from losing its mind if someone has a broken custom Herald rule or something, right?