PluginProbe
Gmail SMTP / trunk
Gmail SMTP vtrunk
1.2.3.21 1.2.3.20 trunk 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.9 1.2.0 1.2.3.14 1.2.3.15 1.2.3.16 1.2.3.18 1.2.3.5
gmail-smtp / google-api-php-client / vendor / guzzlehttp / promises / src / EachPromise.php

EachPromise.php in Gmail SMTP trunk, at google-api-php-client/vendor/guzzlehttp/promises/src/EachPromise.php

274 lines 8.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace GuzzleHttp\Promise;
6
7 /**
8 * Represents a promise that iterates over many promises and invokes
9 * side-effect functions in the process.
10 *
11 * @final
12 */
13 class EachPromise implements PromisorInterface
14 {
15 private $pending = [];
16
17 private $nextPendingIndex = 0;
18
19 /** @var \Iterator|null */
20 private $iterable;
21
22 /** @var callable|int|null */
23 private $concurrency;
24
25 /** @var callable|null */
26 private $onFulfilled;
27
28 /** @var callable|null */
29 private $onRejected;
30
31 /** @var Promise|null */
32 private $aggregate;
33
34 /** @var bool|null */
35 private $mutex;
36
37 /**
38 * Configuration hash can include the following key value pairs:
39 *
40 * - fulfilled: (callable) Invoked when a promise fulfills. The function
41 * is invoked with three arguments: the fulfillment value, the index
42 * position from the iterable list of the promise, and the aggregate
43 * promise that manages all of the promises. The aggregate promise may
44 * be resolved from within the callback to short-circuit the promise.
45 * - rejected: (callable) Invoked when a promise is rejected. The
46 * function is invoked with three arguments: the rejection reason, the
47 * index position from the iterable list of the promise, and the
48 * aggregate promise that manages all of the promises. The aggregate
49 * promise may be resolved from within the callback to short-circuit
50 * the promise.
51 * - concurrency: (integer) Pass this configuration option to limit the
52 * allowed number of outstanding concurrently executing promises,
53 * creating a capped pool of promises. There is no limit by default.
54 *
55 * @param mixed $iterable Promises or values to iterate.
56 * @param array $config Configuration options
57 */
58 public function __construct($iterable, array $config = [])
59 {
60 if (!is_iterable($iterable)) {
61 \trigger_deprecation(
62 'guzzlehttp/promises',
63 '2.5',
64 'Passing a non-iterable to %s::%s() is deprecated; guzzlehttp/promises 3.0 will require an iterable.',
65 __CLASS__,
66 __FUNCTION__
67 );
68
69 $iterable = [$iterable];
70 }
71
72 $this->iterable = Create::iterFor($iterable);
73
74 if (isset($config['concurrency'])) {
75 $this->concurrency = $config['concurrency'];
76 }
77
78 if (isset($config['fulfilled'])) {
79 $this->onFulfilled = $config['fulfilled'];
80 }
81
82 if (isset($config['rejected'])) {
83 $this->onRejected = $config['rejected'];
84 }
85 }
86
87 /** @psalm-suppress InvalidNullableReturnType */
88 public function promise(): PromiseInterface
89 {
90 if ($this->aggregate) {
91 return $this->aggregate;
92 }
93
94 try {
95 $this->createPromise();
96 /** @psalm-assert Promise $this->aggregate */
97 $this->iterable->rewind();
98 $this->refillPending();
99 if (!$this->pending) {
100 Utils::queue()->add(function (): void {
101 if (!$this->aggregate || Is::settled($this->aggregate)) {
102 return;
103 }
104
105 try {
106 $this->checkIfFinished();
107 } catch (\Throwable $e) {
108 $this->aggregate->reject($e);
109 }
110 });
111 }
112 } catch (\Throwable $e) {
113 $this->aggregate->reject($e);
114 }
115
116 /**
117 * @psalm-suppress NullableReturnStatement
118 */
119 return $this->aggregate;
120 }
121
122 private function createPromise(): void
123 {
124 $this->mutex = false;
125 $this->aggregate = new Promise(function (): void {
126 if ($this->checkIfFinished()) {
127 return;
128 }
129 reset($this->pending);
130 // Consume a potentially fluctuating list of promises while
131 // ensuring that indexes are maintained (precluding array_shift).
132 while ($promise = current($this->pending)) {
133 next($this->pending);
134 $promise->wait();
135 if (Is::settled($this->aggregate)) {
136 return;
137 }
138 }
139 });
140
141 // Clear the references when the promise is resolved.
142 $clearFn = function (): void {
143 $this->iterable = $this->concurrency = $this->pending = null;
144 $this->onFulfilled = $this->onRejected = null;
145 $this->nextPendingIndex = 0;
146 };
147
148 $this->aggregate->then($clearFn, $clearFn);
149 }
150
151 private function refillPending(): void
152 {
153 if (!$this->concurrency) {
154 // Add all pending promises.
155 while ($this->addPending() && $this->advanceIterator()) {
156 }
157
158 return;
159 }
160
161 // Add only up to N pending promises.
162 $concurrency = is_callable($this->concurrency)
163 ? ($this->concurrency)(count($this->pending))
164 : $this->concurrency;
165 $concurrency = max($concurrency - count($this->pending), 0);
166 // Concurrency may be set to 0 to disallow new promises.
167 if (!$concurrency) {
168 return;
169 }
170 // Add the first pending promise.
171 $this->addPending();
172 // Note this is special handling for concurrency=1 so that we do
173 // not advance the iterator after adding the first promise. This
174 // helps work around issues with generators that might not have the
175 // next value to yield until promise callbacks are called.
176 while (--$concurrency
177 && $this->advanceIterator()
178 && $this->addPending()) {
179 }
180 }
181
182 private function addPending(): bool
183 {
184 if (!$this->iterable || !$this->iterable->valid()) {
185 return false;
186 }
187
188 $promise = Create::promiseFor($this->iterable->current());
189 $key = $this->iterable->key();
190
191 // Iterable keys may not be unique, so we use a counter to
192 // guarantee uniqueness
193 $idx = $this->nextPendingIndex++;
194
195 $this->pending[$idx] = $promise->then(
196 function ($value) use ($idx, $key): void {
197 if ($this->onFulfilled) {
198 ($this->onFulfilled)(
199 $value,
200 $key,
201 $this->aggregate
202 );
203 }
204 $this->step($idx);
205 },
206 function ($reason) use ($idx, $key): void {
207 if ($this->onRejected) {
208 ($this->onRejected)(
209 $reason,
210 $key,
211 $this->aggregate
212 );
213 }
214 $this->step($idx);
215 }
216 );
217
218 return true;
219 }
220
221 private function advanceIterator(): bool
222 {
223 // Place a lock on the iterator so that we ensure to not recurse,
224 // preventing fatal generator errors.
225 if ($this->mutex) {
226 return false;
227 }
228
229 $this->mutex = true;
230
231 try {
232 $this->iterable->next();
233 $this->mutex = false;
234
235 return true;
236 } catch (\Throwable $e) {
237 $this->aggregate->reject($e);
238 $this->mutex = false;
239
240 return false;
241 }
242 }
243
244 private function step(int $idx): void
245 {
246 // If the promise was already resolved, then ignore this step.
247 if (Is::settled($this->aggregate)) {
248 return;
249 }
250
251 unset($this->pending[$idx]);
252
253 // Only refill pending promises if we are not locked, preventing the
254 // EachPromise to recursively invoke the provided iterator, which
255 // cause a fatal error: "Cannot resume an already running generator"
256 if ($this->advanceIterator() && !$this->checkIfFinished()) {
257 // Add more pending promises if possible.
258 $this->refillPending();
259 }
260 }
261
262 private function checkIfFinished(): bool
263 {
264 if (!$this->pending && !$this->iterable->valid()) {
265 // Resolve the promise if there's nothing left to do.
266 $this->aggregate->resolve(null);
267
268 return true;
269 }
270
271 return false;
272 }
273 }
274