Skip to content

Commit

Permalink
Fix some mistakes
Browse files Browse the repository at this point in the history
  • Loading branch information
toby7002 committed Sep 27, 2023
1 parent 293912d commit d1a9e96
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 116 deletions.
50 changes: 21 additions & 29 deletions src/thebigcrafter/Hydrogen/Hydrogen.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,43 @@
use thebigcrafter\Hydrogen\future\FutureState;
use thebigcrafter\Hydrogen\tasks\CheckUpdatesTask;

class Hydrogen {
class Hydrogen
{

/**
* Notify if an update is available on Poggit.
*/
public static function checkForUpdates(Plugin $plugin) : void {
public static function checkForUpdates(Plugin $plugin) : void
{
Server::getInstance()->getAsyncPool()->submitTask(new CheckUpdatesTask($plugin->getName(), $plugin->getDescription()->getVersion()));
}

/**
* Creates a new fiber asynchronously using the given closure, returning a Future that is completed with the
* eventual return value of the passed function or will fail if the closure throws an exception.
*
* @template T
*
* @param \Closure(...):T $closure
* @param mixed ...$args Arguments forwarded to the closure when starting the fiber.
*
* @return Future<T>
*/
public static function async(\Closure $closure, mixed ...$args) : Future
{
static $run = null;
public static function async(\Closure $closure, mixed ...$args) : Future
{
static $run = null;

$run ??= static function (FutureState $state, \Closure $closure, array $args) : void {
$s = $state;
$c = $closure;
$run ??= static function (FutureState $state, \Closure $closure, array $args) : void {
$s = $state;
$c = $closure;

/* Null function arguments so an exception thrown from the closure does not contain the FutureState object
* in the stack trace, which would create a circular reference, preventing immediate garbage collection */
$state = $closure = null;
$state = $closure = null;

try {
// Clear $args to allow garbage collection of arguments during fiber execution
$s->complete($c(...$args, ...($args = [])));
} catch (\Throwable $exception) {
$s->error($exception);
}
};
try {
$s->complete($c(...$args, ...($args = [])));
} catch (\Throwable $exception) {
$s->error($exception);
}
};

$state = new FutureState();
$state = new FutureState();

EventLoop::queue($run, $state, $closure, $args);
EventLoop::queue($run, $state, $closure, $args);

return new Future($state);
}
return new Future($state);
}

}
30 changes: 30 additions & 0 deletions src/thebigcrafter/Hydrogen/exceptions/UnhandledFutureError.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

/*
* This file is part of Hydrogen.
* (c) thebigcrafter <[email protected]>
* This source file is subject to the Apache-2.0 license that is bundled
* with this source code in the file LICENSE.
*/

declare(strict_types=1);

namespace thebigcrafter\Hydrogen\exceptions;

class UnhandledFutureError extends \Error
{
public function __construct(\Throwable $previous, ?string $origin = null)
{
$message = 'Unhandled future: ' . $previous::class . ': "' . $previous->getMessage()
. '"; Await the Future with Future::await() before the future is destroyed or use '
. 'Future::ignore() to suppress this exception.';

if ($origin) {
$message .= ' The future has been created at ' . $origin;
} else {
$message .= ' Enable assertions and set AMP_DEBUG=true in the process environment to track its origin.';
}

parent::__construct($message, 0, $previous);
}
}
51 changes: 1 addition & 50 deletions src/thebigcrafter/Hydrogen/future/Future.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,18 @@
use thebigcrafter\Hydrogen\trait\ForbidSerialization;
use function is_array;

/**
* @template-covariant T
*/
final class Future
{
use ForbidCloning;
use ForbidSerialization;

/**
* Iterate over the given futures in completion order.
*
* @template Tk
* @template Tv
*
* @param iterable<Tk, Future<Tv>> $futures
* @param Cancellation|null $cancellation Optional cancellation.
*
* @return iterable<Tk, Future<Tv>>
*/
public static function iterate(iterable $futures, ?Cancellation $cancellation = null) : iterable

Check failure on line 28 in src/thebigcrafter/Hydrogen/future/Future.php

View workflow job for this annotation

GitHub Actions / PHPStan analysis (ubuntu-latest, 8.1)

Method thebigcrafter\Hydrogen\future\Future::iterate() has parameter $futures with no value type specified in iterable type iterable.

Check failure on line 28 in src/thebigcrafter/Hydrogen/future/Future.php

View workflow job for this annotation

GitHub Actions / PHPStan analysis (ubuntu-latest, 8.1)

Method thebigcrafter\Hydrogen\future\Future::iterate() return type has no value type specified in iterable type iterable.
{
$iterator = new FutureIterator($cancellation);

// Directly iterate in case of an array, because there can't be suspensions during iteration
if (is_array($futures)) {
foreach ($futures as $key => $future) {
if (!$future instanceof self) {
Expand All @@ -50,8 +38,6 @@ public static function iterate(iterable $futures, ?Cancellation $cancellation =
}
$iterator->complete();
} else {
// Use separate fiber for iteration over non-array, because not all items might be immediately available
// while other futures are already completed.
EventLoop::queue(static function () use ($futures, $iterator) : void {
try {
foreach ($futures as $key => $future) {
Expand All @@ -72,13 +58,6 @@ public static function iterate(iterable $futures, ?Cancellation $cancellation =
}
}

/**
* @template Tv
*
* @param Tv $value
*
* @return Future<Tv>
*/
public static function complete(mixed $value = null) : self
{
$state = new FutureState();
Expand All @@ -99,21 +78,15 @@ public static function error(\Throwable $throwable) : self
return new self($state);
}

/** @var FutureState<T> */
private readonly FutureState $state;

/**
* @param FutureState<T> $state
*
* @internal Use {@see DeferredFuture} or {@see async()} to create and resolve a Future.
*/
public function __construct(FutureState $state)
{
$this->state = $state;
}

/**
* @return bool True if the operation has completed.
* True if the operation has completed.
*/
public function isComplete() : bool
{
Expand All @@ -122,8 +95,6 @@ public function isComplete() : bool

/**
* Do not forward unhandled errors to the event loop handler.
*
* @return Future<T>
*/
public function ignore() : self
{
Expand All @@ -135,14 +106,6 @@ public function ignore() : self
/**
* Attaches a callback that is invoked if this future completes. The returned future is completed with the return
* value of the callback, or errors with an exception thrown from the callback.
*
* @psalm-suppress InvalidTemplateParam
*
* @template Tr
*
* @param \Closure(T):Tr $map
*
* @return Future<Tr>
*/
public function map(\Closure $map) : self
{
Expand All @@ -168,12 +131,6 @@ public function map(\Closure $map) : self
/**
* Attaches a callback that is invoked if this future errors. The returned future is completed with the return
* value of the callback, or errors with an exception thrown from the callback.
*
* @template Tr
*
* @param \Closure(\Throwable):Tr $catch
*
* @return Future<Tr>
*/
public function catch(\Closure $catch) : self
{
Expand All @@ -199,10 +156,6 @@ public function catch(\Closure $catch) : self
* Attaches a callback that is always invoked when the future is completed. The returned future resolves with the
* same value as this future once the callback has finished execution. If the callback throws, the returned future
* will error with the thrown exception.
*
* @param \Closure():void $finally
*
* @return Future<T>
*/
public function finally(\Closure $finally) : self
{
Expand All @@ -229,8 +182,6 @@ public function finally(\Closure $finally) : self
* Awaits the operation to complete.
*
* Throws an exception if the operation fails.
*
* @return T
*/
public function await(?Cancellation $cancellation = null) : mixed
{
Expand Down
19 changes: 1 addition & 18 deletions src/thebigcrafter/Hydrogen/future/FutureIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,11 @@
use thebigcrafter\Hydrogen\trait\ForbidSerialization;
use function array_key_first;

/**
* @template Tk
* @template Tv
*
* @internal
*/
final class FutureIterator
class FutureIterator
{
use ForbidCloning;
use ForbidSerialization;

/** @var FutureIteratorQueue<Tk, Tv> */
private readonly FutureIteratorQueue $queue;

private readonly Cancellation $cancellation;
Expand All @@ -51,11 +44,6 @@ public function __construct(?Cancellation $cancellation = null)
});
}

/**
* @param FutureState<Tv> $state
* @param Tk $key
* @param Future<Tv> $future
*/
public function enqueue(FutureState $state, mixed $key, Future $future) : void
{
if ($this->complete) {
Expand Down Expand Up @@ -116,9 +104,6 @@ public function error(\Throwable $exception) : void
}
}

/**
* @return null|array{Tk, Future<Tv>}
*/
public function consume() : ?array

Check failure on line 107 in src/thebigcrafter/Hydrogen/future/FutureIterator.php

View workflow job for this annotation

GitHub Actions / PHPStan analysis (ubuntu-latest, 8.1)

Method thebigcrafter\Hydrogen\future\FutureIterator::consume() return type has no value type specified in iterable type array.
{
if ($this->queue->suspension) {
Expand All @@ -134,7 +119,6 @@ public function consume() : ?array

$this->queue->suspension = EventLoop::getSuspension();

/** @var null|array{Tk, Future<Tv>} */
return $this->queue->suspension->suspend();
}

Expand All @@ -143,7 +127,6 @@ public function consume() : ?array

unset($this->queue->items[$key]);

/** @var null|array{Tk, Future<Tv>} */
return $item;
}

Expand Down
4 changes: 1 addition & 3 deletions src/thebigcrafter/Hydrogen/future/FutureIteratorQueue.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,10 @@

use thebigcrafter\Hydrogen\eventLoop\Suspension;

final class FutureIteratorQueue
class FutureIteratorQueue
{
/** @var list<array{Tk, Future<Tv>}> */
public array $items = [];

/** @var array<string, FutureState<Tv>> */
public array $pending = [];

public ?Suspension $suspension = null;
Expand Down
20 changes: 4 additions & 16 deletions src/thebigcrafter/Hydrogen/future/FutureState.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
namespace thebigcrafter\Hydrogen\future;

use thebigcrafter\Hydrogen\EventLoop;
use thebigcrafter\Hydrogen\exceptions\UnhandledFutureError;

final class FutureState
class FutureState
{
// Static so they can be used as array keys
private static string $nextId = 'a';

private bool $complete = false;
Expand All @@ -25,7 +25,6 @@ final class FutureState
/** @var array<string, \Closure(?\Throwable, ?T, string): void> */
private array $callbacks = [];

/** @var T|null */
private mixed $result = null;

private ?\Throwable $throwable = null;
Expand All @@ -44,17 +43,12 @@ public function __destruct()
* Registers a callback to be notified once the operation is complete or errored.
*
* The callback is invoked directly from the event loop context, so suspension within the callback is not possible.
*
* @param \Closure(?\Throwable, ?T, string): void $callback Callback invoked on error / successful completion of
* the future.
*
* @return string Identifier that can be used to cancel interest for this future.
*/
public function subscribe(\Closure $callback) : string
{
$id = self::$nextId++;

$this->handled = true; // Even if unsubscribed later, consider the future handled.
$this->handled = true;

if ($this->complete) {
EventLoop::queue($callback, $this->throwable, $this->result, $id);
Expand All @@ -69,8 +63,6 @@ public function subscribe(\Closure $callback) : string
* Cancels a subscription.
*
* Cancellations are advisory only. The callback might still be called if it is already queued for execution.
*
* @param string $id Identifier returned from subscribe()
*/
public function unsubscribe(string $id) : void
{
Expand All @@ -79,8 +71,6 @@ public function unsubscribe(string $id) : void

/**
* Completes the operation with a result value.
*
* @param T $result Result of the operation.
*/
public function complete(mixed $result) : void
{
Expand All @@ -98,8 +88,6 @@ public function complete(mixed $result) : void

/**
* Marks the operation as failed.
*
* @param \Throwable $throwable Throwable to indicate the error.
*/
public function error(\Throwable $throwable) : void
{
Expand All @@ -112,7 +100,7 @@ public function error(\Throwable $throwable) : void
}

/**
* @return bool True if the operation has completed.
* True if the operation has completed.
*/
public function isComplete() : bool
{
Expand Down

0 comments on commit d1a9e96

Please sign in to comment.