PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 27.9
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v27.9
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 19.0 All 129 releases
wordpress-seo / vendor_prefixed / guzzlehttp / guzzle / src / Handler / CurlMultiHandler.php

CurlMultiHandler.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 27.9, at vendor_prefixed/guzzlehttp/guzzle/src/Handler/CurlMultiHandler.php

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