Yaffs site version 1.1
[yaffs-website] / vendor / psy / psysh / src / Psy / ExecutionLoop / ForkingLoop.php
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 }