Skip to content

Commit

Permalink
Merge pull request #10 from henriquemoody/handlers
Browse files Browse the repository at this point in the history
Allow more than one handler for the same signal
  • Loading branch information
henriquemoody committed Nov 6, 2014
2 parents a4c78b2 + 21a8dd3 commit b486d4d
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 49 deletions.
2 changes: 1 addition & 1 deletion src/Arara/Process/Child.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public function kill()
protected function setHandlerAlarm()
{
$handler = new Handler\SignalAlarm($this->control, $this->action, $this->context);
$this->control->signal()->handle('alarm', $handler);
$this->control->signal()->setHandler('alarm', $handler);
$this->control->signal()->alarm($this->context->timeout);
}

Expand Down
8 changes: 4 additions & 4 deletions src/Arara/Process/Control.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ public function __construct()
{
$this->info = new Control\Info();
$this->signal = new Control\Signal();
$this->signal->handle('child', new Handler\SignalChild($this));
$this->signal->handle('interrupt', new Handler\SignalInterrupt($this));
$this->signal->handle('quit', new Handler\SignalQuit($this));
$this->signal->handle('terminate', new Handler\SignalTerminate($this));
$this->signal->setHandler('child', new Handler\SignalChild($this));
$this->signal->setHandler('interrupt', new Handler\SignalInterrupt($this));
$this->signal->setHandler('quit', new Handler\SignalQuit($this));
$this->signal->setHandler('terminate', new Handler\SignalTerminate($this));
}

/**
Expand Down
178 changes: 153 additions & 25 deletions src/Arara/Process/Control/Signal.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,38 @@

namespace Arara\Process\Control;

use RuntimeException;
use InvalidArgumentException;

class Signal
{
/**
* List of signals by name.
*
* @var array
*/
protected $signals = array(
'alarm' => SIGALRM,
'child' => SIGCHLD,
'hangup' => SIGHUP,
'abort' => SIGABRT,
'alarm' => SIGALRM,
'child' => SIGCHLD,
'continue' => SIGCONT,
'hangup' => SIGHUP,
'interrupt' => SIGINT,
'kill' => SIGKILL,
'pipe' => SIGPIPE,
'quit' => SIGQUIT,
'stop' => SIGSTOP,
'kill' => SIGKILL,
'pipe' => SIGPIPE,
'quit' => SIGQUIT,
'stop' => SIGSTOP,
'suspend' => SIGTSTP,
'terminate' => SIGTERM,
);

/**
* May contain signals handlers.
*
* @var array
*/
protected $handlers = array();

/**
* Define the time (in seconds) to send an alarm to the current process.
*
Expand All @@ -41,37 +57,133 @@ public function dispatch()
return pcntl_signal_dispatch();
}

/**
* Register a signal handler
*
* @throws RuntimeException When can not register handler.
* @param int $signalNumber Signal number.
* @param callable|int $handler The signal handler.
* @return void
*/
protected function registerHandler($signalNumber, $handler)
{
if (pcntl_signal($signalNumber, $handler)) {
return;
}
throw new RuntimeException('Could not define signal handler');
}

/**
* Define a handler for the given signal.
*
* @link http://php.net/pcntl_signal
* @throws InvalidArgumentException When $handler is not a valid callback.
* @param int|string $signal Signal (code or name) to handle.
* @param callable $handler Callback to handle the given signal.
* @return bool
* @param string|int $signal Signal name, PCNTL constant name or PCNTL constant value.
* @param callable|int $handler The signal handler
* @param string $placement Placement of handler ("set", "append" or "prepend")
* @return void
*/
public function handle($signal, $handler)
protected function handle($signal, $handler, $placement)
{
declare(ticks = 1);

$signalNumber = $this->translateSignal($signal);

if (is_int($handler) && in_array($handler, array(SIG_IGN, SIG_DFL))) {
unset($this->handlers[$signalNumber]);
$this->registerHandler($signalNumber, $handler);
return;
}

if (! is_callable($handler)) {
throw new InvalidArgumentException('The given handler is not a valid callback');
}

return pcntl_signal($this->translateSignal($signal), $handler);
$this->placeHandler($signalNumber, $handler, $placement);
}

