8000 Add support for signal handling by WyriHaximus · Pull Request #104 · reactphp/event-loop · GitHub
[go: up one dir, main page]

Skip to content

Add support for signal handling #104

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 10, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
8000
Diff view
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.**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No objection to this limitation, but I would suggest making sure this is closer to the verbiage for our stream API (https://github.com/reactphp/event-loop/pull/110/files#diff-04c6e90faac2675aa89e2176d2eec7d8R312). What do you think about this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated it, it isn't my strongest skill and I rather not let it block this PR thus I suggest if it needs work I do it in a follow up PR.


**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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the same listener be added multiple times? If so, which one will be removed here?

Also, the docs should probably include what happens if the given signal or listener is not attached (NO-OP just like other methods?).


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