PluginProbe
Depicter — Popup & Slider Builder / 2.0.0
Depicter — Popup & Slider Builder v2.0.0
4.8.1 trunk 1.0.0 1.1.0 1.1.2 1.1.4 1.1.6 1.1.7 1.1.8 1.1.9 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.5 1.3.8 1.5.0 1.5.1 1.5.2 1.5.5 1.6.0 1.6.1 1.6.2 1.7.0 All 76 releases
depicter / modules / GuzzleHttp / Handler / CurlMultiHandler.php

CurlMultiHandler.php in Depicter — Popup & Slider Builder 2.0.0, at modules/GuzzleHttp/Handler/CurlMultiHandler.php

262 lines 7.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Depicter\GuzzleHttp\Handler;
4
5 use Depicter\GuzzleHttp\Promise as P;
6 use Depicter\GuzzleHttp\Promise\Promise;
7 use Depicter\GuzzleHttp\Promise\PromiseInterface;
8 use Depicter\GuzzleHttp\Utils;
9 use Depicter\Psr\Http\Message\RequestInterface;
10
11 /**
12 * Returns an asynchronous response using curl_multi_* functions.
13 *
14 * When using the CurlMultiHandler, custom curl options can be specified as an
15 * associative array of curl option constants mapping to values in the
16 * **curl** key of the provided request options.
17 *
18 * @property resource|\CurlMultiHandle $_mh Internal use only. Lazy loaded multi-handle.
19 *
20 * @final
21 */
22 class CurlMultiHandler
23 {
24 /**
25 * @var CurlFactoryInterface
26 */
27 private $factory;
28
29 /**
30 * @var int
31 */
32 private $selectTimeout;
33
34 /**
35 * @var resource|\CurlMultiHandle|null the currently executing resource in `curl_multi_exec`.
36 */
37 private $active;
38
39 /**
40 * @var array Request entry handles, indexed by handle id in `addRequest`.
41 *
42 * @see CurlMultiHandler::addRequest
43 */
44 private $handles = [];
45
46 /**
47 * @var array<int, float> An array of delay times, indexed by handle id in `addRequest`.
48 *
49 * @see CurlMultiHandler::addRequest
50 */
51 private $delays = [];
52
53 /**
54 * @var array<mixed> An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt()
55 */
56 private $options = [];
57
58 /**
59 * This handler accepts the following options:
60 *
61 * - handle_factory: An optional factory used to create curl handles
62 * - select_timeout: Optional timeout (in seconds) to block before timing
63 * out while selecting curl handles. Defaults to 1 second.
64 * - options: An associative array of CURLMOPT_* options and
65 * corresponding values for curl_multi_setopt()
66 */
67 public function __construct(array $options = [])
68 {
69 $this->factory = $options['handle_factory'] ?? new CurlFactory(50);
70
71 if (isset($options['select_timeout'])) {
72 $this->selectTimeout = $options['select_timeout'];
73 } elseif ($selectTimeout = Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
74 @trigger_error('Since guzzlehttp/guzzle 7.2.0: Using environment variable GUZZLE_CURL_SELECT_TIMEOUT is deprecated. Use option "select_timeout" instead.', \E_USER_DEPRECATED);
75 $this->selectTimeout = (int) $selectTimeout;
76 } else {
77 $this->selectTimeout = 1;
78 }
79
80 $this->options = $options['options'] ?? [];
81 }
82
83 /**
84 * @param string $name
85 *
86 * @return resource|\CurlMultiHandle
87 *
88 * @throws \BadMethodCallException when another field as `_mh` will be gotten
89 * @throws \RuntimeException when curl can not initialize a multi handle
90 */
91 public function __get($name)
92 {
93 if ($name !== '_mh') {
94 throw new \BadMethodCallException("Can not get other property as '_mh'.");
95 }
96
97 $multiHandle = \curl_multi_init();
98
99 if (false === $multiHandle) {
100 throw new \RuntimeException('Can not initialize curl multi handle.');
101 }
102
103 $this->_mh = $multiHandle;
104
105 foreach ($this->options as $option => $value) {
106 // A warning is raised in case of a wrong option.
107 curl_multi_setopt($this->_mh, $option, $value);
108 }
109
110 return $this->_mh;
111 }
112
113 public function __destruct()
114 {
115 if (isset($this->_mh)) {
116 \curl_multi_close($this->_mh);
117 unset($this->_mh);
118 }
119 }
120
121 public function __invoke(RequestInterface $request, array $options): PromiseInterface
122 {
123 $easy = $this->factory->create($request, $options);
124 $id = (int) $easy->handle;
125
126 $promise = new Promise(
127 [$this, 'execute'],
128 function () use ($id) {
129 return $this->cancel($id);
130 }
131 );
132
133 $this->addRequest(['easy' => $easy, 'deferred' => $promise]);
134
135 return $promise;
136 }
137
138 /**
139 * Ticks the curl event loop.
140 */
141 public function tick(): void
142 {
143 // Add any delayed handles if needed.
144 if ($this->delays) {
145 $currentTime = Utils::currentTime();
146 foreach ($this->delays as $id => $delay) {
147 if ($currentTime >= $delay) {
148 unset($this->delays[$id]);
149 \curl_multi_add_handle(
150 $this->_mh,
151 $this->handles[$id]['easy']->handle
152 );
153 }
154 }
155 }
156
157 // Step through the task queue which may add additional requests.
158 P\Utils::queue()->run();
159
160 if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) {
161 // Perform a usleep if a select returns -1.
162 // See: https://bugs.php.net/bug.php?id=61141
163 \usleep(250);
164 }
165
166 while (\curl_multi_exec($this->_mh, $this->active) === \CURLM_CALL_MULTI_PERFORM);
167
168 $this->processMessages();
169 }
170
171 /**
172 * Runs until all outstanding connections have completed.
173 */
174 public function execute(): void
175 {
176 $queue = P\Utils::queue();
177
178 while ($this->handles || !$queue->isEmpty()) {
179 // If there are no transfers, then sleep for the next delay
180 if (!$this->active && $this->delays) {
181 \usleep($this->timeToNext());
182 }
183 $this->tick();
184 }
185 }
186
187 private function addRequest(array $entry): void
188 {
189 $easy = $entry['easy'];
190 $id = (int) $easy->handle;
191 $this->handles[$id] = $entry;
192 if (empty($easy->options['delay'])) {
193 \curl_multi_add_handle($this->_mh, $easy->handle);
194 } else {
195 $this->delays[$id] = Utils::currentTime() + ($easy->options['delay'] / 1000);
196 }
197 }
198
199 /**
200 * Cancels a handle from sending and removes references to it.
201 *
202 * @param int $id Handle ID to cancel and remove.
203 *
204 * @return bool True on success, false on failure.
205 */
206 private function cancel($id): bool
207 {
208 if (!is_int($id)) {
209 trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an integer to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
210 }
211
212 // Cannot cancel if it has been processed.
213 if (!isset($this->handles[$id])) {
214 return false;
215 }
216
217 $handle = $this->handles[$id]['easy']->handle;
218 unset($this->delays[$id], $this->handles[$id]);
219 \curl_multi_remove_handle($this->_mh, $handle);
220 \curl_close($handle);
221
222 return true;
223 }
224
225 private function processMessages(): void
226 {
227 while ($done = \curl_multi_info_read($this->_mh)) {
228 if ($done['msg'] !== \CURLMSG_DONE) {
229 // if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216
230 continue;
231 }
232 $id = (int) $done['handle'];
233 \curl_multi_remove_handle($this->_mh, $done['handle']);
234
235 if (!isset($this->handles[$id])) {
236 // Probably was cancelled.
237 continue;
238 }
239
240 $entry = $this->handles[$id];
241 unset($this->handles[$id], $this->delays[$id]);
242 $entry['easy']->errno = $done['result'];
243 $entry['deferred']->resolve(
244 CurlFactory::finish($this, $entry['easy'], $this->factory)
245 );
246 }
247 }
248
249 private function timeToNext(): int
250 {
251 $currentTime = Utils::currentTime();
252 $nextTime = \PHP_INT_MAX;
253 foreach ($this->delays as $time) {
254 if ($time < $nextTime) {
255 $nextTime = $time;
256 }
257 }
258
259 return ((int) \max(0, $nextTime - $currentTime)) * 1000000;
260 }
261 }
262