annotate vendor/psy/psysh/src/Psy/ExecutionLoop/ForkingLoop.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /*
Chris@0 4 * This file is part of Psy Shell.
Chris@0 5 *
Chris@0 6 * (c) 2012-2017 Justin Hileman
Chris@0 7 *
Chris@0 8 * For the full copyright and license information, please view the LICENSE
Chris@0 9 * file that was distributed with this source code.
Chris@0 10 */
Chris@0 11
Chris@0 12 namespace Psy\ExecutionLoop;
Chris@0 13
Chris@0 14 use Psy\Context;
Chris@0 15 use Psy\Shell;
Chris@0 16
Chris@0 17 /**
Chris@0 18 * A forking version of the Psy Shell execution loop.
Chris@0 19 *
Chris@0 20 * This version is preferred, as it won't die prematurely if user input includes
Chris@0 21 * a fatal error, such as redeclaring a class or function.
Chris@0 22 */
Chris@0 23 class ForkingLoop extends Loop
Chris@0 24 {
Chris@0 25 private $savegame;
Chris@0 26
Chris@0 27 /**
Chris@0 28 * Run the execution loop.
Chris@0 29 *
Chris@0 30 * Forks into a master and a loop process. The loop process will handle the
Chris@0 31 * evaluation of all instructions, then return its state via a socket upon
Chris@0 32 * completion.
Chris@0 33 *
Chris@0 34 * @param Shell $shell
Chris@0 35 */
Chris@0 36 public function run(Shell $shell)
Chris@0 37 {
Chris@0 38 list($up, $down) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
Chris@0 39
Chris@0 40 if (!$up) {
Chris@0 41 throw new \RuntimeException('Unable to create socket pair.');
Chris@0 42 }
Chris@0 43
Chris@0 44 $pid = pcntl_fork();
Chris@0 45 if ($pid < 0) {
Chris@0 46 throw new \RuntimeException('Unable to start execution loop.');
Chris@0 47 } elseif ($pid > 0) {
Chris@0 48 // This is the main thread. We'll just wait for a while.
Chris@0 49
Chris@0 50 // We won't be needing this one.
Chris@0 51 fclose($up);
Chris@0 52
Chris@0 53 // Wait for a return value from the loop process.
Chris@0 54 $read = array($down);
Chris@0 55 $write = null;
Chris@0 56 $except = null;
Chris@0 57 if (stream_select($read, $write, $except, null) === false) {
Chris@0 58 throw new \RuntimeException('Error waiting for execution loop.');
Chris@0 59 }
Chris@0 60
Chris@0 61 $content = stream_get_contents($down);
Chris@0 62 fclose($down);
Chris@0 63
Chris@0 64 if ($content) {
Chris@0 65 $shell->setScopeVariables(@unserialize($content));
Chris@0 66 }
Chris@0 67
Chris@0 68 return;
Chris@0 69 }
Chris@0 70
Chris@0 71 // This is the child process. It's going to do all the work.
Chris@0 72 if (function_exists('setproctitle')) {
Chris@0 73 setproctitle('psysh (loop)');
Chris@0 74 }
Chris@0 75
Chris@0 76 // We won't be needing this one.
Chris@0 77 fclose($down);
Chris@0 78
Chris@0 79 // Let's do some processing.
Chris@0 80 parent::run($shell);
Chris@0 81
Chris@0 82 // Send the scope variables back up to the main thread
Chris@0 83 fwrite($up, $this->serializeReturn($shell->getScopeVariables(false)));
Chris@0 84 fclose($up);
Chris@0 85
Chris@0 86 posix_kill(posix_getpid(), SIGKILL);
Chris@0 87 }
Chris@0 88
Chris@0 89 /**
Chris@0 90 * Create a savegame at the start of each loop iteration.
Chris@0 91 */
Chris@0 92 public function beforeLoop()
Chris@0 93 {
Chris@0 94 $this->createSavegame();
Chris@0 95 }
Chris@0 96
Chris@0 97 /**
Chris@0 98 * Clean up old savegames at the end of each loop iteration.
Chris@0 99 */
Chris@0 100 public function afterLoop()
Chris@0 101 {
Chris@0 102 // if there's an old savegame hanging around, let's kill it.
Chris@0 103 if (isset($this->savegame)) {
Chris@0 104 posix_kill($this->savegame, SIGKILL);
Chris@0 105 pcntl_signal_dispatch();
Chris@0 106 }
Chris@0 107 }
Chris@0 108
Chris@0 109 /**
Chris@0 110 * Create a savegame fork.
Chris@0 111 *
Chris@0 112 * The savegame contains the current execution state, and can be resumed in
Chris@0 113 * the event that the worker dies unexpectedly (for example, by encountering
Chris@0 114 * a PHP fatal error).
Chris@0 115 */
Chris@0 116 private function createSavegame()
Chris@0 117 {
Chris@0 118 // the current process will become the savegame
Chris@0 119 $this->savegame = posix_getpid();
Chris@0 120
Chris@0 121 $pid = pcntl_fork();
Chris@0 122 if ($pid < 0) {
Chris@0 123 throw new \RuntimeException('Unable to create savegame fork.');
Chris@0 124 } elseif ($pid > 0) {
Chris@0 125 // we're the savegame now... let's wait and see what happens
Chris@0 126 pcntl_waitpid($pid, $status);
Chris@0 127
Chris@0 128 // worker exited cleanly, let's bail
Chris@0 129 if (!pcntl_wexitstatus($status)) {
Chris@0 130 posix_kill(posix_getpid(), SIGKILL);
Chris@0 131 }
Chris@0 132
Chris@0 133 // worker didn't exit cleanly, we'll need to have another go
Chris@0 134 $this->createSavegame();
Chris@0 135 }
Chris@0 136 }
Chris@0 137
Chris@0 138 /**
Chris@0 139 * Serialize all serializable return values.
Chris@0 140 *
Chris@0 141 * A naïve serialization will run into issues if there is a Closure or
Chris@0 142 * SimpleXMLElement (among other things) in scope when exiting the execution
Chris@0 143 * loop. We'll just ignore these unserializable classes, and serialize what
Chris@0 144 * we can.
Chris@0 145 *
Chris@0 146 * @param array $return
Chris@0 147 *
Chris@0 148 * @return string
Chris@0 149 */
Chris@0 150 private function serializeReturn(array $return)
Chris@0 151 {
Chris@0 152 $serializable = array();
Chris@0 153
Chris@0 154 foreach ($return as $key => $value) {
Chris@0 155 // No need to return magic variables
Chris@0 156 if (Context::isSpecialVariableName($key)) {
Chris@0 157 continue;
Chris@0 158 }
Chris@0 159
Chris@0 160 // Resources and Closures don't error, but they don't serialize well either.
Chris@0 161 if (is_resource($value) || $value instanceof \Closure) {
Chris@0 162 continue;
Chris@0 163 }
Chris@0 164
Chris@0 165 try {
Chris@0 166 @serialize($value);
Chris@0 167 $serializable[$key] = $value;
Chris@0 168 } catch (\Exception $e) {
Chris@0 169 // we'll just ignore this one...
Chris@0 170 } catch (\Throwable $e) {
Chris@0 171 // and this one too...
Chris@0 172 }
Chris@0 173 }
Chris@0 174
Chris@0 175 return @serialize($serializable);
Chris@0 176 }
Chris@0 177 }