4
* A 'future' or 'promise' is an object which represents the result of some
5
* pending computation. For a more complete overview of futures, see
6
* @{article:Using Futures}.
10
abstract class Future {
12
protected static $handlerInstalled = null;
18
* Is this future's process complete? Specifically, can this future be
19
* resolved without blocking?
21
* @return bool If true, the external process is complete and resolving this
22
* future will not block.
24
abstract public function isReady();
27
* Resolve a future and return its result, blocking until the result is ready
30
* @param float Optional timeout after which resolution will pause and
31
* execution will return to the caller.
32
* @return mixed Future result, or null if the timeout is hit.
34
public function resolve($timeout = null) {
35
$start = microtime(true);
36
$wait = $this->getDefaultWait();
38
$this->checkException();
39
if ($this->isReady()) {
43
$read = $this->getReadSockets();
44
$write = $this->getWriteSockets();
46
if ($timeout !== null) {
47
$elapsed = microtime(true) - $start;
49
if ($elapsed > $timeout) {
50
$this->checkException();
53
$wait = $timeout - $elapsed;
57
if ($read || $write) {
58
self::waitForSockets($read, $write, $wait);
62
$this->checkException();
63
return $this->getResult();
66
public function setException(Exception $ex) {
67
$this->exception = $ex;
71
public function getException() {
72
return $this->exception;
77
* If an exception was set by setException(), throw it.
79
private function checkException() {
80
if ($this->exception) {
81
throw $this->exception;
87
* Retrieve a list of sockets which we can wait to become readable while
88
* a future is resolving. If your future has sockets which can be
89
* `select()`ed, return them here (or in @{method:getWriteSockets}) to make
90
* the resolve loop do a `select()`. If you do not return sockets in either
91
* case, you'll get a busy wait.
93
* @return list A list of sockets which we expect to become readable.
95
public function getReadSockets() {
101
* Retrieve a list of sockets which we can wait to become writable while a
102
* future is resolving. See @{method:getReadSockets}.
104
* @return list A list of sockets which we expect to become writable.
106
public function getWriteSockets() {
112
* Wait for activity on one of several sockets.
114
* @param list List of sockets expected to become readable.
115
* @param list List of sockets expected to become writable.
116
* @param float Timeout, in seconds.
119
public static function waitForSockets(
123
if (!self::$handlerInstalled) {
124
// If we're spawning child processes, we need to install a signal handler
125
// here to catch cases like execing '(sleep 60 &) &' where the child
126
// exits but a socket is kept open. But we don't actually need to do
127
// anything because the SIGCHLD will interrupt the stream_select(), as
128
// long as we have a handler registered.
129
if (function_exists('pcntl_signal')) {
130
if (!pcntl_signal(SIGCHLD, array('Future', 'handleSIGCHLD'))) {
131
throw new Exception('Failed to install signal handler!');
134
self::$handlerInstalled = true;
137
$timeout_sec = (int)$timeout;
138
$timeout_usec = (int)(1000000 * ($timeout - $timeout_sec));
140
$exceptfds = array();
141
$ok = @stream_select(
149
// Hopefully, means we received a SIGCHLD. In the worst case, we degrade
154
public static function handleSIGCHLD($signo) {
155
// This function is a dummy, we just need to have some handler registered
156
// so that PHP will get interrupted during stream_select(). If we don't
157
// register a handler, stream_select() won't fail.
162
* Retrieve the final result of the future. This method will be called after
163
* the future is ready (as per @{method:isReady}) but before results are
164
* passed back to the caller. The major use of this function is that you can
165
* override it in subclasses to do postprocessing or error checking, which is
166
* particularly useful if building application-specific futures on top of
167
* primitive transport futures (like @{class:CurlFuture} and
168
* @{class:ExecFuture}) which can make it tricky to hook this logic into the
171
* @return mixed Final resolution of this future.
173
protected function getResult() {
174
return $this->result;
178
* Default amount of time to wait on stream select for this future. Normally
179
* 1 second is fine, but if the future has a timeout sooner than that it
180
* should return the amount of time left before the timeout.
182
public function getDefaultWait() {
186
public function start() {