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
|
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).
|
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
|
### 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.
|
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
|
```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.
|
* Stop the loop.
|
||||||
*
|
|
||||||
* Returns false if the loop is already running.
|
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function start(): bool;
|
const STOP = -1;
|
||||||
/**
|
/**
|
||||||
* The actual loop function.
|
* Pause the loop.
|
||||||
*
|
|
||||||
* @return \Generator
|
|
||||||
*/
|
*/
|
||||||
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
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function __toString(): 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 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 `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 return value of the callable can be:
|
The return value of the callable can be:
|
||||||
* A number - the loop will be paused for the specified number of seconds
|
* A number - the loop will be paused for the specified number of seconds
|
||||||
* `GenericLoop::STOP` - The loop will stop
|
* `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
|
### 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.
|
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
|
```php
|
||||||
|
namespace danog\Loop\Generic;
|
||||||
|
|
||||||
|
class PeriodicLoop extends ResumableSignalLoop
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param \danog\API $API Instance of MTProto class
|
* 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 callable $callback Callback to call
|
||||||
* @param int|float $timeout Loop timeout
|
* @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:
|
`PeriodicLoop` runs a callback at a periodic interval.
|
||||||
```php
|
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).
|
||||||
use danog\Loop\Generic\PeriodicLoop;
|
The loop can be stopped from the outside or from the inside by signaling or returning `true`.
|
||||||
$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.
|
|
@ -11,7 +11,13 @@ Loop::run(function () {
|
|||||||
/** @var GenericLoop[] */
|
/** @var GenericLoop[] */
|
||||||
$loops = [];
|
$loops = [];
|
||||||
for ($x = 0; $x < 10; $x++) {
|
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();
|
$loop->start();
|
||||||
yield delay(100);
|
yield delay(100);
|
||||||
$loops []= $loop;
|
$loops []= $loop;
|
||||||
@ -24,7 +30,5 @@ Loop::run(function () {
|
|||||||
echo "OK done, waiting 5 more seconds!".PHP_EOL;
|
echo "OK done, waiting 5 more seconds!".PHP_EOL;
|
||||||
yield delay(5000);
|
yield delay(5000);
|
||||||
echo "Closing all loops!".PHP_EOL;
|
echo "Closing all loops!".PHP_EOL;
|
||||||
foreach ($loops as $loop) {
|
yield delay(10);
|
||||||
$loop->signal(true);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
@ -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;
|
$number = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
yield $this->pause(1);
|
yield $this->pause(1000);
|
||||||
echo "$this: $number".PHP_EOL;
|
echo "$this: $number".PHP_EOL;
|
||||||
$number++;
|
$number++;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ class ResSigLoop extends ResumableSignalLoop
|
|||||||
{
|
{
|
||||||
$number = 0;
|
$number = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (yield $this->waitSignal($this->pause(1))) {
|
if (yield $this->waitSignal($this->pause(1000))) {
|
||||||
echo "Got exit signal in $this!".PHP_EOL;
|
echo "Got exit signal in $this!".PHP_EOL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -67,6 +67,8 @@ class GenericLoop extends ResumableSignalLoop
|
|||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
|
* If possible, the callable will be bound to the current instance of the loop.
|
||||||
|
*
|
||||||
* @param callable $callable Callable to run
|
* @param callable $callable Callable to run
|
||||||
* @param string $name Loop name
|
* @param string $name Loop name
|
||||||
*
|
*
|
||||||
@ -74,6 +76,13 @@ class GenericLoop extends ResumableSignalLoop
|
|||||||
*/
|
*/
|
||||||
public function __construct(callable $callable, string $name)
|
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->callable = $callable;
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
}
|
}
|
||||||
@ -112,14 +121,12 @@ class GenericLoop extends ResumableSignalLoop
|
|||||||
* @param integer $timeout Pause duration, 0 = forever
|
* @param integer $timeout Pause duration, 0 = forever
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*
|
|
||||||
* @codeCoverageIgnore
|
|
||||||
*/
|
*/
|
||||||
protected function reportPause(int $timeout): void
|
protected function reportPause(int $timeout): void
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get loop name.
|
* Get loop name, provided to constructor.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -53,6 +53,8 @@ class PeriodicLoop extends ResumableSignalLoop
|
|||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
|
* If possible, the callable will be bound to the current instance of the loop.
|
||||||
|
*
|
||||||
* @param callable $callback Callback to call
|
* @param callable $callback Callback to call
|
||||||
* @param string $name Loop name
|
* @param string $name Loop name
|
||||||
* @param ?int $interval Loop interval
|
* @param ?int $interval Loop interval
|
||||||
@ -61,6 +63,13 @@ class PeriodicLoop extends ResumableSignalLoop
|
|||||||
*/
|
*/
|
||||||
public function __construct(callable $callback, string $name, ?int $interval)
|
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->callback = $callback;
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->interval = $interval;
|
$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
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -24,7 +24,7 @@ use Closure;
|
|||||||
trait ResumableLoop
|
trait ResumableLoop
|
||||||
{
|
{
|
||||||
use Loop {
|
use Loop {
|
||||||
exitedLoop as parentExitedLoop;
|
exitedLoop as private parentExitedLoop;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Resume deferred.
|
* Resume deferred.
|
||||||
|
@ -35,11 +35,32 @@ class GenericTest extends AsyncTestCase
|
|||||||
{
|
{
|
||||||
$runCount = 0;
|
$runCount = 0;
|
||||||
$pauseTime = GenericLoop::PAUSE;
|
$pauseTime = GenericLoop::PAUSE;
|
||||||
$callable = function () use (&$runCount, &$pauseTime) {
|
$callable = function () use (&$runCount, &$pauseTime, &$zis) {
|
||||||
|
$zis = $this;
|
||||||
$runCount++;
|
$runCount++;
|
||||||
return $pauseTime;
|
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.
|
* Test generator loop.
|
||||||
@ -54,12 +75,35 @@ class GenericTest extends AsyncTestCase
|
|||||||
{
|
{
|
||||||
$runCount = 0;
|
$runCount = 0;
|
||||||
$pauseTime = GenericLoop::PAUSE;
|
$pauseTime = GenericLoop::PAUSE;
|
||||||
$callable = function () use (&$runCount, &$pauseTime): \Generator {
|
$callable = function () use (&$runCount, &$pauseTime, &$zis): \Generator {
|
||||||
|
$zis = $this;
|
||||||
yield delay(1);
|
yield delay(1);
|
||||||
$runCount++;
|
$runCount++;
|
||||||
return $pauseTime;
|
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.
|
* Test promise loop.
|
||||||
@ -74,11 +118,32 @@ class GenericTest extends AsyncTestCase
|
|||||||
{
|
{
|
||||||
$runCount = 0;
|
$runCount = 0;
|
||||||
$pauseTime = GenericLoop::PAUSE;
|
$pauseTime = GenericLoop::PAUSE;
|
||||||
$callable = function () use (&$runCount, &$pauseTime): Promise {
|
$callable = function () use (&$runCount, &$pauseTime, &$zis): Promise {
|
||||||
|
$zis = $this;
|
||||||
$runCount++;
|
$runCount++;
|
||||||
return new Success($pauseTime);
|
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.
|
* Fixture assertions for started loop.
|
||||||
@ -96,14 +161,16 @@ class GenericTest extends AsyncTestCase
|
|||||||
/**
|
/**
|
||||||
* Run fixture assertions.
|
* Run fixture assertions.
|
||||||
*
|
*
|
||||||
* @param \Closure $closure Closure
|
* @param callable $closure Closure
|
||||||
* @param integer $runCount Run count
|
* @param integer $runCount Run count
|
||||||
* @param ?integer $pauseTime Pause time
|
* @param ?integer $pauseTime 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
|
* @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 {
|
$loop = new class($closure, Fixtures::LOOP_NAME) extends GenericLoop implements LoggingPauseInterface {
|
||||||
use LoggingPause;
|
use LoggingPause;
|
||||||
@ -119,6 +186,11 @@ class GenericTest extends AsyncTestCase
|
|||||||
|
|
||||||
$loop->start();
|
$loop->start();
|
||||||
yield delay(2);
|
yield delay(2);
|
||||||
|
if ($checkZis) {
|
||||||
|
$this->assertEquals($loop, $zis);
|
||||||
|
} else {
|
||||||
|
$this->assertNull($zis);
|
||||||
|
}
|
||||||
$this->fixtureStarted($loop);
|
$this->fixtureStarted($loop);
|
||||||
|
|
||||||
$this->assertEquals(1, $runCount);
|
$this->assertEquals(1, $runCount);
|
||||||
|
@ -35,11 +35,32 @@ class PeriodicTest extends AsyncTestCase
|
|||||||
{
|
{
|
||||||
$runCount = 0;
|
$runCount = 0;
|
||||||
$retValue = false;
|
$retValue = false;
|
||||||
$callable = function () use (&$runCount, &$retValue) {
|
$callable = function () use (&$runCount, &$retValue, &$zis) {
|
||||||
|
$zis = $this;
|
||||||
$runCount++;
|
$runCount++;
|
||||||
return $retValue;
|
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.
|
* Test generator loop.
|
||||||
@ -54,12 +75,35 @@ class PeriodicTest extends AsyncTestCase
|
|||||||
{
|
{
|
||||||
$runCount = 0;
|
$runCount = 0;
|
||||||
$retValue = false;
|
$retValue = false;
|
||||||
$callable = function () use (&$runCount, &$retValue): \Generator {
|
$callable = function () use (&$runCount, &$retValue, &$zis): \Generator {
|
||||||
|
$zis = $this;
|
||||||
yield delay(1);
|
yield delay(1);
|
||||||
$runCount++;
|
$runCount++;
|
||||||
return $retValue;
|
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.
|
* Test promise loop.
|
||||||
@ -74,11 +118,32 @@ class PeriodicTest extends AsyncTestCase
|
|||||||
{
|
{
|
||||||
$runCount = 0;
|
$runCount = 0;
|
||||||
$retValue = false;
|
$retValue = false;
|
||||||
$callable = function () use (&$runCount, &$retValue): Promise {
|
$callable = function () use (&$runCount, &$retValue, &$zis): Promise {
|
||||||
|
$zis = $this;
|
||||||
$runCount++;
|
$runCount++;
|
||||||
return new Success($retValue);
|
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.
|
* Fixture assertions for started loop.
|
||||||
@ -96,14 +161,16 @@ class PeriodicTest extends AsyncTestCase
|
|||||||
/**
|
/**
|
||||||
* Run fixture assertions.
|
* Run fixture assertions.
|
||||||
*
|
*
|
||||||
* @param \Closure $closure Closure
|
* @param callable $closure Closure
|
||||||
* @param integer $runCount Run count
|
* @param integer $runCount Run count
|
||||||
* @param bool $retValue Pause time
|
* @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
|
* @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 {
|
$loop = new class($closure, Fixtures::LOOP_NAME, 100) extends PeriodicLoop implements LoggingInterface {
|
||||||
use Logging;
|
use Logging;
|
||||||
@ -118,6 +185,11 @@ class PeriodicTest extends AsyncTestCase
|
|||||||
|
|
||||||
$loop->start();
|
$loop->start();
|
||||||
yield delay(2);
|
yield delay(2);
|
||||||
|
if ($checkZis) {
|
||||||
|
$this->assertEquals($loop, $zis);
|
||||||
|
} else {
|
||||||
|
$this->assertNull($zis);
|
||||||
|
}
|
||||||
$this->fixtureStarted($loop);
|
$this->fixtureStarted($loop);
|
||||||
|
|
||||||
$this->assertEquals(1, $runCount);
|
$this->assertEquals(1, $runCount);
|
||||||
|
@ -54,6 +54,7 @@ trait LoggingPause
|
|||||||
*/
|
*/
|
||||||
protected function reportPause(int $timeout): void
|
protected function reportPause(int $timeout): void
|
||||||
{
|
{
|
||||||
|
parent::reportPause($timeout);
|
||||||
$this->pauseCount++;
|
$this->pauseCount++;
|
||||||
$this->lastPause= $timeout;
|
$this->lastPause= $timeout;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user