push  v0.0.1
PHP shell
Worker.php
1 <?php
2 
3 namespace fpoirotte\push;
4 
5 class Worker extends Protocol
6 {
7  const OP_START = "\x00";
8  const OP_END = "\x01";
9  const OP_READY = "\x02";
10 
11  protected $child;
12  protected $scope;
13  protected $data;
14  protected $result;
15 
16  public function __construct($socket)
17  {
18  parent::__construct('\\fpoirotte\\push\\Manager');
19  $this->socket = $socket;
20  $this->child = null;
21  $this->scope = array();
22  $this->data = null;
23  $this->result = null;
24  }
25 
26  public function run()
27  {
28  // Prepare the environment.
29  list($file, $line) = $this->evaluate(true);
30 
31  // We are now ready to process commands, let's notify the manager!
32  $this->send(self::OP_READY, "$file($line)");
33 
34  // We keep processing incoming commands.
35  while (true) {
36  $this->runOnce();
37  }
38  }
39 
40  protected function cancel()
41  {
42  if ($this->child === null) {
43  return;
44  }
45 
46  echo "Cancelling...\n";
47  posix_kill($this->child, SIGKILL);
48  pcntl_signal_dispatch();
49  }
50 
51  protected function handleCOMMAND($data)
52  {
53  $pid = getmypid();
54  $this->child = pcntl_fork();
55 
56  if ($this->child < 0) {
57  throw new \RuntimeException('Could not run command');
58  }
59 
60  if ($this->child == 0) {
61  // We are now executing in a new worker. \o/
62 
63  // Restore the default handler for the SIGINT signal.
64  pcntl_signal(SIGINT, SIG_DFL, true);
65 
66  // Notify the manager that we are about to process the command.
67  $this->send(self::OP_START, (string) getmypid());
68 
69  // Execute the command and output the results.
70  $this->data = $data;
71  ini_set('display_errors', '0');
72  $this->evaluate();
73  ini_set('display_errors', '1');
74  $this->outputResult();
75 
76  /* Kill the previous worker, because it is now obsolete
77  * (the new worker has a more up-to-date state, which
78  * includes any potential side effects from the last command).
79  * Finally, we notify the manager that the command's execution
80  * has finished (so that it may send new commands our way).
81  *
82  * Note :
83  *
84  * This section of the code is never executed if the command
85  * raised a fatal error/exception.
86  * In this case, the previous worker will handle it.
87  */
88  posix_kill($pid, SIGKILL);
89  pcntl_signal_dispatch();
90  $this->send(self::OP_END);
91  } else {
92  // We're executing in the old worker.
93 
94  // Set up a signal handler so that we may interrupt the running
95  // command if necessary.
96  pcntl_signal(SIGINT, array($this, 'cancel'), true);
97 
98  // Wait for the new worker to terminate.
99  // Normally, this will never happen because the new worker
100  // will kill us right after it's done doing whatever it was doing.
101  $child = pcntl_waitpid(-1, $status);
102  if ($child > 0) {
103  // The new worker exited normally (eg. executed "exit 0;").
104  // In that case, we also exit normally.
105  if (pcntl_wifexited($child) && pcntl_wexitstatus($child) === 0) {
106  exit(0);
107  }
108 
109  // Otherwise, it means the command triggered a fatal error
110  // and we have the latest state.
111  // Notify the manager that we are ready to accept new commands.
112  $this->child = null;
113  $this->send(self::OP_END);
114  }
115  }
116  }
117 
118  protected function evaluate($magic = false)
119  {
120  if ($magic) {
121  // Return this file's name & the line where eval() can be found.
122  return array(__FILE__, __LINE__ + 4);
123  }
124  unset($magic);
125  extract($this->scope, EXTR_OVERWRITE | EXTR_PREFIX_SAME, '_');
126  $this->result = eval('return ' . $this->data);
127  $this->scope = get_defined_vars();
128  return $this->result;
129  }
130 
131  protected function outputResult()
132  {
133  if ($this->result === null) {
134  return;
135  }
136 
137  fwrite(STDOUT, var_export($this->result, true));
138  }
139 
140  protected function handleSIGNAL($data)
141  {
142  $signo = (int) $data;
143 
144  // Just in case we received something that is not a valid signal.
145  if ($signo === 0) {
146  throw new \RuntimeException();
147  }
148 
149  posix_kill(getmypid(), $signo);
150  if (function_exists('pcntl_signal_dispatch')) {
151  pcntl_signal_dispatch();
152  }
153  }
154 }