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