diff --git a/src/future/Future.php b/src/future/Future.php --- a/src/future/Future.php +++ b/src/future/Future.php @@ -14,6 +14,7 @@ private $exception; private $futureKey; private $serviceProfilerCallID; + private $raiseExceptionOnStart = true; private static $nextKey = 1; /** @@ -41,7 +42,7 @@ 'timeout.')); } - if (!$this->hasResult() && !$this->hasException()) { + if (!$this->canResolve()) { $graph = new FutureIterator(array($this)); $graph->resolveAll(); } @@ -53,25 +54,8 @@ return $this->getResult(); } - final public function startFuture() { - if ($this->hasStarted) { - throw new Exception( - pht( - 'Future has already started; futures can not start more '. - 'than once.')); - } - $this->hasStarted = true; - - $this->startServiceProfiler(); - $this->updateFuture(); - } - final public function updateFuture() { - if ($this->hasException()) { - return; - } - - if ($this->hasResult()) { + if ($this->canResolve()) { return; } @@ -84,25 +68,6 @@ } } - final public function endFuture() { - if (!$this->hasException() && !$this->hasResult()) { - throw new Exception( - pht( - 'Trying to end a future which has no exception and no result. '. - 'Futures must resolve before they can be ended.')); - } - - if ($this->hasEnded) { - throw new Exception( - pht( - 'Future has already ended; futures can not end more '. - 'than once.')); - } - $this->hasEnded = true; - - $this->endServiceProfiler(); - } - private function startServiceProfiler() { // NOTE: This is a soft dependency so that we don't need to build the @@ -181,7 +146,24 @@ } public function start() { - $this->isReady(); + if ($this->hasStarted) { + throw new Exception( + pht( + 'Future has already started; futures can not start more '. + 'than once.')); + } + $this->hasStarted = true; + + $this->startServiceProfiler(); + + $this->updateFuture(); + + if ($this->raiseExceptionOnStart) { + if ($this->hasException()) { + throw $this->getException(); + } + } + return $this; } @@ -212,6 +194,8 @@ $this->hasResult = true; $this->result = $result; + $this->endFuture(); + return $this; } @@ -219,13 +203,16 @@ return $this->hasResult; } - final private function setException($exception) { + private function setException($exception) { // NOTE: The parameter may be an Exception or a Throwable. $this->exception = $exception; + + $this->endFuture(); + return $this; } - final private function getException() { + private function getException() { return $this->exception; } @@ -254,4 +241,37 @@ return $this->futureKey; } + final public function setRaiseExceptionOnStart($raise) { + $this->raiseExceptionOnStart = $raise; + return $this; + } + + final public function getHasFutureStarted() { + return $this->hasStarted; + } + + final public function canResolve() { + if ($this->hasResult()) { + return true; + } + + if ($this->hasException()) { + return true; + } + + return false; + } + + private function endFuture() { + if ($this->hasEnded) { + throw new Exception( + pht( + 'Future has already ended; futures can not end more '. + 'than once.')); + } + $this->hasEnded = true; + + $this->endServiceProfiler(); + } + } diff --git a/src/future/FutureIterator.php b/src/future/FutureIterator.php --- a/src/future/FutureIterator.php +++ b/src/future/FutureIterator.php @@ -222,12 +222,7 @@ $resolve_key = null; foreach ($working_set as $future_key => $future) { - if ($future->hasException()) { - $resolve_key = $future_key; - break; - } - - if ($future->hasResult()) { + if ($future->canResolve()) { $resolve_key = $future_key; break; } @@ -393,7 +388,13 @@ unset($this->wait[$future_key]); $this->work[$future_key] = $future_key; - $this->futures[$future_key]->startFuture(); + $future = $this->futures[$future_key]; + + if (!$future->getHasFutureStarted()) { + $future + ->setRaiseExceptionOnStart(false) + ->start(); + } } private function moveFutureToDone($future_key) { @@ -404,8 +405,6 @@ // futures that are ready to go as soon as we can. $this->updateWorkingSet(); - - $this->futures[$future_key]->endFuture(); } /** diff --git a/src/future/exec/ExecFuture.php b/src/future/exec/ExecFuture.php --- a/src/future/exec/ExecFuture.php +++ b/src/future/exec/ExecFuture.php @@ -95,6 +95,18 @@ return $status['pid']; } + public function hasPID() { + if ($this->procStatus) { + return true; + } + + if ($this->proc) { + return true; + } + + return false; + } + /* -( Configuring Execution )---------------------------------------------- */ @@ -194,7 +206,7 @@ public function readStdout() { if ($this->start) { - $this->isReady(); // Sync + $this->updateFuture(); // Sync } $result = (string)substr($this->stdout, $this->stdoutPos); @@ -890,6 +902,17 @@ return $this->procStatus; } } + + // See T13555. This may occur if you call "getPID()" on a future which + // exited immediately without ever creating a valid subprocess. + + if (!$this->proc) { + throw new Exception( + pht( + 'Attempting to get subprocess status in "ExecFuture" with no '. + 'valid subprocess.')); + } + $this->procStatus = proc_get_status($this->proc); return $this->procStatus; diff --git a/src/repository/graph/query/ArcanistGitCommitGraphQuery.php b/src/repository/graph/query/ArcanistGitCommitGraphQuery.php --- a/src/repository/graph/query/ArcanistGitCommitGraphQuery.php +++ b/src/repository/graph/query/ArcanistGitCommitGraphQuery.php @@ -104,7 +104,7 @@ } $future = array_pop($this->futures); - $future->startFuture(); + $future->start(); $iterator = id(new LinesOfALargeExecFuture($future)) ->setDelimiter("\1");