1
0
mirror of https://github.com/danog/loop.git synced 2024-11-26 11:54:51 +01:00
Go to file
2023-01-22 10:05:04 +01:00
.github Cleanup 2022-12-24 17:48:33 +01:00
examples Fix memory leak 2023-01-22 10:05:04 +01:00
lib Fix memory leak 2023-01-22 10:05:04 +01:00
test Fix memory leak 2023-01-22 10:05:04 +01:00
.gitignore Refactoring 2022-12-24 17:24:51 +01:00
.php-cs-fixer.dist.php Refactoring 2022-12-24 17:24:51 +01:00
composer.json Cleanup 2022-12-24 14:37:39 +01:00
LICENSE First commit 2020-07-21 18:06:19 +02:00
phpunit.xml.dist Add resumeDeferOnce 2021-04-21 15:34:59 +02:00
psalm.xml Fix memory leak 2023-01-22 10:05:04 +01:00
README.md Update 2022-12-24 19:04:18 +01:00

Loop

Build status codecov Psalm coverage License

danog/loop provides a set of powerful async loop APIs for executing operations periodically or on demand, in background loops a-la threads.
A more flexible and powerful alternative to AMPHP's repeat function, allowing dynamically changeable repeat periods, resumes and signaling.

Installation

composer require danog/loop

API

All loop APIs are defined by a set of interfaces: however, to use them, you would usually have to extend only one of the abstract class implementations.

Loop

Interface - Example

A basic loop, capable of running in background (asynchronously) the code contained in the loop function.

API:

namespace danog\Loop;

abstract class Loop
{
    abstract public function loop();
    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 fiber 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.

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 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 method.

ResumableLoop

Interface - Example

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.

namespace danog\Loop;

abstract class ResumableLoop extends Loop
{
    public function pause(?int $time = null): Future;
    public function resume(): Future;
}

All methods from 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 future that is resolved when the loop is paused again.

SignalLoop

Interface - Example

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.

namespace danog\Loop;

abstract class SignalLoop extends Loop
{
    public function signal($what): void;
    public function waitSignal($promise): Future;
}

All methods from 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 future or return|throw passed signal.

ResumableSignalLoop

Interface - Example

This is what you would usually use to build a full async loop.
All loop interfaces and loop implementations are combined into a single class you can extend.

namespace danog\Loop;

abstract class ResumableSignalLoop extends SignalLoop, ResumableSignalLoop
{
}

The class is actually composited using traits to feature all methods from SignalLoop and ResumableSignalLoop.

GenericLoop

Class - Example

If you want a simpler way to use the ResumableSignalLoop, you can use the GenericLoop.

namespace danog\Loop\Generic;
class GenericLoop extends ResumableSignalLoop
{
    /**
     * Stop the loop.
     */
    const STOP = -1;
    /**
     * Pause the loop.
     */
    const PAUSE = null;
    /**
     * 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.
     */
    public function __toString(): string;
}

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
  • GenericLoop::PAUSE - The loop will pause forever (or until the resume method is called on the loop object from outside the loop)
  • GenericLoop::CONTINUE - Return this if you want to rerun the loop without waiting

If the callable does not return anything, the loop will behave is if GenericLoop::PAUSE was returned.

PeriodicLoop

Class - Example

If you simply want to execute an action every N seconds, PeriodicLoop is the way to go.

namespace danog\Loop\Generic;

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
     */
    public function __construct(callable $callback, string $name, ?int $interval);
    /**
     * Get name of the loop, passed to the constructor.
     *
     * @return string
     */
    public function __toString(): string;
}

PeriodicLoop runs a callback at a periodic interval.
The callback will be bound to the PeriodicLoop 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 can be stopped from the outside or from the inside by signaling or returning true.