class DriverSuspension
@internal
@template T @implements Suspension<T>
Hierarchy
- class \Revolt\EventLoop\Internal\DriverSuspension implements \Revolt\EventLoop\Suspension
Expanded class hierarchy of DriverSuspension
File
-
vendor/
revolt/ event-loop/ src/ EventLoop/ Internal/ DriverSuspension.php, line 17
Namespace
Revolt\EventLoop\InternalView source
final class DriverSuspension implements Suspension {
private ?\Fiber $suspendedFiber = null;
/** @var \WeakReference<\Fiber>|null */
private readonly ?\WeakReference $fiberRef;
private ?\Error $error = null;
private bool $pending = false;
private bool $deadMain = false;
/**
* @param \WeakMap<object, \WeakReference<DriverSuspension>> $suspensions
*/
public function __construct(\Closure $run, \Closure $queue, \Closure $interrupt, \WeakMap $suspensions) {
$fiber = \Fiber::getCurrent();
$this->fiberRef = $fiber ? \WeakReference::create($fiber) : null;
}
public function resume(mixed $value = null) : void {
// Ignore spurious resumes to old dead {main} suspension
if ($this->deadMain) {
return;
}
if (!$this->pending) {
throw $this->error ?? new \Error('Must call suspend() before calling resume()');
}
$this->pending = false;
/** @var \Fiber|null $fiber */
$fiber = $this->fiberRef?->get();
if ($fiber) {
($this->queue)(static function () use ($fiber, $value) : void {
// The fiber may be destroyed with suspension as part of the GC cycle collector.
if (!$fiber->isTerminated()) {
$fiber->resume($value);
}
});
}
else {
// Suspend event loop fiber to {main}.
($this->interrupt)(static fn() => $value);
}
}
public function suspend() : mixed {
// Throw exception when trying to use old dead {main} suspension
if ($this->deadMain) {
throw new \Error('Suspension cannot be suspended after an uncaught exception is thrown from the event loop');
}
if ($this->pending) {
throw new \Error('Must call resume() or throw() before calling suspend() again');
}
$fiber = $this->fiberRef?->get();
if ($fiber !== \Fiber::getCurrent()) {
throw new \Error('Must not call suspend() from another fiber');
}
$this->pending = true;
$this->error = null;
// Awaiting from within a fiber.
if ($fiber) {
$this->suspendedFiber = $fiber;
try {
$value = \Fiber::suspend();
$this->suspendedFiber = null;
} catch (\FiberError $error) {
$this->pending = false;
$this->suspendedFiber = null;
$this->error = $error;
throw $error;
}
// Setting $this->suspendedFiber = null in finally will set the fiber to null if a fiber is destroyed
// as part of a cycle collection, causing an error if the suspension is subsequently resumed.
return $value;
}
// Awaiting from {main}.
$result = ($this->run)();
/** @psalm-suppress RedundantCondition $this->pending should be changed when resumed. */
if ($this->pending) {
// This is now a dead {main} suspension.
$this->deadMain = true;
// Unset suspension for {main} using queue closure.
unset($this->suspensions[$this->queue]);
$result && $result();
// Unwrap any uncaught exceptions from the event loop
\gc_collect_cycles();
// Collect any circular references before dumping pending suspensions.
$info = '';
foreach ($this->suspensions as $suspensionRef) {
if ($suspension = $suspensionRef->get()) {
\assert($suspension instanceof self);
$fiber = $suspension->fiberRef?->get();
if ($fiber === null) {
continue;
}
$reflectionFiber = new \ReflectionFiber($fiber);
$info .= "\n\n" . $this->formatStacktrace($reflectionFiber->getTrace(\DEBUG_BACKTRACE_IGNORE_ARGS));
}
}
throw new \Error('Event loop terminated without resuming the current suspension (the cause is either a fiber deadlock, or an incorrectly unreferenced/canceled watcher):' . $info);
}
return $result();
}
public function throw(\Throwable $throwable) : void {
// Ignore spurious resumes to old dead {main} suspension
if ($this->deadMain) {
return;
}
if (!$this->pending) {
throw $this->error ?? new \Error('Must call suspend() before calling throw()');
}
$this->pending = false;
/** @var \Fiber|null $fiber */
$fiber = $this->fiberRef?->get();
if ($fiber) {
($this->queue)(static function () use ($fiber, $throwable) : void {
// The fiber may be destroyed with suspension as part of the GC cycle collector.
if (!$fiber->isTerminated()) {
$fiber->throw($throwable);
}
});
}
else {
// Suspend event loop fiber to {main}.
($this->interrupt)(static fn() => throw $throwable);
}
}
private function formatStacktrace(array $trace) : string {
return \implode("\n", \array_map(static function ($e, $i) {
$line = "#{$i} ";
if (isset($e["file"])) {
$line .= "{$e['file']}:{$e['line']} ";
}
if (isset($e["class"], $e["type"])) {
$line .= $e["class"] . $e["type"];
}
return $line . $e["function"] . "()";
}, $trace, \array_keys($trace)));
}
}
Members
Title Sort descending | Modifiers | Object type | Summary | Overriden Title |
---|---|---|---|---|
DriverSuspension::$deadMain | private | property | ||
DriverSuspension::$error | private | property | ||
DriverSuspension::$fiberRef | private | property | @var \WeakReference<\Fiber>|null | |
DriverSuspension::$pending | private | property | ||
DriverSuspension::$suspendedFiber | private | property | ||
DriverSuspension::formatStacktrace | private | function | ||
DriverSuspension::resume | public | function | Overrides Suspension::resume | |
DriverSuspension::suspend | public | function | Returns the value provided to { | Overrides Suspension::suspend |
DriverSuspension::throw | public | function | Throws the given exception from the call to { | Overrides Suspension::throw |
DriverSuspension::__construct | public | function |