Changeset View
Changeset View
Standalone View
Standalone View
src/future/Future.php
| <?php | <?php | ||||
| /** | /** | ||||
| * A 'future' or 'promise' is an object which represents the result of some | * A 'future' or 'promise' is an object which represents the result of some | ||||
| * pending computation. For a more complete overview of futures, see | * pending computation. For a more complete overview of futures, see | ||||
| * @{article:Using Futures}. | * @{article:Using Futures}. | ||||
| */ | */ | ||||
| abstract class Future extends Phobject { | abstract class Future extends Phobject { | ||||
| private $hasResult = false; | private $hasResult = false; | ||||
| private $hasStarted = false; | private $hasStarted = false; | ||||
| private $hasEnded = false; | private $hasEnded = false; | ||||
| private $result; | private $result; | ||||
| private $exception; | private $exception; | ||||
| private $futureKey; | private $futureKey; | ||||
| private $serviceProfilerCallID; | private $serviceProfilerCallID; | ||||
| private $raiseExceptionOnStart = true; | |||||
| private static $nextKey = 1; | private static $nextKey = 1; | ||||
| /** | /** | ||||
| * Is this future's process complete? Specifically, can this future be | * Is this future's process complete? Specifically, can this future be | ||||
| * resolved without blocking? | * resolved without blocking? | ||||
| * | * | ||||
| * @return bool If true, the external process is complete and resolving this | * @return bool If true, the external process is complete and resolving this | ||||
| * future will not block. | * future will not block. | ||||
| Show All 11 Lines | public function resolve() { | ||||
| if (count($args)) { | if (count($args)) { | ||||
| throw new Exception( | throw new Exception( | ||||
| pht( | pht( | ||||
| 'Parameter "timeout" to "Future->resolve()" is no longer '. | 'Parameter "timeout" to "Future->resolve()" is no longer '. | ||||
| 'supported. Update the caller so it no longer passes a '. | 'supported. Update the caller so it no longer passes a '. | ||||
| 'timeout.')); | 'timeout.')); | ||||
| } | } | ||||
| if (!$this->hasResult() && !$this->hasException()) { | if (!$this->canResolve()) { | ||||
| $graph = new FutureIterator(array($this)); | $graph = new FutureIterator(array($this)); | ||||
| $graph->resolveAll(); | $graph->resolveAll(); | ||||
| } | } | ||||
| if ($this->hasException()) { | if ($this->hasException()) { | ||||
| throw $this->getException(); | throw $this->getException(); | ||||
| } | } | ||||
| return $this->getResult(); | 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() { | final public function updateFuture() { | ||||
| if ($this->hasException()) { | if ($this->canResolve()) { | ||||
| return; | |||||
| } | |||||
| if ($this->hasResult()) { | |||||
| return; | return; | ||||
| } | } | ||||
| try { | try { | ||||
| $this->isReady(); | $this->isReady(); | ||||
| } catch (Exception $ex) { | } catch (Exception $ex) { | ||||
| $this->setException($ex); | $this->setException($ex); | ||||
| } catch (Throwable $ex) { | } catch (Throwable $ex) { | ||||
| $this->setException($ex); | $this->setException($ex); | ||||
| } | } | ||||
| } | } | ||||
| 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() { | private function startServiceProfiler() { | ||||
| // NOTE: This is a soft dependency so that we don't need to build the | // NOTE: This is a soft dependency so that we don't need to build the | ||||
| // ServiceProfiler into the Phage agent. Normally, this class is always | // ServiceProfiler into the Phage agent. Normally, this class is always | ||||
| // available. | // available. | ||||
| if (!class_exists('PhutilServiceProfiler')) { | if (!class_exists('PhutilServiceProfiler')) { | ||||
| return; | return; | ||||
| ▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | abstract class Future extends Phobject { | ||||
| * 1 second is fine, but if the future has a timeout sooner than that it | * 1 second is fine, but if the future has a timeout sooner than that it | ||||
| * should return the amount of time left before the timeout. | * should return the amount of time left before the timeout. | ||||
| */ | */ | ||||
| public function getDefaultWait() { | public function getDefaultWait() { | ||||
| return 1; | return 1; | ||||
| } | } | ||||
| public function start() { | 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; | return $this; | ||||
| } | } | ||||
| /** | /** | ||||
| * Retrieve the final result of the future. | * Retrieve the final result of the future. | ||||
| * | * | ||||
| * @return wild Final resolution of this future. | * @return wild Final resolution of this future. | ||||
| */ | */ | ||||
| Show All 14 Lines | if ($this->hasResult()) { | ||||
| pht( | pht( | ||||
| 'Future has already resolved. Futures may not resolve more than '. | 'Future has already resolved. Futures may not resolve more than '. | ||||
| 'once.')); | 'once.')); | ||||
| } | } | ||||
| $this->hasResult = true; | $this->hasResult = true; | ||||
| $this->result = $result; | $this->result = $result; | ||||
| $this->endFuture(); | |||||
| return $this; | return $this; | ||||
| } | } | ||||
| final public function hasResult() { | final public function hasResult() { | ||||
| return $this->hasResult; | return $this->hasResult; | ||||
| } | } | ||||
| final private function setException($exception) { | private function setException($exception) { | ||||
| // NOTE: The parameter may be an Exception or a Throwable. | // NOTE: The parameter may be an Exception or a Throwable. | ||||
| $this->exception = $exception; | $this->exception = $exception; | ||||
| $this->endFuture(); | |||||
| return $this; | return $this; | ||||
| } | } | ||||
| final private function getException() { | private function getException() { | ||||
| return $this->exception; | return $this->exception; | ||||
| } | } | ||||
| final public function hasException() { | final public function hasException() { | ||||
| return ($this->exception !== null); | return ($this->exception !== null); | ||||
| } | } | ||||
| final public function setFutureKey($key) { | final public function setFutureKey($key) { | ||||
| Show All 12 Lines | abstract class Future extends Phobject { | ||||
| final public function getFutureKey() { | final public function getFutureKey() { | ||||
| if ($this->futureKey === null) { | if ($this->futureKey === null) { | ||||
| $this->futureKey = sprintf('Future/%d', self::$nextKey++); | $this->futureKey = sprintf('Future/%d', self::$nextKey++); | ||||
| } | } | ||||
| return $this->futureKey; | 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(); | |||||
| } | |||||
| } | } | ||||