/**
* Define a callback handler for the given signal.
*
* @param int $signal Signal number.
* @param callable $handler The signal handler.
* @param string $placement Placement of handler ("set", "append" or "prepend")
* @return void
*/
protected function placeHandler($signalNumber, $handler, $placement)
{
if (! isset($this->handlers[$signalNumber])) {
$this->handlers[$signalNumber] = array();
$this->registerHandler($signalNumber, $this);
}

switch ($placement) {
case 'set':
$this->handlers[$signalNumber] = array($handler);
break;

case 'append':
array_push($this->handlers[$signalNumber], $handler);
break;

case 'prepend':
array_unshift($this->handlers[$signalNumber], $handler);
break;
}
}

/**
* Returns handlers of a specific signal.
*
* @return array
*/
public function getHandlers($signal)
{
$signalNumber = $this->translateSignal($signal);
$handlers = array();
if (isset($this->handlers[$signalNumber])) {
$handlers = $this->handlers[$signalNumber];
}

return $handlers;
}

/**
* Ignore (do not handle) the given signal.
* Overwrite signal handlers with the defined handler.
*
* Not only user defined handlers, but there are default handlers for a good
* part of existing signals.
* @see handle()
* @param string|int $signal Signal name, PCNTL constant name or PCNTL constant value.
* @param callable|int $handler The signal handler
* @return void
*/
public function setHandler($signal, $handler)
{
$this->handle($signal, $handler, 'set');
}

/**
* Appends the handler to the current signal handler stack.
*
* @link http://php.net/pcntl_signal
* @param int|string $signal Signal (code or name) to ignore.
* @return bool
* @see handle()
* @param string|int $signal Signal name, PCNTL constant name or PCNTL constant value.
* @param callable|int $handler The signal handler
* @return void
*/
public function appendHandler($signal, $handler)
{
$this->handle($signal, $handler, 'append');
}

/**
* Prepends the handler to the current signal handler stack.
*
* @see handle()
* @param string|int $signal Signal name, PCNTL constant name or PCNTL constant value.
* @param callable|int $handler The signal handler
* @return void
*/
public function ignore($signal)
public function prependHandler($signal, $handler)
{
return pcntl_signal($this->translateSignal($signal), SIG_IGN);
$this->handle($signal, $handler, 'prepend');
}

/**
Expand All @@ -94,19 +206,35 @@ public function send($signal, $processId = null)
/**
* Translate signals names to codes.
*
* @param mixed $signal Signal name, PCNTL constant name or PCNTL constant value.
* @throws InvalidArgumentException Then signal is not a valid signal.
* @param string|int $signal Signal name, PCNTL constant name or PCNTL constant value.
* @return int
*/
protected function translateSignal($signal)
{
if (isset($this->signals[$signal])) {
return $this->signals[$signal];
$signal = $this->signals[$signal];
} elseif (defined($signal)) {
$signal = constant($signal);
}

if (defined($signal)) {
return constant($signal);
if (! is_int($signal)) {
throw new InvalidArgumentException('The given value is not a valid signal');
}

return $signal;
}

/**
* Handles the signals using all handlers in the stack.
*
* @param int $signalNumber Signal number to be handled.
* @return void
*/
public function __invoke($signalNumber)
{
foreach ($this->getHandlers($signalNumber) as $handler) {
call_user_func($handler, $signalNumber);
}
}
}
2 changes: 1 addition & 1 deletion tests/Arara/Process/ChildTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ public function testShouldDefineTimeoutHandleWhenStart()
$signal = $this->controlSignal->getMock();
$signal
->expects($this->once())
->method('handle')
->method('setHandler')
->with('alarm');

$control = $this->control->getMock();
Expand Down
Loading

0 comments on commit b486d4d

Please sign in to comment.