mirror of
https://github.com/danog/loop.git
synced 2024-11-30 04:19:04 +01:00
Finish docs
This commit is contained in:
parent
21eeab8680
commit
85af229ec6
434
README.md
434
README.md
@ -12,244 +12,202 @@ It's a more flexible and powerful alternative to [AMPHP](https://amphp.org)'s [r
|
||||
composer require danog/loop
|
||||
```
|
||||
|
||||
## Examples
|
||||
## API
|
||||
|
||||
* Basic
|
||||
* [GenericLoop](#GenericLoop)
|
||||
* [PeriodicLoop](#PeriodicLoop)
|
||||
* Advanced
|
||||
* [Loop](#Loop)
|
||||
* [ResumableLoop](#ResumableLoop)
|
||||
* [SignalLoop](#SignalLoop)
|
||||
* [ResumableSignalLoop](#ResumableSignalLoop)
|
||||
|
||||
All loop APIs are defined by a set of [interfaces](https://github.com/danog/loop/tree/master/lib/Interfaces): however, to use them, you would usually have to extend only one of the [abstract class implementations](https://github.com/danog/loop/tree/master/lib).
|
||||
|
||||
### Loop
|
||||
|
||||
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/LoopInterface.php)
|
||||
[Full example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/Loop.php)
|
||||
|
||||
A basic loop, capable of running in background (asynchronously) the code contained in the `loop` function.
|
||||
|
||||
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/LoopInterface.php):
|
||||
API:
|
||||
```php
|
||||
namespace danog\Loop\Interfaces;
|
||||
namespace danog\Loop;
|
||||
|
||||
interface LoopInterface
|
||||
abstract class Loop
|
||||
{
|
||||
abstract public function loop(): \Generator;
|
||||
abstract public function __toString(): string;
|
||||
|
||||
public function start(): bool;
|
||||
public function isRunning(): bool;
|
||||
|
||||
protected function startedLoop(): void;
|
||||
protected function exitedLoop(): void;
|
||||
}
|
||||
```
|
||||
|
||||
#### loop()
|
||||
|
||||
The `loop` [async coroutine](https://amphp.org/amp/coroutines/) will be run only once, every time the `start` method is called.
|
||||
|
||||
#### __toString()
|
||||
|
||||
This method should return the loop's name.
|
||||
It's useful for implementing the [log methods](#startedLoop).
|
||||
|
||||
#### start()
|
||||
|
||||
Asynchronously starts the `loop` methods once in background.
|
||||
Multiple calls to `start` will be ignored, returning `false` instead of `true`.
|
||||
|
||||
#### isRunning()
|
||||
|
||||
You can use the `isRunning` method to check if the loop is already running.
|
||||
|
||||
#### startedLoop()
|
||||
|
||||
Optionally, you can override this method to detect and log when the loop is started.
|
||||
Make sure to always call the parent `startedLoop()` method to avoid issues.
|
||||
You can use directly `$this` as loop name when logging, thanks to the custom [__toString](#__toString) method.
|
||||
|
||||
#### exitedLoop()
|
||||
|
||||
Optionally, you can override this method to detect and log when the loop is ended.
|
||||
Make sure to always call the parent `exitedLoop()` method to avoid issues.
|
||||
You can use directly `$this` as loop name when logging, thanks to the custom [__toString](#__toString) method.
|
||||
|
||||
|
||||
### ResumableLoop
|
||||
|
||||
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/ResumableLoopInterface.php)
|
||||
[Full example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/ResumableLoop.php)
|
||||
|
||||
A way more useful loop that exposes APIs to pause and resume the execution of the loop, both from outside of the loop, and in a cron-like manner from inside of the loop.
|
||||
|
||||
```php
|
||||
namespace danog\Loop;
|
||||
|
||||
abstract class ResumableLoop extends Loop
|
||||
{
|
||||
public function pause(?int $time = null): Promise;
|
||||
public function resume(): Promise;
|
||||
}
|
||||
```
|
||||
|
||||
All methods from [Loop](#loop), plus:
|
||||
|
||||
#### pause()
|
||||
|
||||
Pauses the loop for the specified number of milliseconds, or forever if `null` is provided.
|
||||
|
||||
#### resume()
|
||||
|
||||
Forcefully resume the loop from the outside.
|
||||
Returns a promise that is resolved when the loop is paused again.
|
||||
|
||||
|
||||
### SignalLoop
|
||||
|
||||
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/SignalLoopInterface.php)
|
||||
[Full example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/SignalLoop.php)
|
||||
|
||||
Yet another loop interface that exposes APIs to send signals to the loop, useful to force the termination of a loop from the outside, or to send data into it.
|
||||
|
||||
```php
|
||||
namespace danog\Loop;
|
||||
|
||||
abstract class SignalLoop extends Loop
|
||||
{
|
||||
public function signal($what): voi d;
|
||||
public function waitSignal($promise): Promise;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
All methods from [Loop](#loop), plus:
|
||||
|
||||
#### signal()
|
||||
|
||||
Sends a signal to the loop: can be anything, but typically `true` is often used as termination signal.
|
||||
Signaling can be used as a message exchange mechanism a-la IPC, and can also be used to throw exceptions inside the loop.
|
||||
|
||||
#### waitSignal()
|
||||
|
||||
Resolve the provided promise or return|throw passed signal.
|
||||
|
||||
### ResumableSignalLoop
|
||||
|
||||
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/ResumableSignalLoopInterface.php)
|
||||
[Full example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/ResumableSignalLoop.php)
|
||||
|
||||
This is what you would usually use to build a full async loop.
|
||||
All loop interfaces and loop implementations are combined into one class you can extend.
|
||||
|
||||
```php
|
||||
namespace danog\Loop;
|
||||
|
||||
abstract class ResumableSignalLoop extends SignalLoop, ResumableSignalLoop
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
The class is actually composited using traits to feature all methods from [SignalLoop](#SignalLoop) and [ResumableSignalLoop](#ResumableSignalLoop).
|
||||
|
||||
### GenericLoop
|
||||
|
||||
[Class](https://github.com/danog/loop/blob/master/lib/Generic/GenericLoop.php)
|
||||
[Full example](https://github.com/danog/loop/blob/master/examples/1.%20Basic/GenericLoop.php)
|
||||
|
||||
If you want a simpler way to use the `ResumableSignalLoop`, you can use the GenericLoop.
|
||||
The constructor accepts three parameters:
|
||||
```php
|
||||
namespace danog\Loop\Generic;
|
||||
class GenericLoop extends ResumableSignalLoop
|
||||
{
|
||||
/**
|
||||
* Start the loop.
|
||||
*
|
||||
* Returns false if the loop is already running.
|
||||
*
|
||||
* @return bool
|
||||
* Stop the loop.
|
||||
*/
|
||||
public function start(): bool;
|
||||
const STOP = -1;
|
||||
/**
|
||||
* The actual loop function.
|
||||
*
|
||||
* @return \Generator
|
||||
* Pause the loop.
|
||||
*/
|
||||
public function loop(): \Generator;
|
||||
const PAUSE = null;
|
||||
/**
|
||||
* Get name of the loop.
|
||||
* Rerun the loop.
|
||||
*/
|
||||
const CONTINUE = 0;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* If possible, the callable will be bound to the current instance of the loop.
|
||||
*
|
||||
* @param callable $callable Callable to run
|
||||
* @param string $name Loop name
|
||||
*/
|
||||
public function __construct(callable $callable, string $name)
|
||||
/**
|
||||
* Report pause, can be overriden for logging.
|
||||
*
|
||||
* @param integer $timeout Pause duration, 0 = forever
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function reportPause(int $timeout): void;
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Get loop name, provided to constructor.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string;
|
||||
/**
|
||||
* Check whether loop is running.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isRunning(): bool;
|
||||
}
|
||||
```
|
||||
|
||||
Usually one would extend the [Loop implementation](https://github.com/danog/loop/blob/master/lib/Loop.php), you have to define only the `loop` function.
|
||||
The `loop` function will be run only once, every time the `start` method is called.
|
||||
Multiple calls to `start` will be ignored, returning `false` instead of `true`.
|
||||
You can use the `isRunning` method to check if the loop is already running.
|
||||
The `__toString` method still has to be implemented in order to get the name of the loop, that will be used by the MadelineProto logging mechanism every time the loop starts/exits/fails to start.
|
||||
|
||||
```php
|
||||
use danog\Loop\Loop;
|
||||
class MyLoop extends Loop
|
||||
{
|
||||
private $callable;
|
||||
public function __construct($API, $callable)
|
||||
{
|
||||
$this->API = $API;
|
||||
$this->callable = $callable;
|
||||
}
|
||||
public function loop()
|
||||
{
|
||||
$MadelineProto = $this->API;
|
||||
$logger = &$MadelineProto->logger;
|
||||
$callable = $this->callable;
|
||||
|
||||
$result = null;
|
||||
while (true) {
|
||||
$params = yield $callable($result);
|
||||
$result = yield $MadelineProto->messages->sendMessage($params);
|
||||
}
|
||||
}
|
||||
public function __toString(): string
|
||||
{
|
||||
return "my custom loop";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The loop can be instantiated and `start()`ed, and this will automatically run the code in the loop in background.
|
||||
If, however, only your loop is started (without an event handling loop), you have to pass the promise returned by `$loop->start()` to `$MadelineProto->loop`.
|
||||
Do NOT do this if you've already started `$MadelineProto->loop()`.
|
||||
```php
|
||||
$loop = new MyLoop;
|
||||
$MadelineProto->loop($loop->start());
|
||||
```
|
||||
|
||||
### ResumableLoop
|
||||
|
||||
A way more useful loop that exposes APIs to pause and resume the execution of the loop, both from outside of the loop, and in a cron-like manner from inside of the loop.
|
||||
|
||||
[Interface](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/ResumableLoopInterface.php):
|
||||
```php
|
||||
namespace danog\Loop;
|
||||
|
||||
interface ResumableLoopInterface extends LoopInterface
|
||||
{
|
||||
/**
|
||||
* Pause the loop.
|
||||
*
|
||||
* @param int $time For how long to pause the loop, if null will pause forever (until resume is called from outside of the loop)
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function pause($time = null): Promise;
|
||||
|
||||
/**
|
||||
* Resume the loop.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resume();
|
||||
}
|
||||
```
|
||||
|
||||
Usually one would extend the [ResumableSignalLoop implementation](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Impl/ResumableSignalLoop.php).
|
||||
An example implementation can be seen in the [ResumableSignalLoop section of this page](#resumablesignalloop).
|
||||
|
||||
### SignalLoop
|
||||
|
||||
Yet another loop interface that exposes APIs to send signals to the loop, useful to force the termination of a loop from the outside, or to send data into it.
|
||||
|
||||
[Interface](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/SignalLoopInterface.php):
|
||||
```php
|
||||
namespace danog\Loop;
|
||||
|
||||
interface SignalLoopInterface extends LoopInterface
|
||||
{
|
||||
/**
|
||||
* Resolve the promise or return|throw the signal.
|
||||
*
|
||||
* @param Promise $promise The origin promise
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function waitSignal($promise): Promise;
|
||||
|
||||
/**
|
||||
* Send a signal to the the loop.
|
||||
*
|
||||
* @param Exception|any $data Signal to send
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function signal($data);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Usually one would extend the [ResumableSignalLoop implementation](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Impl/ResumableSignalLoop.php).
|
||||
An example implementation can be seen in the [ResumableSignalLoop section of this page](#resumablesignalloop).
|
||||
If you want, you can also extend only the [SignalLoop implementation](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Impl/SignalLoop.php), but usually a combination of the SignalLoop and ResumableLoop implementations is used, so read on to find out how to do that.
|
||||
|
||||
### ResumableSignalLoop
|
||||
|
||||
This is what you would usually use to build a full async loop.
|
||||
All loop interfaces and loop implementations are combined into one single abstract class you can extend.
|
||||
|
||||
```php
|
||||
use danog\ResumableSignalLoop;
|
||||
class MySuperLoop extends ResumableSignalLoop
|
||||
{
|
||||
private $timeout;
|
||||
public function __construct($API, $timeout)
|
||||
{
|
||||
$this->API = $API;
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
public function loop()
|
||||
{
|
||||
$MadelineProto = $this->API;
|
||||
$logger = &$MadelineProto->logger;
|
||||
|
||||
while (true) {
|
||||
$t = time();
|
||||
|
||||
$result = yield $this->waitSignal($this->pause($this->timeout));
|
||||
if ($result <= 0) {
|
||||
return;
|
||||
} else if ($result > 0) {
|
||||
$this->timeout = $result;
|
||||
}
|
||||
|
||||
$t = time() - $t;
|
||||
|
||||
$result = yield $MadelineProto->messages->sendMessage(
|
||||
[
|
||||
'peer' => '...',
|
||||
'message' => "Resumed after $t seconds of timeout"
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
public function __toString(): string
|
||||
{
|
||||
return "my cron signal loop";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[ResumableSignalLoop implementation](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Impl/ResumableSignalLoop.php).
|
||||
As with the [Loop](#loop).
|
||||
|
||||
The difference now is that you can use the `pause` method to pause execution of the loop for a certain period of time (in seconds, supports decimals).
|
||||
If `null` is passed, execution will be suspended forever (or until `resume` is called from outside of the loop).
|
||||
|
||||
If the promise returned by `pause` (or by any other async method) is passed to `waitSignal`, and the result is yielded, execution will be suspended for the specified amount of time|forever, or until a signal is received through the `signal` method.
|
||||
The passed signal will then be returned as result of the `waitSignal` method, and can be used to stop the loop, or simply as a message exchange mechanism.
|
||||
|
||||
### GenericLoop
|
||||
|
||||
If you want a simpler way to use the `ResumableSignalLoop`, you can use the [GenericLoop](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Generic/GenericLoop.php).
|
||||
The constructor accepts three parameters:
|
||||
```php
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \danog\API $API Instance of MadelineProto
|
||||
* @param callback $callback Callback to run
|
||||
* @param string $name Fetcher name
|
||||
*/
|
||||
public function __construct($API, $callback, $name) { // ...
|
||||
```
|
||||
|
||||
Example:
|
||||
```php
|
||||
use danog\Loop\Generic\GenericLoop;
|
||||
$loop = new GenericLoop(
|
||||
$MadelineProto,
|
||||
function () {
|
||||
yield $this->API->messages->sendMessage(['peer' => '...', 'message' => 'Hi every 2 seconds']);
|
||||
|
||||
return 2;
|
||||
},
|
||||
"My super loop"
|
||||
);
|
||||
$loop->start();
|
||||
```
|
||||
The callback will be bound to the GenericLoop instance: this means that you will be able to use `$this` as if the callback were actually the `loop` function (you can access the API property, use the pause/waitSignal methods & so on).
|
||||
The callback will be bound to the `GenericLoop` instance: this means that you will be able to use `$this` as if the callback were actually the `loop` function (you can get the loop name by casting `$this` to a string, use the pause/waitSignal methods & so on).
|
||||
The return value of the callable can be:
|
||||
* A number - the loop will be paused for the specified number of seconds
|
||||
* `GenericLoop::STOP` - The loop will stop
|
||||
@ -260,32 +218,34 @@ If the callable does not return anything, the loop will behave is if `GenericLoo
|
||||
|
||||
### PeriodicLoop
|
||||
|
||||
[Class](https://github.com/danog/loop/blob/master/lib/Generic/PeriodicLoop.php)
|
||||
[Full example](https://github.com/danog/loop/blob/master/examples/1.%20Basic/PeriodicLoop.php)
|
||||
|
||||
If you simply want to execute an action every N seconds, [PeriodicLoop](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Generic/PeriodicLoop.php) is the way to go.
|
||||
The constructor accepts four parameters:
|
||||
```php
|
||||
namespace danog\Loop\Generic;
|
||||
|
||||
class PeriodicLoop extends ResumableSignalLoop
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \danog\API $API Instance of MTProto class
|
||||
* @param callable $callback Callback to call
|
||||
* @param string $name Loop name
|
||||
* @param int|float $timeout Loop timeout
|
||||
* If possible, the callable will be bound to the current instance of the loop.
|
||||
*
|
||||
* @param callable $callback Callback to call
|
||||
* @param string $name Loop name
|
||||
* @param ?int $interval Loop interval
|
||||
*/
|
||||
public function __construct($API, callable $callback, string $name, $timeout) { // ...
|
||||
public function __construct(callable $callback, string $name, ?int $interval)
|
||||
/**
|
||||
* Get name of the loop, passed to the constructor.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
}
|
||||
```
|
||||
|
||||
Example:
|
||||
```php
|
||||
use danog\Loop\Generic\PeriodicLoop;
|
||||
$loop = new PeriodicLoop(
|
||||
$MadelineProto,
|
||||
function () use (&$loop) {
|
||||
yield $this->API->messages->sendMessage(['peer' => '...', 'message' => 'Hi every 2 seconds']);
|
||||
},
|
||||
"My super loop",
|
||||
2
|
||||
);
|
||||
$loop->start();
|
||||
```
|
||||
Unlike `GenericLoop`, the callback will **not** be bound to the GenericLoop instance.
|
||||
You can still command the loop by using the pause/waitSignal methods from the outside or by capturing the loop instance in a closure like shown above.
|
||||
`PeriodicLoop` runs a callback at a periodic interval.
|
||||
The callback will be bound to the `GenericLoop` instance: this means that you will be able to use `$this` as if the callback were actually the `loop` function (you can get the loop name by , use the pause/waitSignal methods & so on).
|
||||
The loop can be stopped from the outside or from the inside by signaling or returning `true`.
|
@ -11,7 +11,13 @@ Loop::run(function () {
|
||||
/** @var GenericLoop[] */
|
||||
$loops = [];
|
||||
for ($x = 0; $x < 10; $x++) {
|
||||
$loop = new GenericLoop("Loop number $x");
|
||||
$callable = function () {
|
||||
static $number = 0;
|
||||
echo "$this: $number".PHP_EOL;
|
||||
$number++;
|
||||
return $number < 10 ? 1000 : GenericLoop::STOP;
|
||||
};
|
||||
$loop = new GenericLoop($callable, "Loop number $x");
|
||||
$loop->start();
|
||||
yield delay(100);
|
||||
$loops []= $loop;
|
||||
@ -24,7 +30,5 @@ Loop::run(function () {
|
||||
echo "OK done, waiting 5 more seconds!".PHP_EOL;
|
||||
yield delay(5000);
|
||||
echo "Closing all loops!".PHP_EOL;
|
||||
foreach ($loops as $loop) {
|
||||
$loop->signal(true);
|
||||
}
|
||||
yield delay(10);
|
||||
});
|
||||
|
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
use Amp\Loop;
|
||||
use danog\Loop\Generic\PeriodicLoop;
|
||||
|
||||
use function Amp\delay;
|
||||
|
||||
Loop::run(function () {
|
||||
/** @var PeriodicLoop[] */
|
||||
$loops = [];
|
||||
for ($x = 0; $x < 10; $x++) {
|
||||
$callable = function () {
|
||||
static $number = 0;
|
||||
echo "$this: $number".PHP_EOL;
|
||||
$number++;
|
||||
return $number == 10;
|
||||
};
|
||||
$loop = new PeriodicLoop($callable, "Loop number $x", 1000);
|
||||
$loop->start();
|
||||
yield delay(100);
|
||||
$loops []= $loop;
|
||||
}
|
||||
yield delay(5000);
|
||||
echo "Resuming prematurely all loops!".PHP_EOL;
|
||||
foreach ($loops as $loop) {
|
||||
$loop->resume();
|
||||
}
|
||||
echo "OK done, waiting 5 more seconds!".PHP_EOL;
|
||||
yield delay(5000);
|
||||
echo "Closing all loops!".PHP_EOL;
|
||||
yield delay(10);
|
||||
});
|
@ -33,7 +33,7 @@ class MyLoop extends ResumableLoop
|
||||
{
|
||||
$number = 0;
|
||||
while (true) {
|
||||
yield $this->pause(1);
|
||||
yield $this->pause(1000);
|
||||
echo "$this: $number".PHP_EOL;
|
||||
$number++;
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class ResSigLoop extends ResumableSignalLoop
|
||||
{
|
||||
$number = 0;
|
||||
while (true) {
|
||||
if (yield $this->waitSignal($this->pause(1))) {
|
||||
if (yield $this->waitSignal($this->pause(1000))) {
|
||||
echo "Got exit signal in $this!".PHP_EOL;
|
||||
return;
|
||||
}
|
||||
|
@ -67,6 +67,8 @@ class GenericLoop extends ResumableSignalLoop
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* If possible, the callable will be bound to the current instance of the loop.
|
||||
*
|
||||
* @param callable $callable Callable to run
|
||||
* @param string $name Loop name
|
||||
*
|
||||
@ -74,6 +76,13 @@ class GenericLoop extends ResumableSignalLoop
|
||||
*/
|
||||
public function __construct(callable $callable, string $name)
|
||||
{
|
||||
if ($callable instanceof \Closure) {
|
||||
try {
|
||||
$callable = $callable->bindTo($this);
|
||||
} catch (\Throwable $e) {
|
||||
// Might cause an error for wrapped object methods
|
||||
}
|
||||
}
|
||||
$this->callable = $callable;
|
||||
$this->name = $name;
|
||||
}
|
||||
@ -112,14 +121,12 @@ class GenericLoop extends ResumableSignalLoop
|
||||
* @param integer $timeout Pause duration, 0 = forever
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
protected function reportPause(int $timeout): void
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Get loop name.
|
||||
* Get loop name, provided to constructor.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
@ -53,6 +53,8 @@ class PeriodicLoop extends ResumableSignalLoop
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* If possible, the callable will be bound to the current instance of the loop.
|
||||
*
|
||||
* @param callable $callback Callback to call
|
||||
* @param string $name Loop name
|
||||
* @param ?int $interval Loop interval
|
||||
@ -61,6 +63,13 @@ class PeriodicLoop extends ResumableSignalLoop
|
||||
*/
|
||||
public function __construct(callable $callback, string $name, ?int $interval)
|
||||
{
|
||||
if ($callback instanceof \Closure) {
|
||||
try {
|
||||
$callback = $callback->bindTo($this);
|
||||
} catch (\Throwable $e) {
|
||||
// Might cause an error for wrapped object methods
|
||||
}
|
||||
}
|
||||
$this->callback = $callback;
|
||||
$this->name = $name;
|
||||
$this->interval = $interval;
|
||||
@ -93,7 +102,7 @@ class PeriodicLoop extends ResumableSignalLoop
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get name of the loop.
|
||||
* Get name of the loop, passed to the constructor.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
@ -24,7 +24,7 @@ use Closure;
|
||||
trait ResumableLoop
|
||||
{
|
||||
use Loop {
|
||||
exitedLoop as parentExitedLoop;
|
||||
exitedLoop as private parentExitedLoop;
|
||||
}
|
||||
/**
|
||||
* Resume deferred.
|
||||
|
@ -35,11 +35,32 @@ class GenericTest extends AsyncTestCase
|
||||
{
|
||||
$runCount = 0;
|
||||
$pauseTime = GenericLoop::PAUSE;
|
||||
$callable = function () use (&$runCount, &$pauseTime) {
|
||||
$callable = function () use (&$runCount, &$pauseTime, &$zis) {
|
||||
$zis = $this;
|
||||
$runCount++;
|
||||
return $pauseTime;
|
||||
};
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig);
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig, $zis, true);
|
||||
$obj = new class() {
|
||||
public $pauseTime = GenericLoop::PAUSE;
|
||||
public $runCount = 0;
|
||||
public function run()
|
||||
{
|
||||
$this->runCount++;
|
||||
return $this->pauseTime;
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
|
||||
$obj = new class() {
|
||||
public $pauseTime = GenericLoop::PAUSE;
|
||||
public $runCount = 0;
|
||||
public function run()
|
||||
{
|
||||
$this->runCount++;
|
||||
return $this->pauseTime;
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
|
||||
}
|
||||
/**
|
||||
* Test generator loop.
|
||||
@ -54,12 +75,35 @@ class GenericTest extends AsyncTestCase
|
||||
{
|
||||
$runCount = 0;
|
||||
$pauseTime = GenericLoop::PAUSE;
|
||||
$callable = function () use (&$runCount, &$pauseTime): \Generator {
|
||||
$callable = function () use (&$runCount, &$pauseTime, &$zis): \Generator {
|
||||
$zis = $this;
|
||||
yield delay(1);
|
||||
$runCount++;
|
||||
return $pauseTime;
|
||||
};
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig);
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig, $zis, true);
|
||||
$obj = new class() {
|
||||
public $pauseTime = GenericLoop::PAUSE;
|
||||
public $runCount = 0;
|
||||
public function run(): \Generator
|
||||
{
|
||||
yield delay(1);
|
||||
$this->runCount++;
|
||||
return $this->pauseTime;
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
|
||||
$obj = new class() {
|
||||
public $pauseTime = GenericLoop::PAUSE;
|
||||
public $runCount = 0;
|
||||
public function run(): \Generator
|
||||
{
|
||||
yield delay(1);
|
||||
$this->runCount++;
|
||||
return $this->pauseTime;
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
|
||||
}
|
||||
/**
|
||||
* Test promise loop.
|
||||
@ -74,11 +118,32 @@ class GenericTest extends AsyncTestCase
|
||||
{
|
||||
$runCount = 0;
|
||||
$pauseTime = GenericLoop::PAUSE;
|
||||
$callable = function () use (&$runCount, &$pauseTime): Promise {
|
||||
$callable = function () use (&$runCount, &$pauseTime, &$zis): Promise {
|
||||
$zis = $this;
|
||||
$runCount++;
|
||||
return new Success($pauseTime);
|
||||
};
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig);
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $pauseTime, $stopSig, $zis, true);
|
||||
$obj = new class() {
|
||||
public $pauseTime = GenericLoop::PAUSE;
|
||||
public $runCount = 0;
|
||||
public function run(): Promise
|
||||
{
|
||||
$this->runCount++;
|
||||
return new Success($this->pauseTime);
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
|
||||
$obj = new class() {
|
||||
public $pauseTime = GenericLoop::PAUSE;
|
||||
public $runCount = 0;
|
||||
public function run(): Promise
|
||||
{
|
||||
$this->runCount++;
|
||||
return new Success($this->pauseTime);
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->pauseTime, $stopSig, $zisNew, false);
|
||||
}
|
||||
/**
|
||||
* Fixture assertions for started loop.
|
||||
@ -96,14 +161,16 @@ class GenericTest extends AsyncTestCase
|
||||
/**
|
||||
* Run fixture assertions.
|
||||
*
|
||||
* @param \Closure $closure Closure
|
||||
* @param callable $closure Closure
|
||||
* @param integer $runCount Run count
|
||||
* @param ?integer $pauseTime Pause time
|
||||
* @param bool $stopSig Whether to stop with signal
|
||||
* @param bool $zis Reference to closure's this
|
||||
* @param bool $checkZis Whether to check zis
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function fixtureAssertions(\Closure $closure, int &$runCount, ?int &$pauseTime, bool $stopSig = false): \Generator
|
||||
private function fixtureAssertions(callable $closure, int &$runCount, ?int &$pauseTime, bool $stopSig, &$zis, bool $checkZis): \Generator
|
||||
{
|
||||
$loop = new class($closure, Fixtures::LOOP_NAME) extends GenericLoop implements LoggingPauseInterface {
|
||||
use LoggingPause;
|
||||
@ -119,6 +186,11 @@ class GenericTest extends AsyncTestCase
|
||||
|
||||
$loop->start();
|
||||
yield delay(2);
|
||||
if ($checkZis) {
|
||||
$this->assertEquals($loop, $zis);
|
||||
} else {
|
||||
$this->assertNull($zis);
|
||||
}
|
||||
$this->fixtureStarted($loop);
|
||||
|
||||
$this->assertEquals(1, $runCount);
|
||||
|
@ -35,11 +35,32 @@ class PeriodicTest extends AsyncTestCase
|
||||
{
|
||||
$runCount = 0;
|
||||
$retValue = false;
|
||||
$callable = function () use (&$runCount, &$retValue) {
|
||||
$callable = function () use (&$runCount, &$retValue, &$zis) {
|
||||
$zis = $this;
|
||||
$runCount++;
|
||||
return $retValue;
|
||||
};
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig);
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig, $zis, true);
|
||||
$obj = new class() {
|
||||
public $retValue = false;
|
||||
public $runCount = 0;
|
||||
public function run()
|
||||
{
|
||||
$this->runCount++;
|
||||
return $this->retValue;
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
|
||||
$obj = new class() {
|
||||
public $retValue = false;
|
||||
public $runCount = 0;
|
||||
public function run()
|
||||
{
|
||||
$this->runCount++;
|
||||
return $this->retValue;
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
|
||||
}
|
||||
/**
|
||||
* Test generator loop.
|
||||
@ -54,12 +75,35 @@ class PeriodicTest extends AsyncTestCase
|
||||
{
|
||||
$runCount = 0;
|
||||
$retValue = false;
|
||||
$callable = function () use (&$runCount, &$retValue): \Generator {
|
||||
$callable = function () use (&$runCount, &$retValue, &$zis): \Generator {
|
||||
$zis = $this;
|
||||
yield delay(1);
|
||||
$runCount++;
|
||||
return $retValue;
|
||||
};
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig);
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig, $zis, true);
|
||||
$obj = new class() {
|
||||
public $retValue = false;
|
||||
public $runCount = 0;
|
||||
public function run(): \Generator
|
||||
{
|
||||
yield delay(1);
|
||||
$this->runCount++;
|
||||
return $this->retValue;
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
|
||||
$obj = new class() {
|
||||
public $retValue = false;
|
||||
public $runCount = 0;
|
||||
public function run(): \Generator
|
||||
{
|
||||
yield delay(1);
|
||||
$this->runCount++;
|
||||
return $this->retValue;
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
|
||||
}
|
||||
/**
|
||||
* Test promise loop.
|
||||
@ -74,11 +118,32 @@ class PeriodicTest extends AsyncTestCase
|
||||
{
|
||||
$runCount = 0;
|
||||
$retValue = false;
|
||||
$callable = function () use (&$runCount, &$retValue): Promise {
|
||||
$callable = function () use (&$runCount, &$retValue, &$zis): Promise {
|
||||
$zis = $this;
|
||||
$runCount++;
|
||||
return new Success($retValue);
|
||||
};
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig);
|
||||
yield from $this->fixtureAssertions($callable, $runCount, $retValue, $stopSig, $zis, true);
|
||||
$obj = new class() {
|
||||
public $retValue = false;
|
||||
public $runCount = 0;
|
||||
public function run(): Promise
|
||||
{
|
||||
$this->runCount++;
|
||||
return new Success($this->retValue);
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions([$obj, 'run'], $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
|
||||
$obj = new class() {
|
||||
public $retValue = false;
|
||||
public $runCount = 0;
|
||||
public function run(): Promise
|
||||
{
|
||||
$this->runCount++;
|
||||
return new Success($this->retValue);
|
||||
}
|
||||
};
|
||||
yield from $this->fixtureAssertions(\Closure::fromCallable([$obj, 'run']), $obj->runCount, $obj->retValue, $stopSig, $zisNew, false);
|
||||
}
|
||||
/**
|
||||
* Fixture assertions for started loop.
|
||||
@ -96,14 +161,16 @@ class PeriodicTest extends AsyncTestCase
|
||||
/**
|
||||
* Run fixture assertions.
|
||||
*
|
||||
* @param \Closure $closure Closure
|
||||
* @param integer $runCount Run count
|
||||
* @param callable $closure Closure
|
||||
* @param integer $runCount Run count
|
||||
* @param bool $retValue Pause time
|
||||
* @param bool $stopSig Whether to stop with signal
|
||||
* @param bool $stopSig Whether to stop with signal
|
||||
* @param bool $zis Reference to closure's this
|
||||
* @param bool $checkZis Whether to check zis
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function fixtureAssertions(\Closure $closure, int &$runCount, bool &$retValue, bool $stopSig = false): \Generator
|
||||
private function fixtureAssertions(callable $closure, int &$runCount, bool &$retValue, bool $stopSig, &$zis, bool $checkZis): \Generator
|
||||
{
|
||||
$loop = new class($closure, Fixtures::LOOP_NAME, 100) extends PeriodicLoop implements LoggingInterface {
|
||||
use Logging;
|
||||
@ -118,6 +185,11 @@ class PeriodicTest extends AsyncTestCase
|
||||
|
||||
$loop->start();
|
||||
yield delay(2);
|
||||
if ($checkZis) {
|
||||
$this->assertEquals($loop, $zis);
|
||||
} else {
|
||||
$this->assertNull($zis);
|
||||
}
|
||||
$this->fixtureStarted($loop);
|
||||
|
||||
$this->assertEquals(1, $runCount);
|
||||
|
@ -54,6 +54,7 @@ trait LoggingPause
|
||||
*/
|
||||
protected function reportPause(int $timeout): void
|
||||
{
|
||||
parent::reportPause($timeout);
|
||||
$this->pauseCount++;
|
||||
$this->lastPause= $timeout;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user