8000 Support for signal handling · reactphp/event-loop@f8ad016 · GitHub
[go: up one dir, main page]

Skip to content

Commit f8ad016

Browse files
committed
Support for signal handling
1 parent 58e3814 commit f8ad016

12 files changed

+562
-1
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,49 @@ echo 'a';
292292

293293
See also [example #3](examples).
294294

295+
### addSignal()
296+
297+
The `addSignal(int $signal, callable $listener): void` method can be used to
298+
be notified about OS signals. This is useful to catch user interrupt signals or
299+
shutdown signals from tools like `supervisor` or `systemd`.
300+
301+
The listener callback function MUST be able to accept a single parameter,
302+
the signal added by this method or you MAY use a function which
303+
has no parameters at all.
304+
305+
The listener callback function MUST NOT throw an `Exception`.
306+
The return value of the listener callback function will be ignored and has
307+
no effect, so for performance reasons you're recommended to not return
308+
any excessive data structures.
309+
310+
```php
311+
$listener = function (int $signal) {
312+
echo 'Caught user iterrupt signal', PHP_EOL;
313+
};
314+
$loop->addSignal(SIGINT, $listener);
315+
```
316+
317+
See also [example #4](examples).
318+
319+
**Note: A listener can only be added once to the same signal, any attempts to add it
320+
more then once will be ignored.**
321+
322+
**Note: Signaling is only available on Unix-like platform, Windows isn't supported due
323+
to limitations from underlying signal handlers.**
324+
325+
### removeSignal()
326+
327+
The `removeSignal(int $signal, callable $listener): void` removes a previously added
328+
signal listener.
329+
330+
Any attempts to remove listeners that aren't registerred will be ignored.
331+
332+
```php
333+
$loop->removeSignal(SIGINT, $listener);
334+
```
335+
336+
See also [example #4](examples).
337+
295338
### addReadStream()
296339

297340
> Advanced! Note that this low-level API is considered advanced usage.

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"suggest": {
1313
"ext-libevent": ">=0.1.0 for LibEventLoop and PHP5 only",
1414
"ext-event": "~1.0 for ExtEventLoop",
15-
"ext-libev": "for LibEvLoop"
15+
"ext-libev": "for LibEvLoop",
16+
"ext-pcntl": "For signals support when using the stream_select loop"
1617
},
1718
"autoload": {
1819
"psr-4": {

examples/04-signals.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
require __DIR__ . '/../vendor/autoload.php';
4+
5+
$loop = React\EventLoop\Factory::create();
6+
7+
$loop->addSignal(SIGINT, $func = function ($signal) use ($loop, &$func) {
8+
echo 'Signal: ', (string)$signal, PHP_EOL;
9+
$loop->removeSignal(SIGINT, $func);
10+
});
11+
12+
$loop->run();

src/ExtEventLoop.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,36 @@ class ExtEventLoop implements LoopInterface
2525
private $readListeners = [];
2626
private $writeListeners = [];
2727
private $running;
28+
private $signals;
29+
private $signalEvents = [];
2830

2931
public function __construct(EventBaseConfig $config = null)
3032
{
3133
$this->eventBase = new EventBase($config);
3234
$this->futureTickQueue = new FutureTickQueue();
3335
$this->timerEvents = new SplObjectStorage();
3436

37+
$this->signals = new SignalsHandler(
38+
$this,
39+
function ($signal) {
40+
$this->signalEvents[$signal] = Event::signal($this->eventBase, $signal, $f = function () use ($signal, &$f) {
41+
$this->signals->call($signal);
42+
// Ensure there are two copies of the callable around until it has been executed.
43+
// For more information see: https://bugs.php.net/bug.php?id=62452
44+
// Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped.
45+
$g = $f;
46+
$f = $g;
47+
});
48+
$this->signalEvents[$signal]->add();
49+
},
50+
function ($signal) {
51+
if ($this->signals->count($signal) === 0) {
52+
$this->signalEvents[$signal]->del();
53+
unset($this->signalEvents[$signal]);
54+
}
55+
}
56+
);
57+
3558
$this->createTimerCallback();
3659
$this->createStreamCallback();
3760
}
@@ -158,6 +181,22 @@ public function futureTick(callable $listener)
158181
$this->futureTickQueue->add($listener);
159182
}
160183

184+
/**
185+
* {@inheritdoc}
186+
*/
187+
public function addSignal($signal, callable $listener)
188+
{
189+
$this->signals->add($signal, $listener);
190+
}
191+
192+
/**
193+
* {@inheritdoc}
194+
*/
195+
public function removeSignal($signal, callable $listener)
196+
{
197+
$this->signals->remove($signal, $listener);
198+
}
199+
161200
/**
162201
* {@inheritdoc}
163202
*/

src/LibEvLoop.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use libev\EventLoop;
66
use libev\IOEvent;
7+
use libev\SignalEvent;
78
use libev\TimerEvent;
89
use React\EventLoop\Tick\FutureTickQueue;
910
use React\EventLoop\Timer\Timer;
@@ -22,12 +23,35 @@ class LibEvLoop implements LoopInterface
2223
private $readEvents = [];
2324
private $writeEvents = [];
2425
private $running;
26+
private $signals;
27+
private $signalEvents = [];
2528

2629
public function __construct()
2730
{
2831
$this->loop = new EventLoop();
2932
$this->futureTickQueue = new FutureTickQueue();
3033
$this->timerEvents = new SplObjectStorage();
34+
35+
$this->signals = new SignalsHandler(
36+
$this,
37+
function ($signal) {
38+
$this->signalEvents[$signal] = new SignalEvent($f = function () use ($signal, &$f) {
39+
$this->signals->call($signal);
40+
// Ensure there are two copies of the callable around until it has been executed.
41+
// For more information see: https://bugs.php.net/bug.php?id=62452
42+
// Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped.
43+
$g = $f;
44+
$f = $g;
45+
}, $signal);
46+
$this->loop->add($this->signalEvents[$signal]);
47+
},
48+
function ($signal) {
49+
if ($this->signals->count($signal) === 0) {
50+
$this->loop->remove($this->signalEvents[$signal]);
51+
unset($this->signalEvents[$signal]);
52+
}
53+
}
54+
);
3155
}
3256

3357
/**
@@ -170,6 +194,22 @@ public function futureTick(callable $listener)
170194
$this->futureTickQueue->add($listener);
171195
}
172196

197+
/**
198+
* {@inheritdoc}
199+
*/
200+
public function addSignal($signal, callable $listener)
201+
{
202+
$this->signals->add($signal, $listener);
203+
}
204+
205+
/**
206+
* {@inheritdoc}
207+
*/
208+
public function removeSignal($signal, callable $listener)
209+
{
210+
$this->signals->remove($signal, $listener);
211+
}
212+
173213
/**
174214
* {@inheritdoc}
175215
*/

src/LibEventLoop.php

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Event;
66
use EventBase;
7+
use React\EventLoop\Signal\Pcntl;
78
use React\EventLoop\Tick\FutureTickQueue;
89
use React\EventLoop\Timer\Timer;
910
use React\EventLoop\Timer\TimerInterface;
@@ -26,13 +27,39 @@ class LibEventLoop implements LoopInterface
2627
private $readListeners = [];
2728
private $writeListeners = [];
2829
private $running;
30+
private $signals;
31+
private $signalEvents = [];
2932

3033
public function __construct()
3134
{
3235
$this->eventBase = event_base_new();
3336
$this->futureTickQueue = new FutureTickQueue();
3437
$this->timerEvents = new SplObjectStorage();
3538

39+
$this->signals = new SignalsHandler(
40+
$this,
41+
function ($signal) {
42+
$this->signalEvents[$signal] = event_new();
43+
event_set($this->signalEvents[$signal], $signal, EV_PERSIST | EV_SIGNAL, $f = function () use ($signal, &$f) {
44+
$this->signals->call($signal);
45+
// Ensure there are two copies of the callable around until it has been executed.
46+
// For more information see: https://bugs.php.net/bug.php?id=62452
47+
// Only an issue for PHP 5, this hack can be removed once PHP 5 suppose has been dropped.
48+
$g = $f;
49+
$f = $g;
50+
});
51+
event_base_set($this->signalEvents[$signal], $this->eventBase);
52+
event_add($this->signalEvents[$signal]);
53+
},
54+
function ($signal) {
55+
if ($this->signals->count($signal) === 0) {
56+
event_del($this->signalEvents[$signal]);
57+
event_free($this->signalEvents[$signal]);
58+
unset($this->signalEvents[$signal]);
59+
}
60+
}
61+
);
62+
3663
$this->createTimerCallback();
3764
$this->createStreamCallback();
3865
}
@@ -166,6 +193,22 @@ public function futureTick(callable $listener)
166193
$this->futureTickQueue->add($listener);
167194
}
168195

196+
/**
197+
* {@inheritdoc}
198+
*/
199+
public function addSignal($signal, callable $listener)
200+
{
201+
$this->signals->add($signal, $listener);
202+
}
203+
204+
/**
205+
* {@inheritdoc}
206+
*/
207+
public function removeSignal($signal, callable $listener)
208+
{
209+
$this->signals->remove($signal, $listener);
210+
}
211+
169212
/**
170213
* {@inheritdoc}
171214
*/

src/LoopInterface.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,41 @@ public function isTimerActive(TimerInterface $timer);
326326
*/
327327
public function futureTick(callable $listener);
328328

329+
/**
330+
* Registers a signal listener with the loop, which
331+
* on it's turn registers it with a signal handler
332+
* suitable for the loop implementation.
333+
*
334+
* A listener can only be added once, any attempts
335+
* to add it again will be ignored.
336+
*
337+
* See also [example #4](examples).
338+
*
339+
* @param int $signal
340+
* @param callable $listener
341+
*
342+
* @throws \BadMethodCallException when signals
343+
* aren't supported by the loop, e.g. when required
344+
* extensions are missing.
345+
*
346+
* @return void
347+
*/
348+
public function addSignal($signal, callable $listener);
349+
350+
/**
351+
* Removed previous registered signal listener from
352+
* the loop, which on it's turn removes it from the
353+
* underlying signal handler.
354+
*
355+
* See also [example #4](examples).
356+
*
357+
* @param int $signal
358+
* @param callable $listener
359+
*
360+
* @return void
361+
*/
362+
public function removeSignal($signal, callable $listener);
363+
329364
/**
330365
* Run the event loop until there are no more tasks to perform.
331366
*/

0 commit comments

Comments
 (0)
0