~ubuntu-branches/ubuntu/vivid/phabricator/vivid-proposed

« back to all changes in this revision

Viewing changes to libphutil/src/future/Future.php

  • Committer: Package Import Robot
  • Author(s): Richard Sellam
  • Date: 2014-10-23 20:49:26 UTC
  • mfrom: (0.2.1) (0.1.1)
  • Revision ID: package-import@ubuntu.com-20141023204926-vq80u1op4df44azb
Tags: 0~git20141023-1
Initial release (closes: #703046)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
 
 
3
/**
 
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}.
 
7
 *
 
8
 * @stable
 
9
 */
 
10
abstract class Future {
 
11
 
 
12
  protected static $handlerInstalled = null;
 
13
 
 
14
  protected $result;
 
15
  protected $exception;
 
16
 
 
17
  /**
 
18
   * Is this future's process complete? Specifically, can this future be
 
19
   * resolved without blocking?
 
20
   *
 
21
   * @return bool  If true, the external process is complete and resolving this
 
22
   *               future will not block.
 
23
   */
 
24
  abstract public function isReady();
 
25
 
 
26
  /**
 
27
   * Resolve a future and return its result, blocking until the result is ready
 
28
   * if necessary.
 
29
   *
 
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.
 
33
   */
 
34
  public function resolve($timeout = null) {
 
35
    $start = microtime(true);
 
36
    $wait  = $this->getDefaultWait();
 
37
    do {
 
38
      $this->checkException();
 
39
      if ($this->isReady()) {
 
40
        break;
 
41
      }
 
42
 
 
43
      $read = $this->getReadSockets();
 
44
      $write = $this->getWriteSockets();
 
45
 
 
46
      if ($timeout !== null) {
 
47
        $elapsed = microtime(true) - $start;
 
48
 
 
49
        if ($elapsed > $timeout) {
 
50
          $this->checkException();
 
51
          return null;
 
52
        } else {
 
53
          $wait = $timeout - $elapsed;
 
54
        }
 
55
      }
 
56
 
 
57
      if ($read || $write) {
 
58
        self::waitForSockets($read, $write, $wait);
 
59
      }
 
60
    } while (true);
 
61
 
 
62
    $this->checkException();
 
63
    return $this->getResult();
 
64
  }
 
65
 
 
66
  public function setException(Exception $ex) {
 
67
    $this->exception = $ex;
 
68
    return $this;
 
69
  }
 
70
 
 
71
  public function getException() {
 
72
    return $this->exception;
 
73
  }
 
74
 
 
75
 
 
76
  /**
 
77
   * If an exception was set by setException(), throw it.
 
78
   */
 
79
  private function checkException() {
 
80
    if ($this->exception) {
 
81
      throw $this->exception;
 
82
    }
 
83
  }
 
84
 
 
85
 
 
86
  /**
 
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.
 
92
   *
 
93
   * @return list  A list of sockets which we expect to become readable.
 
94
   */
 
95
  public function getReadSockets() {
 
96
    return array();
 
97
  }
 
98
 
 
99
 
 
100
  /**
 
101
   * Retrieve a list of sockets which we can wait to become writable while a
 
102
   * future is resolving. See @{method:getReadSockets}.
 
103
   *
 
104
   * @return list  A list of sockets which we expect to become writable.
 
105
   */
 
106
  public function getWriteSockets() {
 
107
    return array();
 
108
  }
 
109
 
 
110
 
 
111
  /**
 
112
   * Wait for activity on one of several sockets.
 
113
   *
 
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.
 
117
   * @return void
 
118
   */
 
119
  public static function waitForSockets(
 
120
    array $read_list,
 
121
    array $write_list,
 
122
    $timeout = 1) {
 
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!');
 
132
        }
 
133
      }
 
134
      self::$handlerInstalled = true;
 
135
    }
 
136
 
 
137
    $timeout_sec = (int)$timeout;
 
138
    $timeout_usec = (int)(1000000 * ($timeout - $timeout_sec));
 
139
 
 
140
    $exceptfds = array();
 
141
    $ok = @stream_select(
 
142
      $read_list,
 
143
      $write_list,
 
144
      $exceptfds,
 
145
      $timeout_sec,
 
146
      $timeout_usec);
 
147
 
 
148
    if ($ok === false) {
 
149
      // Hopefully, means we received a SIGCHLD. In the worst case, we degrade
 
150
      // to a busy wait.
 
151
    }
 
152
  }
 
153
 
 
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.
 
158
  }
 
159
 
 
160
 
 
161
  /**
 
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
 
169
   * main pipeline.
 
170
   *
 
171
   * @return mixed   Final resolution of this future.
 
172
   */
 
173
  protected function getResult() {
 
174
    return $this->result;
 
175
  }
 
176
 
 
177
  /**
 
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.
 
181
   */
 
182
  public function getDefaultWait() {
 
183
    return 1;
 
184
  }
 
185
 
 
186
  public function start() {
 
187
    $this->isReady();
 
188
    return $this;
 
189
  }
 
190
 
 
191
}