PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / trunk
Yoast SEO – Advanced SEO with real-time guidance and built-in AI vtrunk
28.6 28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 All 130 releases
← All changes | vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php +0 -313 28.0 → trunk View file →
@@ -1,313 +1,0 @@
1 -<?php
2 -
3 -namespace YoastSEO_Vendor\GuzzleHttp\Handler;
4 -
5 -use Closure;
6 -use YoastSEO_Vendor\GuzzleHttp\Promise as P;
7 -use YoastSEO_Vendor\GuzzleHttp\Promise\Promise;
8 -use YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface;
9 -use YoastSEO_Vendor\GuzzleHttp\TransportSharing;
10 -use YoastSEO_Vendor\GuzzleHttp\Utils;
11 -use YoastSEO_Vendor\Psr\Http\Message\RequestInterface;
12 -/**
13 - * Returns an asynchronous response using curl_multi_* functions.
14 - *
15 - * When using the CurlMultiHandler, custom curl options can be specified as an
16 - * associative array of curl option constants mapping to values in the
17 - * **curl** key of the provided request options.
18 - *
19 - * @final
20 - */
21 -class CurlMultiHandler
22 -{
23 - /**
24 - * @var CurlFactoryInterface
25 - */
26 - private $factory;
27 - /**
28 - * @var CurlShareHandleState|null
29 - */
30 - private $shareHandleState;
31 - /**
32 - * @var int
33 - */
34 - private $selectTimeout;
35 - /**
36 - * @var int Will be higher than 0 when `curl_multi_exec` is still running.
37 - */
38 - private $active = 0;
39 - /**
40 - * @var array Request entry handles, indexed by handle id in `addRequest`.
41 - *
42 - * @see CurlMultiHandler::addRequest
43 - */
44 - private $handles = [];
45 - /**
46 - * @var array<int, float> An array of delay times, indexed by handle id in `addRequest`.
47 - *
48 - * @see CurlMultiHandler::addRequest
49 - */
50 - private $delays = [];
51 - /**
52 - * @var array<mixed> An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt()
53 - */
54 - private $options = [];
55 - /** @var resource|\CurlMultiHandle */
56 - private $_mh;
57 - /**
58 - * @var bool
59 - */
60 - private $executingMulti = \false;
61 - /**
62 - * @var array<int, EasyHandle>
63 - */
64 - private $deferredCancels = [];
65 - /**
66 - * This handler accepts the following options:
67 - *
68 - * - handle_factory: An optional factory used to create curl handles
69 - * - transport_sharing: Optional transport sharing mode.
70 - * - select_timeout: Optional timeout (in seconds) to block before timing
71 - * out while selecting curl handles. Defaults to 1 second.
72 - * - options: An associative array of CURLMOPT_* options and
73 - * corresponding values for curl_multi_setopt()
74 - */
75 - public function __construct(array $options = [])
76 - {
77 - \YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState::assertNoRequiredSharingCustomFactoryConflict($options, 'CurlMultiHandler');
78 - $transportSharing = $options['transport_sharing'] ?? null;
79 - $sharingMode = \YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState::normalizeMode($transportSharing, 'transport_sharing');
80 - if (\array_key_exists('handle_factory', $options) && $options['handle_factory'] !== null) {
81 - $this->shareHandleState = null;
82 - $this->factory = $options['handle_factory'];
83 - } else {
84 - $this->shareHandleState = $sharingMode !== \YoastSEO_Vendor\GuzzleHttp\TransportSharing::NONE ? \YoastSEO_Vendor\GuzzleHttp\Handler\CurlShareHandleState::fromOption($transportSharing) : null;
85 - $this->factory = $this->shareHandleState !== null ? new \YoastSEO_Vendor\GuzzleHttp\Handler\CurlFactory(50, $this->shareHandleState->mode, $this->shareHandleState->handle) : new \YoastSEO_Vendor\GuzzleHttp\Handler\CurlFactory(50);
86 - }
87 - if (isset($options['select_timeout'])) {
88 - $this->selectTimeout = $options['select_timeout'];
89 - } elseif ($selectTimeout = \YoastSEO_Vendor\GuzzleHttp\Utils::getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
90 - \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.2', 'The GUZZLE_CURL_SELECT_TIMEOUT environment variable is deprecated; use the "select_timeout" option instead.');
91 - $this->selectTimeout = (int) $selectTimeout;
92 - } else {
93 - $this->selectTimeout = 1;
94 - }
95 - $this->options = $options['options'] ?? [];
96 - // unsetting the property forces the first access to go through
97 - // __get().
98 - unset($this->_mh);
99 - }
100 - /**
101 - * @param string $name
102 - *
103 - * @return resource|\CurlMultiHandle
104 - *
105 - * @throws \BadMethodCallException when another field as `_mh` will be gotten
106 - * @throws \RuntimeException when curl can not initialize a multi handle
107 - */
108 - public function __get($name)
109 - {
110 - if ($name !== '_mh') {
111 - throw new \BadMethodCallException("Can not get other property as '_mh'.");
112 - }
113 - $multiHandle = \curl_multi_init();
114 - if (\false === $multiHandle) {
115 - throw new \RuntimeException('Can not initialize curl multi handle.');
116 - }
117 - $this->_mh = $multiHandle;
118 - foreach ($this->options as $option => $value) {
119 - // A warning is raised in case of a wrong option.
120 - \curl_multi_setopt($this->_mh, $option, $value);
121 - }
122 - return $this->_mh;
123 - }
124 - public function __destruct()
125 - {
126 - if (isset($this->_mh)) {
127 - try {
128 - \curl_multi_close($this->_mh);
129 - } catch (\Throwable $e) {
130 - // Destructors must not throw.
131 - } finally {
132 - unset($this->_mh);
133 - }
134 - }
135 - }
136 - public function __invoke(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request, array $options) : \YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface
137 - {
138 - $easy = $this->factory->create($request, $options);
139 - $id = (int) $easy->handle;
140 - $promise = new \YoastSEO_Vendor\GuzzleHttp\Promise\Promise([$this, 'execute'], function () use($id) {
141 - return $this->cancel($id);
142 - });
143 - $this->addRequest(['easy' => $easy, 'deferred' => $promise]);
144 - return $promise;
145 - }
146 - /**
147 - * Ticks the curl event loop.
148 - */
149 - public function tick() : void
150 - {
151 - // Add any delayed handles if needed.
152 - if ($this->delays) {
153 - $currentTime = \YoastSEO_Vendor\GuzzleHttp\Utils::currentTime();
154 - foreach ($this->delays as $id => $delay) {
155 - if ($currentTime >= $delay) {
156 - unset($this->delays[$id]);
157 - \curl_multi_add_handle($this->_mh, $this->handles[$id]['easy']->handle);
158 - }
159 - }
160 - }
161 - // Run curl_multi_exec in the queue to enable other async tasks to run
162 - \YoastSEO_Vendor\GuzzleHttp\Promise\Utils::queue()->add(\Closure::fromCallable([$this, 'tickInQueue']));
163 - // Step through the task queue which may add additional requests.
164 - \YoastSEO_Vendor\GuzzleHttp\Promise\Utils::queue()->run();
165 - if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) {
166 - // Perform a usleep if a select returns -1.
167 - // See: https://bugs.php.net/bug.php?id=61141
168 - \usleep(250);
169 - }
170 - do {
171 - $this->executingMulti = \true;
172 - try {
173 - $exec = \curl_multi_exec($this->_mh, $this->active);
174 - } finally {
175 - $this->executingMulti = \false;
176 - $this->cleanupDeferredCancels();
177 - }
178 - // Prevent busy looping for slow HTTP requests.
179 - if ($exec === \CURLM_CALL_MULTI_PERFORM) {
180 - \curl_multi_select($this->_mh, $this->selectTimeout);
181 - }
182 - } while ($exec === \CURLM_CALL_MULTI_PERFORM);
183 - $this->processMessages();
184 - }
185 - /**
186 - * Runs \curl_multi_exec() inside the event loop, to prevent busy looping
187 - */
188 - private function tickInQueue() : void
189 - {
190 - $this->executingMulti = \true;
191 - try {
192 - $exec = \curl_multi_exec($this->_mh, $this->active);
193 - } finally {
194 - $this->executingMulti = \false;
195 - $this->cleanupDeferredCancels();
196 - }
197 - if ($exec === \CURLM_CALL_MULTI_PERFORM) {
198 - \curl_multi_select($this->_mh, 0);
199 - \YoastSEO_Vendor\GuzzleHttp\Promise\Utils::queue()->add(\Closure::fromCallable([$this, 'tickInQueue']));
200 - }
201 - }
202 - /**
203 - * Runs until all outstanding connections have completed.
204 - */
205 - public function execute() : void
206 - {
207 - $queue = \YoastSEO_Vendor\GuzzleHttp\Promise\Utils::queue();
208 - while ($this->handles || !$queue->isEmpty()) {
209 - // If there are no transfers, then sleep for the next delay
210 - if (!$this->active && $this->delays) {
211 - \usleep($this->timeToNext());
212 - }
213 - $this->tick();
214 - }
215 - }
216 - private function addRequest(array $entry) : void
217 - {
218 - $easy = $entry['easy'];
219 - $id = (int) $easy->handle;
220 - $this->handles[$id] = $entry;
221 - if (empty($easy->options['delay'])) {
222 - \curl_multi_add_handle($this->_mh, $easy->handle);
223 - } else {
224 - $this->delays[$id] = \YoastSEO_Vendor\GuzzleHttp\Utils::currentTime() + $easy->options['delay'] / 1000;
225 - }
226 - }
227 - /**
228 - * Cancels a handle from sending and removes references to it.
229 - *
230 - * @param int $id Handle ID to cancel and remove.
231 - *
232 - * @return bool True on success, false on failure.
233 - */
234 - private function cancel($id) : bool
235 - {
236 - if (!\is_int($id)) {
237 - \YoastSEO_Vendor\trigger_deprecation('guzzlehttp/guzzle', '7.4', 'Not passing an int to %s::%s() is deprecated and will cause an error in 8.0.', __CLASS__, __FUNCTION__);
238 - }
239 - // Cannot cancel if it has been processed.
240 - if (!isset($this->handles[$id])) {
241 - return \false;
242 - }
243 - $easy = $this->handles[$id]['easy'];
244 - unset($this->delays[$id], $this->handles[$id]);
245 - if ($this->executingMulti) {
246 - $this->deferredCancels[$id] = $easy;
247 - return \true;
248 - }
249 - $this->cleanupCancelledHandle($easy);
250 - return \true;
251 - }
252 - private function cleanupDeferredCancels() : void
253 - {
254 - if ($this->deferredCancels === []) {
255 - return;
256 - }
257 - $entries = $this->deferredCancels;
258 - $this->deferredCancels = [];
259 - foreach ($entries as $easy) {
260 - $this->cleanupCancelledHandle($easy);
261 - }
262 - }
263 - private function cleanupCancelledHandle(\YoastSEO_Vendor\GuzzleHttp\Handler\EasyHandle $easy) : void
264 - {
265 - $handle = $easy->handle;
266 - \curl_multi_remove_handle($this->_mh, $handle);
267 - if (\PHP_VERSION_ID < 80000) {
268 - \curl_close($handle);
269 - }
270 - }
271 - private function processMessages() : void
272 - {
273 - while ($done = \curl_multi_info_read($this->_mh)) {
274 - if ($done['msg'] !== \CURLMSG_DONE) {
275 - // if it's not done, then it would be premature to remove the handle. ref https://github.com/guzzle/guzzle/pull/2892#issuecomment-945150216
276 - continue;
277 - }
278 - if (!isset($done['handle'])) {
279 - // Work around a PHP issue where cancelled transfers may omit the handle.
280 - // Remove this once we no longer support PHP versions before the fix in
281 - // https://github.com/php/php-src/pull/16302.
282 - continue;
283 - }
284 - $id = (int) $done['handle'];
285 - \curl_multi_remove_handle($this->_mh, $done['handle']);
286 - if (!isset($this->handles[$id])) {
287 - // Probably was cancelled.
288 - continue;
289 - }
290 - $entry = $this->handles[$id];
291 - unset($this->handles[$id], $this->delays[$id]);
292 - $entry['easy']->errno = $done['result'];
293 - try {
294 - $result = \YoastSEO_Vendor\GuzzleHttp\Handler\CurlFactory::finish($this, $entry['easy'], $this->factory);
295 - } catch (\Throwable $e) {
296 - $entry['deferred']->reject($e);
297 - continue;
298 - }
299 - $entry['deferred']->resolve($result);
300 - }
301 - }
302 - private function timeToNext() : int
303 - {
304 - $currentTime = \YoastSEO_Vendor\GuzzleHttp\Utils::currentTime();
305 - $nextTime = \PHP_INT_MAX;
306 - foreach ($this->delays as $time) {
307 - if ($time < $nextTime) {
308 - $nextTime = $time;
309 - }
310 - }
311 - return (int) \max(0, $nextTime - $currentTime) * 1000000;
312 - }
313 -}