More updates to stop using dev or alpha or beta versions.
[yaffs-website] / vendor / psy / psysh / src / ExecutionLoop / ProcessForker.php
1 <?php
2
3 /*
4  * This file is part of Psy Shell.
5  *
6  * (c) 2012-2018 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\Exception\BreakException;
16 use Psy\Shell;
17
18 /**
19  * An execution loop listener that forks the process before executing code.
20  *
21  * This is awesome, as the session won't die prematurely if user input includes
22  * a fatal error, such as redeclaring a class or function.
23  */
24 class ProcessForker extends AbstractListener
25 {
26     private $savegame;
27     private $up;
28
29     /**
30      * Process forker is supported if pcntl and posix extensions are available.
31      *
32      * @return bool
33      */
34     public static function isSupported()
35     {
36         return function_exists('pcntl_signal') && function_exists('posix_getpid');
37     }
38
39     /**
40      * Forks into a master and a loop process.
41      *
42      * The loop process will handle the evaluation of all instructions, then
43      * return its state via a socket upon completion.
44      *
45      * @param Shell $shell
46      */
47     public function beforeRun(Shell $shell)
48     {
49         list($up, $down) = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
50
51         if (!$up) {
52             throw new \RuntimeException('Unable to create socket pair');
53         }
54
55         $pid = pcntl_fork();
56         if ($pid < 0) {
57             throw new \RuntimeException('Unable to start execution loop');
58         } elseif ($pid > 0) {
59             // This is the main thread. We'll just wait for a while.
60
61             // We won't be needing this one.
62             fclose($up);
63
64             // Wait for a return value from the loop process.
65             $read   = [$down];
66             $write  = null;
67             $except = null;
68             if (stream_select($read, $write, $except, null) === false) {
69                 throw new \RuntimeException('Error waiting for execution loop');
70             }
71
72             $content = stream_get_contents($down);
73             fclose($down);
74
75             if ($content) {
76                 $shell->setScopeVariables(@unserialize($content));
77             }
78
79             throw new BreakException('Exiting main thread');
80         }
81
82         // This is the child process. It's going to do all the work.
83         if (function_exists('setproctitle')) {
84             setproctitle('psysh (loop)');
85         }
86
87         // We won't be needing this one.
88         fclose($down);
89
90         // Save this; we'll need to close it in `afterRun`
91         $this->up = $up;
92     }
93
94     /**
95      * Create a savegame at the start of each loop iteration.
96      *
97      * @param Shell $shell
98      */
99     public function beforeLoop(Shell $shell)
100     {
101         $this->createSavegame();
102     }
103
104     /**
105      * Clean up old savegames at the end of each loop iteration.
106      *
107      * @param Shell $shell
108      */
109     public function afterLoop(Shell $shell)
110     {
111         // if there's an old savegame hanging around, let's kill it.
112         if (isset($this->savegame)) {
113             posix_kill($this->savegame, SIGKILL);
114             pcntl_signal_dispatch();
115         }
116     }
117
118     /**
119      * After the REPL session ends, send the scope variables back up to the main
120      * thread (if this is a child thread).
121      *
122      * @param Shell $shell
123      */
124     public function afterRun(Shell $shell)
125     {
126         // We're a child thread. Send the scope variables back up to the main thread.
127         if (isset($this->up)) {
128             fwrite($this->up, $this->serializeReturn($shell->getScopeVariables(false)));
129             fclose($this->up);
130
131             posix_kill(posix_getpid(), SIGKILL);
132         }
133     }
134
135     /**
136      * Create a savegame fork.
137      *
138      * The savegame contains the current execution state, and can be resumed in
139      * the event that the worker dies unexpectedly (for example, by encountering
140      * a PHP fatal error).
141      */
142     private function createSavegame()
143     {
144         // the current process will become the savegame
145         $this->savegame = posix_getpid();
146
147         $pid = pcntl_fork();
148         if ($pid < 0) {
149             throw new \RuntimeException('Unable to create savegame fork');
150         } elseif ($pid > 0) {
151             // we're the savegame now... let's wait and see what happens
152             pcntl_waitpid($pid, $status);
153
154             // worker exited cleanly, let's bail
155             if (!pcntl_wexitstatus($status)) {
156                 posix_kill(posix_getpid(), SIGKILL);
157             }
158
159             // worker didn't exit cleanly, we'll need to have another go
160             $this->createSavegame();
161         }
162     }
163
164     /**
165      * Serialize all serializable return values.
166      *
167      * A naïve serialization will run into issues if there is a Closure or
168      * SimpleXMLElement (among other things) in scope when exiting the execution
169      * loop. We'll just ignore these unserializable classes, and serialize what
170      * we can.
171      *
172      * @param array $return
173      *
174      * @return string
175      */
176     private function serializeReturn(array $return)
177     {
178         $serializable = [];
179
180         foreach ($return as $key => $value) {
181             // No need to return magic variables
182             if (Context::isSpecialVariableName($key)) {
183                 continue;
184             }
185
186             // Resources and Closures don't error, but they don't serialize well either.
187             if (is_resource($value) || $value instanceof \Closure) {
188                 continue;
189             }
190
191             try {
192                 @serialize($value);
193                 $serializable[$key] = $value;
194             } catch (\Throwable $e) {
195                 // we'll just ignore this one...
196             } catch (\Exception $e) {
197                 // and this one too...
198                 // @todo remove this once we don't support PHP 5.x anymore :)
199             }
200         }
201
202         return @serialize($serializable);
203     }
204 }