/*
* This file is part of Psy Shell.
*
- * (c) 2012-2017 Justin Hileman
+ * (c) 2012-2018 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
namespace Psy\ExecutionLoop;
use Psy\Context;
+use Psy\Exception\BreakException;
use Psy\Shell;
/**
- * A forking version of the Psy Shell execution loop.
+ * An execution loop listener that forks the process before executing code.
*
- * This version is preferred, as it won't die prematurely if user input includes
+ * This is awesome, as the session won't die prematurely if user input includes
* a fatal error, such as redeclaring a class or function.
*/
-class ForkingLoop extends Loop
+class ProcessForker extends AbstractListener
{
private $savegame;
+ private $up;
/**
- * Run the execution loop.
+ * Process forker is supported if pcntl and posix extensions are available.
*
- * Forks into a master and a loop process. The loop process will handle the
- * evaluation of all instructions, then return its state via a socket upon
- * completion.
+ * @return bool
+ */
+ public static function isSupported()
+ {
+ return function_exists('pcntl_signal') && function_exists('posix_getpid');
+ }
+
+ /**
+ * Forks into a master and a loop process.
+ *
+ * The loop process will handle the evaluation of all instructions, then
+ * return its state via a socket upon completion.
*
* @param Shell $shell
*/
- public function run(Shell $shell)
+ public function beforeRun(Shell $shell)
{
list($up, $down) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
if (!$up) {
- throw new \RuntimeException('Unable to create socket pair.');
+ throw new \RuntimeException('Unable to create socket pair');
}
$pid = pcntl_fork();
if ($pid < 0) {
- throw new \RuntimeException('Unable to start execution loop.');
+ throw new \RuntimeException('Unable to start execution loop');
} elseif ($pid > 0) {
// This is the main thread. We'll just wait for a while.
fclose($up);
// Wait for a return value from the loop process.
- $read = array($down);
+ $read = [$down];
$write = null;
$except = null;
if (stream_select($read, $write, $except, null) === false) {
- throw new \RuntimeException('Error waiting for execution loop.');
+ throw new \RuntimeException('Error waiting for execution loop');
}
$content = stream_get_contents($down);
$shell->setScopeVariables(@unserialize($content));
}
- return;
+ throw new BreakException('Exiting main thread');
}
// This is the child process. It's going to do all the work.
// We won't be needing this one.
fclose($down);
- // Let's do some processing.
- parent::run($shell);
-
- // Send the scope variables back up to the main thread
- fwrite($up, $this->serializeReturn($shell->getScopeVariables(false)));
- fclose($up);
-
- posix_kill(posix_getpid(), SIGKILL);
+ // Save this; we'll need to close it in `afterRun`
+ $this->up = $up;
}
/**
* Create a savegame at the start of each loop iteration.
+ *
+ * @param Shell $shell
*/
- public function beforeLoop()
+ public function beforeLoop(Shell $shell)
{
$this->createSavegame();
}
/**
* Clean up old savegames at the end of each loop iteration.
+ *
+ * @param Shell $shell
*/
- public function afterLoop()
+ public function afterLoop(Shell $shell)
{
// if there's an old savegame hanging around, let's kill it.
if (isset($this->savegame)) {
}
}
+ /**
+ * After the REPL session ends, send the scope variables back up to the main
+ * thread (if this is a child thread).
+ *
+ * @param Shell $shell
+ */
+ public function afterRun(Shell $shell)
+ {
+ // We're a child thread. Send the scope variables back up to the main thread.
+ if (isset($this->up)) {
+ fwrite($this->up, $this->serializeReturn($shell->getScopeVariables(false)));
+ fclose($this->up);
+
+ posix_kill(posix_getpid(), SIGKILL);
+ }
+ }
+
/**
* Create a savegame fork.
*
$pid = pcntl_fork();
if ($pid < 0) {
- throw new \RuntimeException('Unable to create savegame fork.');
+ throw new \RuntimeException('Unable to create savegame fork');
} elseif ($pid > 0) {
// we're the savegame now... let's wait and see what happens
pcntl_waitpid($pid, $status);
*/
private function serializeReturn(array $return)
{
- $serializable = array();
+ $serializable = [];
foreach ($return as $key => $value) {
// No need to return magic variables
try {
@serialize($value);
$serializable[$key] = $value;
- } catch (\Exception $e) {
- // we'll just ignore this one...
} catch (\Throwable $e) {
+ // we'll just ignore this one...
+ } catch (\Exception $e) {
// and this one too...
+ // @todo remove this once we don't support PHP 5.x anymore :)
}
}