945d06ee4e2adae1fa28d5202b5b97efd62ad4ab
[yaffs-website] / vendor / guzzlehttp / guzzle / src / Handler / CurlMultiHandler.php
1 <?php
2 namespace GuzzleHttp\Handler;
3
4 use GuzzleHttp\Promise as P;
5 use GuzzleHttp\Promise\Promise;
6 use GuzzleHttp\Psr7;
7 use Psr\Http\Message\RequestInterface;
8
9 /**
10  * Returns an asynchronous response using curl_multi_* functions.
11  *
12  * When using the CurlMultiHandler, custom curl options can be specified as an
13  * associative array of curl option constants mapping to values in the
14  * **curl** key of the provided request options.
15  *
16  * @property resource $_mh Internal use only. Lazy loaded multi-handle.
17  */
18 class CurlMultiHandler
19 {
20     /** @var CurlFactoryInterface */
21     private $factory;
22     private $selectTimeout;
23     private $active;
24     private $handles = [];
25     private $delays = [];
26
27     /**
28      * This handler accepts the following options:
29      *
30      * - handle_factory: An optional factory  used to create curl handles
31      * - select_timeout: Optional timeout (in seconds) to block before timing
32      *   out while selecting curl handles. Defaults to 1 second.
33      *
34      * @param array $options
35      */
36     public function __construct(array $options = [])
37     {
38         $this->factory = isset($options['handle_factory'])
39             ? $options['handle_factory'] : new CurlFactory(50);
40         $this->selectTimeout = isset($options['select_timeout'])
41             ? $options['select_timeout'] : 1;
42     }
43
44     public function __get($name)
45     {
46         if ($name === '_mh') {
47             return $this->_mh = curl_multi_init();
48         }
49
50         throw new \BadMethodCallException();
51     }
52
53     public function __destruct()
54     {
55         if (isset($this->_mh)) {
56             curl_multi_close($this->_mh);
57             unset($this->_mh);
58         }
59     }
60
61     public function __invoke(RequestInterface $request, array $options)
62     {
63         $easy = $this->factory->create($request, $options);
64         $id = (int) $easy->handle;
65
66         $promise = new Promise(
67             [$this, 'execute'],
68             function () use ($id) { return $this->cancel($id); }
69         );
70
71         $this->addRequest(['easy' => $easy, 'deferred' => $promise]);
72
73         return $promise;
74     }
75
76     /**
77      * Ticks the curl event loop.
78      */
79     public function tick()
80     {
81         // Add any delayed handles if needed.
82         if ($this->delays) {
83             $currentTime = microtime(true);
84             foreach ($this->delays as $id => $delay) {
85                 if ($currentTime >= $delay) {
86                     unset($this->delays[$id]);
87                     curl_multi_add_handle(
88                         $this->_mh,
89                         $this->handles[$id]['easy']->handle
90                     );
91                 }
92             }
93         }
94
95         // Step through the task queue which may add additional requests.
96         P\queue()->run();
97
98         if ($this->active &&
99             curl_multi_select($this->_mh, $this->selectTimeout) === -1
100         ) {
101             // Perform a usleep if a select returns -1.
102             // See: https://bugs.php.net/bug.php?id=61141
103             usleep(250);
104         }
105
106         while (curl_multi_exec($this->_mh, $this->active) === CURLM_CALL_MULTI_PERFORM);
107
108         $this->processMessages();
109     }
110
111     /**
112      * Runs until all outstanding connections have completed.
113      */
114     public function execute()
115     {
116         $queue = P\queue();
117
118         while ($this->handles || !$queue->isEmpty()) {
119             // If there are no transfers, then sleep for the next delay
120             if (!$this->active && $this->delays) {
121                 usleep($this->timeToNext());
122             }
123             $this->tick();
124         }
125     }
126
127     private function addRequest(array $entry)
128     {
129         $easy = $entry['easy'];
130         $id = (int) $easy->handle;
131         $this->handles[$id] = $entry;
132         if (empty($easy->options['delay'])) {
133             curl_multi_add_handle($this->_mh, $easy->handle);
134         } else {
135             $this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000);
136         }
137     }
138
139     /**
140      * Cancels a handle from sending and removes references to it.
141      *
142      * @param int $id Handle ID to cancel and remove.
143      *
144      * @return bool True on success, false on failure.
145      */
146     private function cancel($id)
147     {
148         // Cannot cancel if it has been processed.
149         if (!isset($this->handles[$id])) {
150             return false;
151         }
152
153         $handle = $this->handles[$id]['easy']->handle;
154         unset($this->delays[$id], $this->handles[$id]);
155         curl_multi_remove_handle($this->_mh, $handle);
156         curl_close($handle);
157
158         return true;
159     }
160
161     private function processMessages()
162     {
163         while ($done = curl_multi_info_read($this->_mh)) {
164             $id = (int) $done['handle'];
165             curl_multi_remove_handle($this->_mh, $done['handle']);
166
167             if (!isset($this->handles[$id])) {
168                 // Probably was cancelled.
169                 continue;
170             }
171
172             $entry = $this->handles[$id];
173             unset($this->handles[$id], $this->delays[$id]);
174             $entry['easy']->errno = $done['result'];
175             $entry['deferred']->resolve(
176                 CurlFactory::finish(
177                     $this,
178                     $entry['easy'],
179                     $this->factory
180                 )
181             );
182         }
183     }
184
185     private function timeToNext()
186     {
187         $currentTime = microtime(true);
188         $nextTime = PHP_INT_MAX;
189         foreach ($this->delays as $time) {
190             if ($time < $nextTime) {
191                 $nextTime = $time;
192             }
193         }
194
195         return max(0, $nextTime - $currentTime) * 1000000;
196     }
197 }