2020-05-13 17:15:21 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Amp\Internal;
|
|
|
|
|
|
|
|
use Amp\Deferred;
|
|
|
|
use Amp\DisposedException;
|
2020-10-04 17:22:51 +02:00
|
|
|
use Amp\Failure;
|
2020-07-29 17:29:57 +02:00
|
|
|
use Amp\Loop;
|
2020-08-23 16:18:28 +02:00
|
|
|
use Amp\Pipeline;
|
2020-05-13 17:15:21 +02:00
|
|
|
use Amp\Promise;
|
2020-10-04 17:22:51 +02:00
|
|
|
use Amp\Success;
|
2020-11-03 23:55:29 +01:00
|
|
|
use function Amp\defer;
|
2020-05-13 17:15:21 +02:00
|
|
|
|
|
|
|
/**
|
2020-08-23 16:18:28 +02:00
|
|
|
* Class used internally by {@see Pipeline} implementations. Do not use this class in your code, instead compose your
|
|
|
|
* class from one of the available classes implementing {@see Pipeline}.
|
2020-05-13 17:15:21 +02:00
|
|
|
*
|
|
|
|
* @internal
|
|
|
|
*
|
|
|
|
* @template TValue
|
|
|
|
* @template TSend
|
|
|
|
*/
|
2020-05-28 19:59:55 +02:00
|
|
|
final class EmitSource
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-09-24 18:52:22 +02:00
|
|
|
private bool $completed = false;
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-11-03 23:55:29 +01:00
|
|
|
private \Throwable $exception;
|
|
|
|
|
|
|
|
/** @var mixed[] */
|
2020-09-24 18:52:22 +02:00
|
|
|
private array $emittedValues = [];
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-11-18 00:28:28 +01:00
|
|
|
/** @var [\Throwable, mixed][] */
|
2020-09-24 18:52:22 +02:00
|
|
|
private array $sendValues = [];
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-11-03 23:55:29 +01:00
|
|
|
/** @var Deferred[] */
|
2020-09-24 18:52:22 +02:00
|
|
|
private array $backPressure = [];
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-11-18 00:28:28 +01:00
|
|
|
/** @var \Continuation[] */
|
|
|
|
private array $yielding = [];
|
|
|
|
|
2020-11-05 18:29:31 +01:00
|
|
|
/** @var \Continuation[] */
|
2020-09-24 18:52:22 +02:00
|
|
|
private array $waiting = [];
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-24 18:52:22 +02:00
|
|
|
private int $consumePosition = 0;
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-24 18:52:22 +02:00
|
|
|
private int $emitPosition = 0;
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-25 05:14:58 +02:00
|
|
|
private ?array $resolutionTrace = null;
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-24 18:52:22 +02:00
|
|
|
private bool $disposed = false;
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-24 18:52:22 +02:00
|
|
|
private bool $used = false;
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-11-03 23:55:29 +01:00
|
|
|
/** @var callable[]|null */
|
2020-09-24 18:52:22 +02:00
|
|
|
private ?array $onDisposal = [];
|
2020-07-29 17:29:57 +02:00
|
|
|
|
2020-05-13 17:15:21 +02:00
|
|
|
/**
|
2020-09-25 05:14:58 +02:00
|
|
|
* @psalm-return TValue
|
2020-05-13 17:15:21 +02:00
|
|
|
*/
|
2020-09-25 05:14:58 +02:00
|
|
|
public function continue(): mixed
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-11-18 00:28:28 +01:00
|
|
|
return $this->next(null, null);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-param TSend $value
|
|
|
|
*
|
2020-11-18 00:28:28 +01:00
|
|
|
* @psalm-return TValue
|
2020-05-13 17:15:21 +02:00
|
|
|
*/
|
2020-09-25 05:14:58 +02:00
|
|
|
public function send(mixed $value): mixed
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
|
|
|
if ($this->consumePosition === 0) {
|
|
|
|
throw new \Error("Must initialize async generator by calling continue() first");
|
|
|
|
}
|
|
|
|
|
2020-11-18 00:28:28 +01:00
|
|
|
return $this->next(null, $value);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-11-18 00:28:28 +01:00
|
|
|
* @psalm-return TValue
|
2020-05-13 17:15:21 +02:00
|
|
|
*/
|
2020-09-25 05:14:58 +02:00
|
|
|
public function throw(\Throwable $exception): mixed
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
|
|
|
if ($this->consumePosition === 0) {
|
|
|
|
throw new \Error("Must initialize async generator by calling continue() first");
|
|
|
|
}
|
|
|
|
|
2020-11-18 00:28:28 +01:00
|
|
|
return $this->next($exception, null);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-11-18 00:28:28 +01:00
|
|
|
* @psalm-param TSend|null $value
|
2020-05-13 17:15:21 +02:00
|
|
|
*
|
2020-09-25 05:14:58 +02:00
|
|
|
* @psalm-return TValue
|
2020-05-13 17:15:21 +02:00
|
|
|
*/
|
2020-11-18 00:28:28 +01:00
|
|
|
private function next(?\Throwable $exception, mixed $value): mixed
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
|
|
|
$position = $this->consumePosition++;
|
|
|
|
|
2020-11-18 00:28:28 +01:00
|
|
|
if (isset($this->yielding[$position - 1])) {
|
|
|
|
$continuation = $this->yielding[$position - 1];
|
|
|
|
unset($this->yielding[$position - 1]);
|
|
|
|
if ($exception) {
|
|
|
|
Loop::defer(static fn() => $continuation->throw($exception));
|
|
|
|
} else {
|
|
|
|
Loop::defer(static fn() => $continuation->resume($value));
|
|
|
|
}
|
|
|
|
} elseif (isset($this->backPressure[$position - 1])) {
|
2020-05-13 17:15:21 +02:00
|
|
|
$deferred = $this->backPressure[$position - 1];
|
|
|
|
unset($this->backPressure[$position - 1]);
|
2020-11-18 00:28:28 +01:00
|
|
|
if ($exception) {
|
|
|
|
$deferred->fail($exception);
|
|
|
|
} else {
|
|
|
|
$deferred->resolve($value);
|
|
|
|
}
|
2020-05-13 17:15:21 +02:00
|
|
|
} elseif ($position > 0) {
|
|
|
|
// Send-values are indexed as $this->consumePosition - 1.
|
2020-11-18 00:28:28 +01:00
|
|
|
$this->sendValues[$position - 1] = [$exception, $value];
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-11-18 00:28:28 +01:00
|
|
|
if (isset($this->emittedValues[$position])) {
|
2020-05-28 19:59:55 +02:00
|
|
|
$value = $this->emittedValues[$position];
|
|
|
|
unset($this->emittedValues[$position]);
|
2020-11-03 23:55:29 +01:00
|
|
|
return $value;
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-11-03 23:55:29 +01:00
|
|
|
if ($this->completed || $this->disposed) {
|
|
|
|
if (isset($this->exception)) {
|
|
|
|
throw $this->exception;
|
|
|
|
}
|
|
|
|
return null;
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-11-05 18:29:31 +01:00
|
|
|
return \Fiber::suspend(
|
|
|
|
fn(\Continuation $continuation) => $this->waiting[$position] = $continuation,
|
|
|
|
Loop::get()
|
|
|
|
);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-08-23 16:18:28 +02:00
|
|
|
public function pipe(): Pipeline
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
|
|
|
if ($this->used) {
|
2020-08-23 16:18:28 +02:00
|
|
|
throw new \Error("A pipeline may be started only once");
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->used = true;
|
|
|
|
|
2020-08-23 16:18:28 +02:00
|
|
|
return new AutoDisposingPipeline($this);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-05-19 17:49:40 +02:00
|
|
|
/**
|
|
|
|
* @return void
|
2020-07-30 21:33:39 +02:00
|
|
|
*
|
2020-08-23 16:18:28 +02:00
|
|
|
* @see Pipeline::dispose()
|
2020-05-19 17:49:40 +02:00
|
|
|
*/
|
2020-09-24 18:52:22 +02:00
|
|
|
public function dispose(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-08-27 19:45:48 +02:00
|
|
|
$this->cancel(true);
|
|
|
|
}
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-09-24 18:52:22 +02:00
|
|
|
public function destroy(): void
|
2020-08-27 19:45:48 +02:00
|
|
|
{
|
|
|
|
$this->cancel(false);
|
|
|
|
}
|
|
|
|
|
2020-09-24 18:52:22 +02:00
|
|
|
private function cancel(bool $cancelPending): void
|
2020-08-27 19:45:48 +02:00
|
|
|
{
|
|
|
|
try {
|
2020-11-03 23:55:29 +01:00
|
|
|
if ($this->completed || $this->disposed) {
|
2020-08-27 19:45:48 +02:00
|
|
|
return; // Pipeline already completed or failed.
|
|
|
|
}
|
|
|
|
|
2020-11-03 23:55:29 +01:00
|
|
|
$this->finalize(new DisposedException, true);
|
2020-08-27 19:45:48 +02:00
|
|
|
} finally {
|
|
|
|
if ($this->disposed && $cancelPending) {
|
|
|
|
$this->triggerDisposal();
|
|
|
|
}
|
|
|
|
}
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-07-29 17:29:57 +02:00
|
|
|
/**
|
|
|
|
* @param callable():void $onDispose
|
|
|
|
*
|
|
|
|
* @return void
|
2020-07-30 21:33:39 +02:00
|
|
|
*
|
2020-08-23 16:18:28 +02:00
|
|
|
* @see Pipeline::onDisposal()
|
2020-07-29 17:29:57 +02:00
|
|
|
*/
|
2020-09-24 18:52:22 +02:00
|
|
|
public function onDisposal(callable $onDisposal): void
|
2020-07-29 17:29:57 +02:00
|
|
|
{
|
2020-07-30 21:33:39 +02:00
|
|
|
if ($this->disposed) {
|
2020-11-03 23:55:29 +01:00
|
|
|
defer($onDisposal);
|
2020-07-29 17:29:57 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-11-03 23:55:29 +01:00
|
|
|
if ($this->completed) {
|
2020-07-30 21:33:39 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-29 17:29:57 +02:00
|
|
|
$this->onDisposal[] = $onDisposal;
|
|
|
|
}
|
|
|
|
|
2020-05-13 17:15:21 +02:00
|
|
|
/**
|
2020-08-23 16:18:28 +02:00
|
|
|
* Emits a value from the pipeline. The returned promise is resolved once the emitted value has been consumed or
|
|
|
|
* if the pipeline is completed, failed, or disposed.
|
2020-05-13 17:15:21 +02:00
|
|
|
*
|
|
|
|
* @param mixed $value
|
2020-11-18 00:28:28 +01:00
|
|
|
* @param int $position
|
2020-05-13 17:15:21 +02:00
|
|
|
*
|
|
|
|
* @psalm-param TValue $value
|
|
|
|
*
|
2020-05-18 20:49:56 +02:00
|
|
|
* @return Promise<mixed> Resolves with the sent value once the value has been consumed. Fails with the failure
|
2020-08-23 16:18:28 +02:00
|
|
|
* reason if the {@see fail()} is called, or with {@see DisposedException} if the pipeline
|
2020-05-18 20:49:56 +02:00
|
|
|
* is destroyed.
|
2020-05-13 17:15:21 +02:00
|
|
|
*
|
2020-05-18 20:49:56 +02:00
|
|
|
* @psalm-return Promise<TSend|null>
|
2020-05-13 17:15:21 +02:00
|
|
|
*
|
2020-08-23 16:18:28 +02:00
|
|
|
* @throws \Error If the pipeline has completed.
|
2020-05-13 17:15:21 +02:00
|
|
|
*/
|
2020-11-18 00:28:28 +01:00
|
|
|
private function push(mixed $value, int $position): ?array
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-05-21 17:11:22 +02:00
|
|
|
if ($value === null) {
|
2020-08-23 16:18:28 +02:00
|
|
|
throw new \TypeError("Pipelines cannot emit NULL");
|
2020-05-19 00:01:14 +02:00
|
|
|
}
|
|
|
|
|
2020-10-30 16:17:52 +01:00
|
|
|
if ($value instanceof Promise) {
|
2020-08-23 16:18:28 +02:00
|
|
|
throw new \TypeError("Pipelines cannot emit promises");
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isset($this->waiting[$position])) {
|
2020-11-05 18:29:31 +01:00
|
|
|
$continuation = $this->waiting[$position];
|
2020-05-13 17:15:21 +02:00
|
|
|
unset($this->waiting[$position]);
|
2020-11-05 18:29:31 +01:00
|
|
|
Loop::defer(static fn() => $continuation->resume($value));
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-11-17 00:20:13 +01:00
|
|
|
if ($this->disposed && empty($this->waiting)) {
|
|
|
|
\assert(empty($this->sendValues)); // If $this->waiting is empty, $this->sendValues must be.
|
|
|
|
$this->triggerDisposal();
|
2020-11-18 00:28:28 +01:00
|
|
|
return [null, null]; // Subsequent push() calls will throw.
|
2020-11-17 00:20:13 +01:00
|
|
|
}
|
|
|
|
|
2020-05-13 17:15:21 +02:00
|
|
|
// Send-values are indexed as $this->consumePosition - 1, so use $position for the next value.
|
|
|
|
if (isset($this->sendValues[$position])) {
|
2020-11-18 00:28:28 +01:00
|
|
|
$pair = $this->sendValues[$position];
|
2020-05-13 17:15:21 +02:00
|
|
|
unset($this->sendValues[$position]);
|
2020-11-18 00:28:28 +01:00
|
|
|
return $pair;
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
2020-11-03 23:55:29 +01:00
|
|
|
} elseif ($this->completed) {
|
|
|
|
throw new \Error("Pipelines cannot emit values after calling complete");
|
2020-11-17 00:20:13 +01:00
|
|
|
} elseif ($this->disposed) {
|
|
|
|
\assert(isset($this->exception), "Failure exception must be set when disposed");
|
|
|
|
// Pipeline has been disposed and no Continuations are still pending.
|
2020-11-18 00:28:28 +01:00
|
|
|
return [$this->exception, null];
|
2020-05-13 17:15:21 +02:00
|
|
|
} else {
|
2020-05-28 19:59:55 +02:00
|
|
|
$this->emittedValues[$position] = $value;
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-11-18 00:28:28 +01:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-param TValue $value
|
|
|
|
* @psalm-return Promise<TSend>
|
|
|
|
*/
|
|
|
|
public function emit(mixed $value): Promise
|
|
|
|
{
|
|
|
|
$position = $this->emitPosition;
|
|
|
|
|
|
|
|
$pair = $this->push($value, $position);
|
|
|
|
|
|
|
|
++$this->emitPosition;
|
|
|
|
|
|
|
|
if ($pair === null) {
|
|
|
|
$this->backPressure[$position] = $deferred = new Deferred;
|
|
|
|
return $deferred->promise();
|
|
|
|
}
|
|
|
|
|
|
|
|
[$exception, $value] = $pair;
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-11-18 00:28:28 +01:00
|
|
|
if ($exception) {
|
|
|
|
return new Failure($exception);
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Success($value);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @psalm-param TValue $value
|
|
|
|
* @psalm-return TSend
|
|
|
|
*/
|
|
|
|
public function yield(mixed $value): mixed
|
|
|
|
{
|
|
|
|
$position = $this->emitPosition;
|
|
|
|
|
|
|
|
$pair = $this->push($value, $position);
|
|
|
|
|
|
|
|
++$this->emitPosition;
|
|
|
|
|
|
|
|
if ($pair === null) {
|
|
|
|
return \Fiber::suspend(
|
|
|
|
fn(\Continuation $continuation) => $this->yielding[$position] = $continuation,
|
|
|
|
Loop::get()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
[$exception, $value] = $pair;
|
|
|
|
|
|
|
|
if ($exception) {
|
|
|
|
throw $exception;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $value;
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-07-16 20:50:38 +02:00
|
|
|
/**
|
2020-08-23 16:18:28 +02:00
|
|
|
* @return bool True if the pipeline has been completed or failed.
|
2020-07-16 20:50:38 +02:00
|
|
|
*/
|
|
|
|
public function isComplete(): bool
|
|
|
|
{
|
|
|
|
return $this->completed;
|
|
|
|
}
|
|
|
|
|
2020-07-17 18:22:13 +02:00
|
|
|
/**
|
2020-08-23 16:18:28 +02:00
|
|
|
* @return bool True if the pipeline was disposed.
|
2020-07-17 18:22:13 +02:00
|
|
|
*/
|
|
|
|
public function isDisposed(): bool
|
|
|
|
{
|
2020-08-24 23:28:08 +02:00
|
|
|
return $this->disposed && empty($this->waiting);
|
2020-07-17 18:22:13 +02:00
|
|
|
}
|
|
|
|
|
2020-05-13 17:15:21 +02:00
|
|
|
/**
|
2020-08-23 16:18:28 +02:00
|
|
|
* Completes the pipeline.
|
2020-07-30 21:33:39 +02:00
|
|
|
*
|
2020-05-13 17:15:21 +02:00
|
|
|
* @return void
|
|
|
|
*
|
|
|
|
* @throws \Error If the iterator has already been completed.
|
|
|
|
*/
|
2020-09-24 18:52:22 +02:00
|
|
|
public function complete(): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-11-03 23:55:29 +01:00
|
|
|
$this->finalize();
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-08-23 16:18:28 +02:00
|
|
|
* Fails the pipeline.
|
2020-05-13 17:15:21 +02:00
|
|
|
*
|
|
|
|
* @param \Throwable $exception
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-09-24 18:52:22 +02:00
|
|
|
public function fail(\Throwable $exception): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
2020-11-03 23:55:29 +01:00
|
|
|
$this->finalize($exception);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-11-03 23:55:29 +01:00
|
|
|
* @param \Throwable|null $exception Failure reason or null for success.
|
|
|
|
* @param bool $disposed Flag if the generator was disposed.
|
2020-05-13 17:15:21 +02:00
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-11-03 23:55:29 +01:00
|
|
|
private function finalize(?\Throwable $exception = null, bool $disposed = false): void
|
2020-05-13 17:15:21 +02:00
|
|
|
{
|
|
|
|
if ($this->completed) {
|
2020-08-23 16:18:28 +02:00
|
|
|
$message = "Pipeline has already been completed";
|
2020-05-13 17:15:21 +02:00
|
|
|
|
|
|
|
if (isset($this->resolutionTrace)) {
|
|
|
|
$trace = formatStacktrace($this->resolutionTrace);
|
|
|
|
$message .= ". Previous completion trace:\n\n{$trace}\n\n";
|
|
|
|
} else {
|
|
|
|
// @codeCoverageIgnoreStart
|
|
|
|
$message .= ", define environment variable AMP_DEBUG or const AMP_DEBUG = true and enable assertions "
|
|
|
|
. "for a stacktrace of the previous resolution.";
|
|
|
|
// @codeCoverageIgnoreEnd
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new \Error($message);
|
|
|
|
}
|
|
|
|
|
2020-08-21 21:12:56 +02:00
|
|
|
$this->completed = $this->completed ?: !$disposed; // $disposed is false if complete() or fail() invoked
|
2020-07-29 17:29:57 +02:00
|
|
|
$this->disposed = $this->disposed ?: $disposed; // Once disposed, do not change flag
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-07-30 21:33:39 +02:00
|
|
|
if ($this->completed) { // Record stack trace when calling complete() or fail()
|
|
|
|
\assert((function () {
|
|
|
|
if (isDebugEnabled()) {
|
|
|
|
$trace = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
|
|
|
|
\array_shift($trace); // remove current closure
|
|
|
|
$this->resolutionTrace = $trace;
|
|
|
|
}
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-07-30 21:33:39 +02:00
|
|
|
return true;
|
|
|
|
})());
|
|
|
|
}
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-11-03 23:55:29 +01:00
|
|
|
if (isset($this->exception)) {
|
2020-07-30 21:33:39 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-11-03 23:55:29 +01:00
|
|
|
if ($exception !== null) {
|
|
|
|
$this->exception = $exception;
|
|
|
|
}
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-08-27 19:45:48 +02:00
|
|
|
if ($this->disposed) {
|
2020-08-24 23:28:08 +02:00
|
|
|
if (empty($this->waiting)) {
|
|
|
|
$this->triggerDisposal();
|
|
|
|
}
|
|
|
|
} else {
|
2020-11-03 23:55:29 +01:00
|
|
|
Loop::defer(fn() => $this->resolvePending());
|
2020-08-27 19:45:48 +02:00
|
|
|
}
|
|
|
|
}
|
2020-08-24 23:28:08 +02:00
|
|
|
|
2020-08-27 19:45:48 +02:00
|
|
|
/**
|
2020-11-18 00:28:28 +01:00
|
|
|
* Resolves all backpressure and outstanding calls for emitted values.
|
2020-08-27 19:45:48 +02:00
|
|
|
*/
|
2020-09-24 18:52:22 +02:00
|
|
|
private function resolvePending(): void
|
2020-08-27 19:45:48 +02:00
|
|
|
{
|
2020-11-18 00:28:28 +01:00
|
|
|
$backPressure = \array_merge($this->backPressure, $this->yielding);
|
2020-08-27 19:45:48 +02:00
|
|
|
$waiting = $this->waiting;
|
2020-11-17 00:20:13 +01:00
|
|
|
|
2020-11-18 00:28:28 +01:00
|
|
|
unset($this->waiting, $this->backPressure, $this->yielding);
|
|
|
|
|
|
|
|
$exception = isset($this->exception) ? $this->exception : null;
|
2020-08-27 19:45:48 +02:00
|
|
|
|
|
|
|
foreach ($backPressure as $deferred) {
|
2020-11-18 00:28:28 +01:00
|
|
|
if ($deferred instanceof \Continuation) {
|
|
|
|
// Using a defer watcher to maintain backpressure execution order.
|
|
|
|
if ($exception) {
|
|
|
|
Loop::defer(static fn() => $deferred->throw($exception));
|
|
|
|
} else {
|
|
|
|
Loop::defer(static fn() => $deferred->resume());
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($exception) {
|
|
|
|
$deferred->fail($exception);
|
2020-11-03 23:55:29 +01:00
|
|
|
} else {
|
|
|
|
$deferred->resolve();
|
|
|
|
}
|
2020-08-27 19:45:48 +02:00
|
|
|
}
|
|
|
|
|
2020-11-05 18:29:31 +01:00
|
|
|
foreach ($waiting as $continuation) {
|
2020-11-18 00:28:28 +01:00
|
|
|
if ($exception) {
|
2020-11-05 18:29:31 +01:00
|
|
|
$continuation->throw($this->exception);
|
2020-11-03 23:55:29 +01:00
|
|
|
} else {
|
2020-11-05 18:29:31 +01:00
|
|
|
$continuation->resume();
|
2020-11-03 23:55:29 +01:00
|
|
|
}
|
2020-08-24 23:28:08 +02:00
|
|
|
}
|
|
|
|
}
|
2020-05-13 17:15:21 +02:00
|
|
|
|
2020-08-27 19:45:48 +02:00
|
|
|
/**
|
|
|
|
* Invokes all pending {@see onDisposal()} callbacks and fails pending {@see continue()} promises.
|
|
|
|
*/
|
2020-09-24 18:52:22 +02:00
|
|
|
private function triggerDisposal(): void
|
2020-08-24 23:28:08 +02:00
|
|
|
{
|
2020-08-27 19:45:48 +02:00
|
|
|
\assert($this->disposed, "Pipeline was not disposed on triggering disposal");
|
|
|
|
|
2020-08-24 23:28:08 +02:00
|
|
|
if ($this->onDisposal === null) {
|
|
|
|
return;
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
|
2020-07-30 21:33:39 +02:00
|
|
|
$onDisposal = $this->onDisposal;
|
|
|
|
$this->onDisposal = null;
|
|
|
|
|
2020-11-03 23:55:29 +01:00
|
|
|
Loop::defer(fn() => $this->resolvePending());
|
2020-07-29 17:29:57 +02:00
|
|
|
|
2020-08-24 23:28:08 +02:00
|
|
|
/** @psalm-suppress PossiblyNullIterator $alreadyDisposed is a guard against $this->onDisposal being null */
|
|
|
|
foreach ($onDisposal as $callback) {
|
2020-11-03 23:55:29 +01:00
|
|
|
defer($callback);
|
2020-05-13 17:15:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|