/vendor/psy/psysh/src/Psy/ExecutionLoop/ForkingLoop.php

https://gitlab.com/Pasantias/pasantiasASLG · PHP · 174 lines · 83 code · 28 blank · 63 comment · 12 complexity · cd160250a0a2ac6da8af2b2b73ab12f2 MD5 · raw file

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