Browse code

added appinfo/info.xml appinfo/signature.json CHANGELOG.txt lib/AppInfo/Application.php css/style.css providers/Plivo

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

removed appinfo/info.xml appinfo/signature.json CHANGELOG.txt lib/AppInfo/Application.php css/style.css providers/Plivo

DoubleBastionAdmin authored on 05/11/2025 13:12:22
Showing 1 changed files
1 1
deleted file mode 100644
... ...
@@ -1,253 +0,0 @@
1
-<?php
2
-
3
-namespace GuzzleHttp\Handler;
4
-
5
-use GuzzleHttp\Promise as P;
6
-use GuzzleHttp\Promise\Promise;
7
-use GuzzleHttp\Promise\PromiseInterface;
8
-use GuzzleHttp\Utils;
9
-use 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
-        // Cannot cancel if it has been processed.
209
-        if (!isset($this->handles[$id])) {
210
-            return false;
211
-        }
212
-
213
-        $handle = $this->handles[$id]['easy']->handle;
214
-        unset($this->delays[$id], $this->handles[$id]);
215
-        \curl_multi_remove_handle($this->_mh, $handle);
216
-        \curl_close($handle);
217
-
218
-        return true;
219
-    }
220
-
221
-    private function processMessages(): void
222
-    {
223
-        while ($done = \curl_multi_info_read($this->_mh)) {
224
-            $id = (int) $done['handle'];
225
-            \curl_multi_remove_handle($this->_mh, $done['handle']);
226
-
227
-            if (!isset($this->handles[$id])) {
228
-                // Probably was cancelled.
229
-                continue;
230
-            }
231
-
232
-            $entry = $this->handles[$id];
233
-            unset($this->handles[$id], $this->delays[$id]);
234
-            $entry['easy']->errno = $done['result'];
235
-            $entry['deferred']->resolve(
236
-                CurlFactory::finish($this, $entry['easy'], $this->factory)
237
-            );
238
-        }
239
-    }
240
-
241
-    private function timeToNext(): int
242
-    {
243
-        $currentTime = Utils::currentTime();
244
-        $nextTime = \PHP_INT_MAX;
245
-        foreach ($this->delays as $time) {
246
-            if ($time < $nextTime) {
247
-                $nextTime = $time;
248
-            }
249
-        }
250
-
251
-        return ((int) \max(0, $nextTime - $currentTime)) * 1000000;
252
-    }
253
-}
Browse code

Added README.md appinfo/info.xml appinfo/signature.json lib/Controller/AuthorApiController.php and the providers directory

DoubleBastionAdmin authored on 20/08/2022 16:33:00
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,253 @@
1
+<?php
2
+
3
+namespace GuzzleHttp\Handler;
4
+
5
+use GuzzleHttp\Promise as P;
6
+use GuzzleHttp\Promise\Promise;
7
+use GuzzleHttp\Promise\PromiseInterface;
8
+use GuzzleHttp\Utils;
9
+use 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
+        // Cannot cancel if it has been processed.
209
+        if (!isset($this->handles[$id])) {
210
+            return false;
211
+        }
212
+
213
+        $handle = $this->handles[$id]['easy']->handle;
214
+        unset($this->delays[$id], $this->handles[$id]);
215
+        \curl_multi_remove_handle($this->_mh, $handle);
216
+        \curl_close($handle);
217
+
218
+        return true;
219
+    }
220
+
221
+    private function processMessages(): void
222
+    {
223
+        while ($done = \curl_multi_info_read($this->_mh)) {
224
+            $id = (int) $done['handle'];
225
+            \curl_multi_remove_handle($this->_mh, $done['handle']);
226
+
227
+            if (!isset($this->handles[$id])) {
228
+                // Probably was cancelled.
229
+                continue;
230
+            }
231
+
232
+            $entry = $this->handles[$id];
233
+            unset($this->handles[$id], $this->delays[$id]);
234
+            $entry['easy']->errno = $done['result'];
235
+            $entry['deferred']->resolve(
236
+                CurlFactory::finish($this, $entry['easy'], $this->factory)
237
+            );
238
+        }
239
+    }
240
+
241
+    private function timeToNext(): int
242
+    {
243
+        $currentTime = Utils::currentTime();
244
+        $nextTime = \PHP_INT_MAX;
245
+        foreach ($this->delays as $time) {
246
+            if ($time < $nextTime) {
247
+                $nextTime = $time;
248
+            }
249
+        }
250
+
251
+        return ((int) \max(0, $nextTime - $currentTime)) * 1000000;
252
+    }
253
+}