1
0
mirror of https://github.com/danog/amp.git synced 2025-01-22 13:21:16 +01:00

Remove Driver::run() and stop()

Added Driver::isRunning().

Driver now must be started and stopped through an instance of DriverControl.
This commit is contained in:
Aaron Piotrowski 2020-09-26 12:34:39 -05:00
parent 9a2ebe777a
commit 9f68bd4046
No known key found for this signature in database
GPG Key ID: ADD1EF783EDE9EEB
9 changed files with 390 additions and 394 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ phpunit.xml
vendor
humbug.json
.php_cs.cache
.phpunit.result.cache

View File

@ -41,7 +41,7 @@
"amphp/phpunit-util": "^1",
"amphp/php-cs-fixer-config": "dev-master",
"react/promise": "^2",
"phpunit/phpunit": "^6.0.9 | ^7",
"phpunit/phpunit": "^9",
"psalm/phar": "^3.11@dev",
"jetbrains/phpstorm-stubs": "^2019.3"
},

View File

@ -3,6 +3,7 @@
namespace Amp;
use Amp\Loop\Driver;
use Amp\Loop\DriverControl;
use Amp\Loop\DriverFactory;
use Amp\Loop\InvalidWatcherError;
use Amp\Loop\UnsupportedFeatureException;
@ -15,11 +16,10 @@ use Amp\Loop\Watcher;
*/
final class Loop
{
/**
* @var Driver
*/
private static Driver $driver;
private static DriverControl $control;
/**
* Disable construction as this is a static class.
*/
@ -37,6 +37,10 @@ final class Loop
*/
public static function set(Driver $driver): void
{
if (isset(self::$driver) && self::$driver->isRunning()) {
throw new \Error("Can't swap the event loop while it is running");
}
try {
self::$driver = new class extends Driver {
protected function activate(array $watchers): void
@ -63,6 +67,7 @@ final class Loop
\gc_collect_cycles();
} finally {
self::$driver = $driver;
self::$control = $driver->createControl();
}
}
@ -83,7 +88,7 @@ final class Loop
self::$driver->defer($callback);
}
self::$driver->run();
self::$control->run();
}
/**
@ -96,7 +101,15 @@ final class Loop
*/
public static function stop(): void
{
self::$driver->stop();
self::$control->stop();
}
/**
* @return bool True if the event loop is running, false if it is stopped.
*/
public static function isRunning(): bool
{
return self::$driver->isRunning();
}
/**

View File

@ -39,61 +39,31 @@ abstract class Driver
/** @var callable(\Throwable):void|null */
private $errorHandler;
private bool $running = false;
/** @var mixed[] */
private array $registry = [];
/**
* Run the event loop.
*
* One iteration of the loop is called one "tick". A tick covers the following steps:
*
* 1. Activate watchers created / enabled in the last tick / before `run()`.
* 2. Execute all enabled defer watchers.
* 3. Execute all due timer, pending signal and actionable stream callbacks, each only once per tick.
*
* The loop MUST continue to run until it is either stopped explicitly, no referenced watchers exist anymore, or an
* exception is thrown that cannot be handled. Exceptions that cannot be handled are exceptions thrown from an
* error handler or exceptions that would be passed to an error handler but none exists to handle them.
*
* @return void
*/
public function run(): void
{
$this->running = true;
try {
while ($this->running) {
if ($this->isEmpty()) {
return;
}
$this->tick();
}
} finally {
$this->stop();
}
}
private int $runCount = 0;
/**
* Create a control that can be used to start and stop a specific iteration of the driver loop.
*
* This method is intended for {@see \Amp\Promise\wait()} only and NOT exposed as method in {@see \Amp\Loop}.
*
* @return DriverControl
*
* @see Driver::run()
*/
public function createControl(): DriverControl
{
return new class(function () use (&$running): void {
$this->runCount++;
$running = true;
while ($running) {
if ($this->isEmpty()) {
return;
}
try {
while ($running) {
if ($this->isEmpty()) {
return;
}
$this->tick();
$this->tick();
}
} finally {
$this->runCount--;
}
}, static function () use (&$running): void {
$running = false;
@ -120,16 +90,11 @@ abstract class Driver
}
/**
* Stop the event loop.
*
* When an event loop is stopped, it continues with its current tick and exits the loop afterwards. Multiple calls
* to stop MUST be ignored and MUST NOT raise an exception.
*
* @return void
* @return bool True if the event loop is running, false if it is stopped.
*/
public function stop(): void
public function isRunning(): bool
{
$this->running = false;
return $this->runCount > 0;
}
/**
@ -605,7 +570,6 @@ abstract class Driver
* "on_writable" => ["enabled" => int, "disabled" => int],
* "on_signal" => ["enabled" => int, "disabled" => int],
* "enabled_watchers" => ["referenced" => int, "unreferenced" => int],
* "running" => bool
* ];
*
* Implementations MAY optionally add more information in the array but at minimum the above `key => value` format
@ -673,7 +637,6 @@ abstract class Driver
"on_readable" => $onReadable,
"on_writable" => $onWritable,
"on_signal" => $onSignal,
"running" => (bool) $this->running,
];
}
@ -780,6 +743,6 @@ abstract class Driver
}
/** @psalm-suppress RedundantCondition */
$this->dispatch(empty($this->nextTickQueue) && empty($this->enableQueue) && $this->running && !$this->isEmpty());
$this->dispatch(empty($this->nextTickQueue) && empty($this->enableQueue) && !$this->isEmpty());
}
}

View File

@ -5,14 +5,27 @@ namespace Amp\Loop;
interface DriverControl extends \FiberScheduler
{
/**
* Run the driver event loop.
* Run the event loop.
*
* One iteration of the loop is called one "tick". A tick covers the following steps:
*
* 1. Activate watchers created / enabled in the last tick / before `run()`.
* 2. Execute all enabled defer watchers.
* 3. Execute all due timer, pending signal and actionable stream callbacks, each only once per tick.
*
* The loop MUST continue to run until it is either stopped explicitly, no referenced watchers exist anymore, or an
* exception is thrown that cannot be handled. Exceptions that cannot be handled are exceptions thrown from an
* error handler or exceptions that would be passed to an error handler but none exists to handle them.
*
* @return void
*/
public function run(): void;
/**
* Stop the driver event loop.
* Stop the event loop.
*
* When an event loop is stopped, it continues with its current tick and exits the loop afterwards. Multiple calls
* to stop MUST be ignored and MUST NOT raise an exception.
*
* @return void
*/

View File

@ -22,6 +22,13 @@ namespace Amp
*/
function await(Promise|ReactPromise|array $promise): mixed
{
static $loop, $control;
if ($loop !== Loop::get()) {
$loop = Loop::get();
$control = $loop->createControl();
}
if (!$promise instanceof Promise) {
if ($promise instanceof ReactPromise) {
$promise = Promise\adapt($promise);
@ -30,7 +37,7 @@ namespace Amp
}
}
return \Fiber::await($promise, Loop::get());
return \Fiber::await($promise, $control);
}
/**

View File

@ -8,16 +8,15 @@ use PHPUnit\Framework\TestCase;
class DriverStateTest extends TestCase
{
/** @var Driver */
private $loop;
private Driver $loop;
protected function setUp()
protected function setUp(): void
{
$this->loop = $this->getMockForAbstractClass(Driver::class);
}
/** @test */
public function defaultsToNull()
public function defaultsToNull(): void
{
$this->assertNull($this->loop->getState("foobar"));
}
@ -26,7 +25,7 @@ class DriverStateTest extends TestCase
* @test
* @dataProvider provideValues
*/
public function getsPreviouslySetValue($value)
public function getsPreviouslySetValue($value): void
{
$this->loop->setState("foobar", $value);
$this->assertSame($value, $this->loop->getState("foobar"));
@ -36,13 +35,13 @@ class DriverStateTest extends TestCase
* @test
* @dataProvider provideValues
*/
public function getsPreviouslySetValueViaAccessor($value)
public function getsPreviouslySetValueViaAccessor($value): void
{
Loop::setState("foobar", $value);
$this->assertSame($value, Loop::getState("foobar"));
}
public function provideValues()
public function provideValues(): array
{
return [
["string"],

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ class TracingDriverTest extends DriverTest
{
public function getFactory(): callable
{
return static function () {
return static function (): TracingDriver {
return new TracingDriver(new NativeDriver);
};
}
@ -18,7 +18,7 @@ class TracingDriverTest extends DriverTest
* @dataProvider provideRegistrationArgs
* @group memoryleak
*/
public function testNoMemoryLeak($type, $args)
public function testNoMemoryLeak(string $type, array $args): void
{
// Skip, because the driver intentionally leaks
$this->assertTrue(true);