Skip to content

Commit

Permalink
Support for signal handling
Browse files Browse the repository at this point in the history
  • Loading branch information
WyriHaximus committed Nov 10, 2017
1 parent 58e3814 commit f8ad016
Show file tree
Hide file tree
Showing 12 changed files with 562 additions and 1 deletion.
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,49 @@ echo 'a';

See also [example #3](examples).

### addSignal()

The `addSignal(int $signal, callable $listener): void` method can be used to
be notified about OS signals. This is useful to catch user interrupt signals or
shutdown signals from tools like `supervisor` or `systemd`.

The listener callback function MUST be able to accept a single parameter,
the signal added by this method or you MAY use a function which
has no parameters at all.

The listener callback function MUST NOT throw an `Exception`.
The return value of the listener callback function will be ignored and has
no effect, so for performance reasons you're recommended to not return
any excessive data structures.

```php
$listener = function (int $signal) {
echo 'Caught user iterrupt signal', PHP_EOL;
};
$loop->addSignal(SIGINT, $listener);
```

See also [example #4](examples).

**Note: A listener can only be added once to the same signal, any attempts to add it
more then once will be ignored.**

**Note: Signaling is only available on Unix-like platform, Windows isn't supported due
to limitations from underlying signal handlers.**

### removeSignal()

The `removeSignal(int $signal, callable $listener): void` removes a previously added
signal listener.

Any attempts to remove listeners that aren't registerred will be ignored.

```php
$loop->removeSignal(SIGINT, $listener);
```

See also [example #4](examples).

### addReadStream()

> Advanced! Note that this low-level API is considered advanced usage.
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"suggest": {
"ext-libevent": ">=0.1.0 for LibEventLoop and PHP5 only",
"ext-event": "~1.0 for ExtEventLoop",
"ext-libev": "for LibEvLoop"
"ext-libev": "for LibEvLoop",
"ext-pcntl": "For signals support when using the stream_select loop"
},
"autoload": {
"psr-4": {
Expand Down
12 changes: 12 additions & 0 deletions examples/04-signals.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

require __DIR__ . '/../vendor/autoload.php';

$loop = React\EventLoop\Factory::create();

$loop->addSignal(SIGINT, $func = function ($signal) use ($loop, &$func) {
echo 'Signal: ', (string)$signal, PHP_EOL;
$loop->removeSignal(SIGINT, $func);
});

$loop->run();
39 changes: 39 additions & 0 deletions src/ExtEventLoop.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,36 @@ class ExtEventLoop implements LoopInterface
private $readListeners = [];
private $writeListeners = [];
private $running;
private $signals;
private $signalEvents = [];

public function __construct(EventBaseConfig $config = null)
{
$this->eventBase = new EventBase($config);
$this->futureTickQueue = new FutureTickQueue();
$this->timerEvents = new SplObjectStorage();

$this->signals = new SignalsHandler(
$this,
function ($signal) {
$this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, $f = function () use ($signal, &$f) {
$this->signals->call($signal);
// Ensure there are two copies of the callable around until it has been executed.
// For more information see: https://bugs.php.net/bug.php?id=62452
// Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped.
$g = $f;
$f = $g;
});
$this->signalEvents[$signal]->add();
},
function ($signal) {
if ($this->signals->count($signal) === 0) {
$this->signalEvents[$signal]->del();
unset($this->signalEvents[$signal]);
}
}
);

$this->createTimerCallback();
$this->createStreamCallback();
}
Expand Down Expand Up @@ -158,6 +181,22 @@ public function futureTick(callable $listener)
$this->futureTickQueue->add($listener);
}

/**
* {@inheritdoc}
*/
public function addSignal($signal, callable $listener)
{
$this->signals->add($signal, $listener);
}

/**
* {@inheritdoc}
*/
public function removeSignal($signal, callable $listener)
{
$this->signals->remove($signal, $listener);
}

/**
* {@inheritdoc}
*/
Expand Down
40 changes: 40 additions & 0 deletions src/LibEvLoop.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use libev\EventLoop;
use libev\IOEvent;
use libev\SignalEvent;
use libev\TimerEvent;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
Expand All @@ -22,12 +23,35 @@ class LibEvLoop implements LoopInterface
private $readEvents = [];
private $writeEvents = [];
private $running;
private $signals;
private $signalEvents = [];

public function __construct()
{
$this->loop = new EventLoop();
$this->futureTickQueue = new FutureTickQueue();
$this->timerEvents = new SplObjectStorage();

$this->signals = new SignalsHandler(
$this,
function ($signal) {
$this->signalEvents[$signal] = new SignalEvent($f = function () use ($signal, &$f) {
$this->signals->call($signal);
// Ensure there are two copies of the callable around until it has been executed.
// For more information see: https://bugs.php.net/bug.php?id=62452
// Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped.
$g = $f;
$f = $g;
}, $signal);
$this->loop->add($this->signalEvents[$signal]);
},
function ($signal) {
if ($this->signals->count($signal) === 0) {
$this->loop->remove($this->signalEvents[$signal]);
unset($this->signalEvents[$signal]);
}
}
);
}

/**
Expand Down Expand Up @@ -170,6 +194,22 @@ public function futureTick(callable $listener)
$this->futureTickQueue->add($listener);
}

/**
* {@inheritdoc}
*/
public function addSignal($signal, callable $listener)
{
$this->signals->add($signal, $listener);
}

/**
* {@inheritdoc}
*/
public function removeSignal($signal, callable $listener)
{
$this->signals->remove($signal, $listener);
}

/**
* {@inheritdoc}
*/
Expand Down
43 changes: 43 additions & 0 deletions src/LibEventLoop.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Event;
use EventBase;
use React\EventLoop\Signal\Pcntl;
use React\EventLoop\Tick\FutureTickQueue;
use React\EventLoop\Timer\Timer;
use React\EventLoop\Timer\TimerInterface;
Expand All @@ -26,13 +27,39 @@ class LibEventLoop implements LoopInterface
private $readListeners = [];
private $writeListeners = [];
private $running;
private $signals;
private $signalEvents = [];

public function __construct()
{
$this->eventBase = event_base_new();
$this->futureTickQueue = new FutureTickQueue();
$this->timerEvents = new SplObjectStorage();

$this->signals = new SignalsHandler(
$this,
function ($signal) {
$this->signalEvents[$signal] = event_new();
event_set($this->signalEvents[$signal], $signal, EV_PERSIST | EV_SIGNAL, $f = function () use ($signal, &$f) {
$this->signals->call($signal);
// Ensure there are two copies of the callable around until it has been executed.
// For more information see: https://bugs.php.net/bug.php?id=62452
// Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped.
$g = $f;
$f = $g;
});
event_base_set($this->signalEvents[$signal], $this->eventBase);
event_add($this->signalEvents[$signal]);
},
function ($signal) {
if ($this->signals->count($signal) === 0) {
event_del($this->signalEvents[$signal]);
event_free($this->signalEvents[$signal]);
unset($this->signalEvents[$signal]);
}
}
);

$this->createTimerCallback();
$this->createStreamCallback();
}
Expand Down Expand Up @@ -166,6 +193,22 @@ public function futureTick(callable $listener)
$this->futureTickQueue->add($listener);
}

/**
* {@inheritdoc}
*/
public function addSignal($signal, callable $listener)
{
$this->signals->add($signal, $listener);
}

/**
* {@inheritdoc}
*/
public function removeSignal($signal, callable $listener)
{
$this->signals->remove($signal, $listener);
}

/**
* {@inheritdoc}
*/
Expand Down
35 changes: 35 additions & 0 deletions src/LoopInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,41 @@ public function isTimerActive(TimerInterface $timer);
*/
public function futureTick(callable $listener);

/**
* Registers a signal listener with the loop, which
* on it's turn registers it with a signal handler
* suitable for the loop implementation.
*
* A listener can only be added once, any attempts
* to add it again will be ignored.
*
* See also [example #4](examples).
*
* @param int $signal
* @param callable $listener
*
* @throws \BadMethodCallException when signals
* aren't supported by the loop, e.g. when required
* extensions are missing.
*
* @return void
*/
public function addSignal($signal, callable $listener);

/**
* Removed previous registered signal listener from
* the loop, which on it's turn removes it from the
* underlying signal handler.
*
* See also [example #4](examples).
*
* @param int $signal
* @param callable $listener
*
* @return void
*/
public function removeSignal($signal, callable $listener);

/**
* Run the event loop until there are no more tasks to perform.
*/
Expand Down
Loading

0 comments on commit f8ad016

Please sign in to comment.