Chris@0: 0) { Chris@0: // This is the main thread. We'll just wait for a while. Chris@0: Chris@0: // We won't be needing this one. Chris@0: fclose($up); Chris@0: Chris@0: // Wait for a return value from the loop process. Chris@0: $read = array($down); Chris@0: $write = null; Chris@0: $except = null; Chris@0: if (stream_select($read, $write, $except, null) === false) { Chris@0: throw new \RuntimeException('Error waiting for execution loop.'); Chris@0: } Chris@0: Chris@0: $content = stream_get_contents($down); Chris@0: fclose($down); Chris@0: Chris@0: if ($content) { Chris@0: $shell->setScopeVariables(@unserialize($content)); Chris@0: } Chris@0: Chris@0: return; Chris@0: } Chris@0: Chris@0: // This is the child process. It's going to do all the work. Chris@0: if (function_exists('setproctitle')) { Chris@0: setproctitle('psysh (loop)'); Chris@0: } Chris@0: Chris@0: // We won't be needing this one. Chris@0: fclose($down); Chris@0: Chris@0: // Let's do some processing. Chris@0: parent::run($shell); Chris@0: Chris@0: // Send the scope variables back up to the main thread Chris@0: fwrite($up, $this->serializeReturn($shell->getScopeVariables(false))); Chris@0: fclose($up); Chris@0: Chris@0: posix_kill(posix_getpid(), SIGKILL); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Create a savegame at the start of each loop iteration. Chris@0: */ Chris@0: public function beforeLoop() Chris@0: { Chris@0: $this->createSavegame(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Clean up old savegames at the end of each loop iteration. Chris@0: */ Chris@0: public function afterLoop() Chris@0: { Chris@0: // if there's an old savegame hanging around, let's kill it. Chris@0: if (isset($this->savegame)) { Chris@0: posix_kill($this->savegame, SIGKILL); Chris@0: pcntl_signal_dispatch(); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Create a savegame fork. Chris@0: * Chris@0: * The savegame contains the current execution state, and can be resumed in Chris@0: * the event that the worker dies unexpectedly (for example, by encountering Chris@0: * a PHP fatal error). Chris@0: */ Chris@0: private function createSavegame() Chris@0: { Chris@0: // the current process will become the savegame Chris@0: $this->savegame = posix_getpid(); Chris@0: Chris@0: $pid = pcntl_fork(); Chris@0: if ($pid < 0) { Chris@0: throw new \RuntimeException('Unable to create savegame fork.'); Chris@0: } elseif ($pid > 0) { Chris@0: // we're the savegame now... let's wait and see what happens Chris@0: pcntl_waitpid($pid, $status); Chris@0: Chris@0: // worker exited cleanly, let's bail Chris@0: if (!pcntl_wexitstatus($status)) { Chris@0: posix_kill(posix_getpid(), SIGKILL); Chris@0: } Chris@0: Chris@0: // worker didn't exit cleanly, we'll need to have another go Chris@0: $this->createSavegame(); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Serialize all serializable return values. Chris@0: * Chris@0: * A naïve serialization will run into issues if there is a Closure or Chris@0: * SimpleXMLElement (among other things) in scope when exiting the execution Chris@0: * loop. We'll just ignore these unserializable classes, and serialize what Chris@0: * we can. Chris@0: * Chris@0: * @param array $return Chris@0: * Chris@0: * @return string Chris@0: */ Chris@0: private function serializeReturn(array $return) Chris@0: { Chris@0: $serializable = array(); Chris@0: Chris@0: foreach ($return as $key => $value) { Chris@0: // No need to return magic variables Chris@0: if (Context::isSpecialVariableName($key)) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: // Resources and Closures don't error, but they don't serialize well either. Chris@0: if (is_resource($value) || $value instanceof \Closure) { Chris@0: continue; Chris@0: } Chris@0: Chris@0: try { Chris@0: @serialize($value); Chris@0: $serializable[$key] = $value; Chris@0: } catch (\Exception $e) { Chris@0: // we'll just ignore this one... Chris@0: } catch (\Throwable $e) { Chris@0: // and this one too... Chris@0: } Chris@0: } Chris@0: Chris@0: return @serialize($serializable); Chris@0: } Chris@0: }