PluginProbe
Media Cloud Sync / 1.3.11
Media Cloud Sync v1.3.11
1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 All 34 releases
media-cloud-sync / includes / sdk / s3 / GuzzleHttp / Handler / CurlMultiHandler.php

CurlMultiHandler.php in Media Cloud Sync 1.3.11, at includes/sdk/s3/GuzzleHttp/Handler/CurlMultiHandler.php

238 lines 8.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Dudlewebs\WPMCS\s3\GuzzleHttp\Handler;
4
5 use Closure;
6 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise as P;
7 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\Promise;
8 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\PromiseInterface;
9 use Dudlewebs\WPMCS\s3\GuzzleHttp\Utils;
10 use Dudlewebs\WPMCS\s3\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 CurlFactory(50);
64 if (isset($options['select_timeout'])) {
65 $this->selectTimeout = $options['select_timeout'];
66 } elseif ($selectTimeout = 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(RequestInterface $request, array $options) : PromiseInterface
109 {
110 $easy = $this->factory->create($request, $options);
111 $id = (int) $easy->handle;
112 $promise = new 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 = 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 P\Utils::queue()->add(Closure::fromCallable([$this, 'tickInQueue']));
135 // Step through the task queue which may add additional requests.
136 P\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 P\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 = P\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] = 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(CurlFactory::finish($this, $entry['easy'], $this->factory));
224 }
225 }
226 private function timeToNext() : int
227 {
228 $currentTime = 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