PluginProbe
Media Cloud Sync / 1.4.1
Media Cloud Sync v1.4.1
1.4.1 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 All 35 releases
← All changes | includes/sdk/s3/GuzzleHttp/Handler/CurlMultiHandler.php +90 -30 1.1.01.4.1 View file →
@@ -1,10 +1,12 @@
1 1 <?php
2 2
3 3 namespace Dudlewebs\WPMCS\s3\GuzzleHttp\Handler;
4 4
5 +use Closure;
5 6 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise as P;
6 7 use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\Promise;
8 +use Dudlewebs\WPMCS\s3\GuzzleHttp\Promise\PromiseInterface;
7 9 use Dudlewebs\WPMCS\s3\GuzzleHttp\Utils;
8 10 use Dudlewebs\WPMCS\s3\Psr\Http\Message\RequestInterface;
9 11 /**
10 12 * Returns an asynchronous response using curl_multi_* functions.
@@ -12,19 +14,42 @@
12 14 * When using the CurlMultiHandler, custom curl options can be specified as an
13 15 * associative array of curl option constants mapping to values in the
14 16 * **curl** key of the provided request options.
15 17 *
16 - * @property resource $_mh Internal use only. Lazy loaded multi-handle.
18 + * @final
17 19 */
18 20 class CurlMultiHandler
19 21 {
20 - /** @var CurlFactoryInterface */
22 + /**
23 + * @var CurlFactoryInterface
24 + */
21 25 private $factory;
26 + /**
27 + * @var int
28 + */
22 29 private $selectTimeout;
23 - private $active;
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 + */
24 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 + */
25 45 private $delays = [];
46 + /**
47 + * @var array<mixed> An associative array of CURLMOPT_* options and corresponding values for curl_multi_setopt()
48 + */
26 49 private $options = [];
50 + /** @var resource|\CurlMultiHandle */
51 + private $_mh;
27 52 /**
28 53 * This handler accepts the following options:
29 54 *
30 55 * - handle_factory: An optional factory used to create curl handles
@@ -31,36 +56,48 @@
31 56 * - select_timeout: Optional timeout (in seconds) to block before timing
32 57 * out while selecting curl handles. Defaults to 1 second.
33 58 * - options: An associative array of CURLMOPT_* options and
34 59 * corresponding values for curl_multi_setopt()
35 - *
36 - * @param array $options
37 60 */
38 61 public function __construct(array $options = [])
39 62 {
40 - $this->factory = isset($options['handle_factory']) ? $options['handle_factory'] : new CurlFactory(50);
63 + $this->factory = $options['handle_factory'] ?? new CurlFactory(50);
41 64 if (isset($options['select_timeout'])) {
42 65 $this->selectTimeout = $options['select_timeout'];
43 - } elseif ($selectTimeout = \getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
44 - $this->selectTimeout = $selectTimeout;
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;
45 69 } else {
46 70 $this->selectTimeout = 1;
47 71 }
48 - $this->options = isset($options['options']) ? $options['options'] : [];
72 + $this->options = $options['options'] ?? [];
73 + // unsetting the property forces the first access to go through
74 + // __get().
75 + unset($this->_mh);
49 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 + */
50 85 public function __get($name)
51 86 {
52 - if ($name === '_mh') {
53 - $this->_mh = \curl_multi_init();
54 - foreach ($this->options as $option => $value) {
55 - // A warning is raised in case of a wrong option.
56 - \curl_multi_setopt($this->_mh, $option, $value);
57 - }
58 - // Further calls to _mh will return the value directly, without entering the
59 - // __get() method at all.
60 - return $this->_mh;
87 + if ($name !== '_mh') {
88 + throw new \BadMethodCallException("Can not get other property as '_mh'.");
61 89 }
62 - throw new \BadMethodCallException();
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;
63 100 }
64 101 public function __destruct()
65 102 {
66 103 if (isset($this->_mh)) {
@@ -67,9 +104,9 @@
67 104 \curl_multi_close($this->_mh);
68 105 unset($this->_mh);
69 106 }
70 107 }
71 - public function __invoke(RequestInterface $request, array $options)
108 + public function __invoke(RequestInterface $request, array $options) : PromiseInterface
72 109 {
73 110 $easy = $this->factory->create($request, $options);
74 111 $id = (int) $easy->handle;
75 112 $promise = new Promise([$this, 'execute'], function () use($id) {
@@ -80,9 +117,9 @@
80 117 }
81 118 /**
82 119 * Ticks the curl event loop.
83 120 */
84 - public function tick()
121 + public function tick() : void
85 122 {
86 123 // Add any delayed handles if needed.
87 124 if ($this->delays) {
88 125 $currentTime = Utils::currentTime();
@@ -92,10 +129,12 @@
92 129 \curl_multi_add_handle($this->_mh, $this->handles[$id]['easy']->handle);
93 130 }
94 131 }
95 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']));
96 135 // Step through the task queue which may add additional requests.
97 - P\queue()->run();
136 + P\Utils::queue()->run();
98 137 if ($this->active && \curl_multi_select($this->_mh, $this->selectTimeout) === -1) {
99 138 // Perform a usleep if a select returns -1.
100 139 // See: https://bugs.php.net/bug.php?id=61141
101 140 \usleep(250);
@@ -100,17 +139,29 @@
100 139 // See: https://bugs.php.net/bug.php?id=61141
101 140 \usleep(250);
102 141 }
103 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);
104 145 }
105 146 $this->processMessages();
106 147 }
107 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 + /**
108 159 * Runs until all outstanding connections have completed.
109 160 */
110 - public function execute()
161 + public function execute() : void
111 162 {
112 - $queue = P\queue();
163 + $queue = P\Utils::queue();
113 164 while ($this->handles || !$queue->isEmpty()) {
114 165 // If there are no transfers, then sleep for the next delay
115 166 if (!$this->active && $this->delays) {
116 167 \usleep($this->timeToNext());
@@ -117,9 +168,9 @@
117 168 }
118 169 $this->tick();
119 170 }
120 171 }
121 - private function addRequest(array $entry)
172 + private function addRequest(array $entry) : void
122 173 {
123 174 $easy = $entry['easy'];
124 175 $id = (int) $easy->handle;
125 176 $this->handles[$id] = $entry;
@@ -135,10 +186,13 @@
135 186 * @param int $id Handle ID to cancel and remove.
136 187 *
137 188 * @return bool True on success, false on failure.
138 189 */
139 - private function cancel($id)
190 + private function cancel($id) : bool
140 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 + }
141 195 // Cannot cancel if it has been processed.
142 196 if (!isset($this->handles[$id])) {
143 197 return \false;
144 198 }
@@ -144,14 +198,20 @@
144 198 }
145 199 $handle = $this->handles[$id]['easy']->handle;
146 200 unset($this->delays[$id], $this->handles[$id]);
147 201 \curl_multi_remove_handle($this->_mh, $handle);
148 - \curl_close($handle);
202 + if (\PHP_VERSION_ID < 80000) {
203 + \curl_close($handle);
204 + }
149 205 return \true;
150 206 }
151 - private function processMessages()
207 + private function processMessages() : void
152 208 {
153 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 + }
154 214 $id = (int) $done['handle'];
155 215 \curl_multi_remove_handle($this->_mh, $done['handle']);
156 216 if (!isset($this->handles[$id])) {
157 217 // Probably was cancelled.
@@ -162,9 +222,9 @@
162 222 $entry['easy']->errno = $done['result'];
163 223 $entry['deferred']->resolve(CurlFactory::finish($this, $entry['easy'], $this->factory));
164 224 }
165 225 }
166 - private function timeToNext()
226 + private function timeToNext() : int
167 227 {
168 228 $currentTime = Utils::currentTime();
169 229 $nextTime = \PHP_INT_MAX;
170 230 foreach ($this->delays as $time) {
@@ -171,7 +231,7 @@
171 231 if ($time < $nextTime) {
172 232 $nextTime = $time;
173 233 }
174 234 }
175 - return \max(0, $nextTime - $currentTime) * 1000000;
235 + return (int) \max(0, $nextTime - $currentTime) * 1000000;
176 236 }
177 237 }