1
0
mirror of https://github.com/danog/amp.git synced 2024-12-02 09:27:46 +01:00

Rename weaken to weakClosure and limit to closures

This commit is contained in:
Aaron Piotrowski 2021-12-02 17:08:00 -06:00
parent d20f07acee
commit 1a2be8f2b2
No known key found for this signature in database
GPG Key ID: ADD1EF783EDE9EEB
2 changed files with 23 additions and 47 deletions

View File

@ -77,7 +77,7 @@ function delay(float $timeout, bool $reference = true, ?Cancellation $cancellati
/**
* Wait for signal(s) in a non-blocking way.
*
* @param int|array $signals Signal number or array of signal numbers.
* @param int|int[] $signals Signal number or array of signal numbers.
* @param bool $reference If false, unreference the underlying watcher.
* @param Cancellation|null $cancellation Cancel waiting if cancellation is requested.
*
@ -116,79 +116,55 @@ function trapSignal(int|array $signals, bool $reference = true, ?Cancellation $c
}
/**
* Returns a callable that maintains a weak reference to any $this object held by the callable.
* This allows a class to hold a self-referencing callback without creating a circular reference that would
* Returns a Closure that maintains a weak reference to any $this object held by the Closure (a weak-Closure).
* This allows a class to hold a self-referencing Closure without creating a circular reference that would
* prevent or delay automatic garbage collection.
* Invoking the returned callback after the object is destroyed will throw an instance of Error.
* Invoking the returned Closure after the object is destroyed will throw an instance of Error.
*
* @param callable $callable
* @param \Closure $closure
*
* @return callable
* @return \Closure
*/
function weaken(callable $callable): callable
function weakClosure(\Closure $closure): \Closure
{
if (!$callable instanceof \Closure) {
if (\is_string($callable)) {
return $callable;
}
if (\is_object($callable)) {
$callable = [$callable, '__invoke'];
}
[$that, $method] = $callable;
if (!\is_object($that)) {
return $callable;
}
$reference = \WeakReference::create($that);
return static function (mixed ...$args) use ($reference, $method): mixed {
$that = $reference->get();
if (!$that) {
throw new \Error('Weakened callback invoked after referenced object destroyed');
}
return $that->{$method}(...$args);
};
}
$reflection = new \ReflectionFunction($callable);
$reflection = new \ReflectionFunction($closure);
$that = $reflection->getClosureThis();
if (!$that) {
return $callable;
return $closure;
}
$method = $reflection->getShortName();
if ($method !== '{closure}') {
// Closure from first-class callable or \Closure::fromCallable(), declare an anonymous closure to rebind.
/** @psalm-suppress InvalidScope Closure is bound before being invoked. */
$callable = fn (mixed ...$args) => $this->{$method}(...$args);
$closure = fn (mixed ...$args) => $this->{$method}(...$args);
} else {
// Rebind to remove reference to $that
$callable = $callable->bindTo(new \stdClass());
$closure = $closure->bindTo(new \stdClass());
}
// For internal classes use \Closure::bindTo() without scope.
$useBindTo = !(new \ReflectionClass($that))->isUserDefined();
$reference = \WeakReference::create($that);
return static function (mixed ...$args) use ($reference, $callable, $useBindTo): mixed {
return static function (mixed ...$args) use ($reference, $closure, $useBindTo): mixed {
$that = $reference->get();
if (!$that) {
throw new \Error('Weakened callback invoked after referenced object destroyed');
throw new \Error('Weakened closure invoked after referenced object destroyed');
}
if ($useBindTo) {
$callable = $callable->bindTo($that);
$closure = $closure->bindTo($that);
if (!$callable) {
if (!$closure) {
throw new \RuntimeException('Unable to rebind function to object of type ' . \get_class($that));
}
return $callable(...$args);
return $closure(...$args);
}
return $callable->call($that, ...$args);
return $closure->call($that, ...$args);
};
}

View File

@ -5,7 +5,7 @@ namespace Amp;
use Amp\PHPUnit\AsyncTestCase;
use Revolt\EventLoop;
class WeakenTest extends AsyncTestCase
class WeakClosureTest extends AsyncTestCase
{
public function provideObjectFactories(): iterable
{
@ -17,7 +17,7 @@ class WeakenTest extends AsyncTestCase
{
$this->callbackId = $id = EventLoop::repeat(
0.001,
weaken(function (string $callbackId) use (&$count): void {
weakClosure(function (string $callbackId) use (&$count): void {
AsyncTestCase::assertNotNull($this);
AsyncTestCase::assertStringContainsString('anonymous', \get_class($this));
AsyncTestCase::assertSame($callbackId, $this->callbackId);
@ -40,7 +40,7 @@ class WeakenTest extends AsyncTestCase
public function __construct(int &$count, &$id)
{
$callbackIdRef = &$this->callbackId;
$this->callbackId = $id = EventLoop::repeat(0.001, weaken(static function (string $callbackId) use (
$this->callbackId = $id = EventLoop::repeat(0.001, weakClosure(static function (string $callbackId) use (
&$count,
&$callbackIdRef
): void {
@ -66,7 +66,7 @@ class WeakenTest extends AsyncTestCase
$this->count = &$count;
$this->callbackId = $id = EventLoop::repeat(
0.001,
weaken(\Closure::fromCallable([$this, 'callback']))
weakClosure(\Closure::fromCallable([$this, 'callback']))
);
}
@ -93,7 +93,7 @@ class WeakenTest extends AsyncTestCase
public function __construct(int &$count, &$id)
{
$this->count = &$count;
$this->callbackId = $id = EventLoop::repeat(0.001, weaken($this));
$this->callbackId = $id = EventLoop::repeat(0.001, weakClosure(\Closure::fromCallable($this)));
}
public function __invoke(string $callbackId): void