Skip to content

Commit

Permalink
Fix CompositeCancellation when two are cancelled
Browse files Browse the repository at this point in the history
Also fixed releasing the reference to contained Cancellations as soon as one is cancelled.
  • Loading branch information
trowski committed May 16, 2022
1 parent d8d0b8f commit 557f98d
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 12 deletions.
39 changes: 28 additions & 11 deletions src/CompositeCancellation.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,34 @@ public function __construct(Cancellation ...$cancellations)
{
$thatException = &$this->exception;
$thatCallbacks = &$this->callbacks;
$thatCancellations = &$this->cancellations;
$onCancel = static function (CancelledException $exception) use (
&$thatException,
&$thatCallbacks,
&$thatCancellations,
): void {
if ($thatException) {
return;
}

foreach ($cancellations as $cancellation) {
$id = $cancellation->subscribe(static function (CancelledException $exception) use (
&$thatException,
&$thatCallbacks
): void {
$thatException = $exception;
$thatException = $exception;

foreach ($thatCancellations as [$cancellation, $id]) {
/** @var Cancellation $cancellation */
$cancellation->unsubscribe($id);
}

foreach ($thatCallbacks as $callback) {
EventLoop::queue($callback, $exception);
}
$thatCancellations = [];

$thatCallbacks = [];
});
foreach ($thatCallbacks as $callback) {
EventLoop::queue($callback, $exception);
}

$thatCallbacks = [];
};

foreach ($cancellations as $cancellation) {
$id = $cancellation->subscribe($onCancel);
$this->cancellations[] = [$cancellation, $id];
}
}
Expand All @@ -48,6 +61,10 @@ public function __destruct()
/** @var Cancellation $cancellation */
$cancellation->unsubscribe($id);
}

// The reference created in the constructor causes this property to persist beyond the life of this object,
// so explicitly removing references will speed up garbage collection.
$this->cancellations = [];
}

public function subscribe(\Closure $callback): string
Expand Down
29 changes: 28 additions & 1 deletion test/Cancellation/CompositeCancellationTest.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<?php

namespace Cancellation;
namespace Amp\Cancellation;

use Amp\CancelledException;
use Amp\CompositeCancellation;
use Amp\DeferredCancellation;
use Amp\PHPUnit\AsyncTestCase;
use Amp\PHPUnit\TestException;
use function Amp\delay;

class CompositeCancellationTest extends AsyncTestCase
Expand Down Expand Up @@ -38,4 +40,29 @@ public function testBenchmark(): void
}
}
}

public function testCombinedWithDoubleCancellation(): void
{
$deferredCancellation1 = new DeferredCancellation();
$deferredCancellation2 = new DeferredCancellation();

$compositeCancellation = new CompositeCancellation(
$deferredCancellation1->getCancellation(),
$deferredCancellation2->getCancellation(),
);

$compositeCancellation->subscribe(
$this->createCallback(1, expectArgs: [self::isInstanceOf(CancelledException::class)])
);

$deferredCancellation1->cancel($exception = new TestException());
$deferredCancellation2->cancel(new TestException());

delay(0.1); // Ensure cancellation callbacks are invoked.

// Add another cancellation subscriber to ensure exception did not change.
$compositeCancellation->subscribe(function (CancelledException $cancelled) use ($exception) {
self::assertSame($exception, $cancelled->getPrevious());
});
}
}

0 comments on commit 557f98d

Please sign in to comment.