Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
daemon.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage Application
5  *
6  * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
7  * @license GNU General Public License version 2 or later; see LICENSE
8  */
9 
10 defined('JPATH_PLATFORM') or die;
11 
12 jimport('joomla.filesystem.folder');
13 
14 /**
15  * Class to turn JCli applications into daemons. It requires CLI and PCNTL support built into PHP.
16  *
17  * @package Joomla.Platform
18  * @subpackage Application
19  * @see http://www.php.net/manual/en/book.pcntl.php
20  * @see http://php.net/manual/en/features.commandline.php
21  * @since 11.1
22  */
24 {
25  /**
26  * @var array The available POSIX signals to be caught by default.
27  * @see http://php.net/manual/pcntl.constants.php
28  * @since 11.1
29  */
30  protected static $signals = array(
31  'SIGHUP',
32  'SIGINT',
33  'SIGQUIT',
34  'SIGILL',
35  'SIGTRAP',
36  'SIGABRT',
37  'SIGIOT',
38  'SIGBUS',
39  'SIGFPE',
40  'SIGUSR1',
41  'SIGSEGV',
42  'SIGUSR2',
43  'SIGPIPE',
44  'SIGALRM',
45  'SIGTERM',
46  'SIGSTKFLT',
47  'SIGCLD',
48  'SIGCHLD',
49  'SIGCONT',
50  'SIGTSTP',
51  'SIGTTIN',
52  'SIGTTOU',
53  'SIGURG',
54  'SIGXCPU',
55  'SIGXFSZ',
56  'SIGVTALRM',
57  'SIGPROF',
58  'SIGWINCH',
59  'SIGPOLL',
60  'SIGIO',
61  'SIGPWR',
62  'SIGSYS',
63  'SIGBABY',
64  'SIG_BLOCK',
65  'SIG_UNBLOCK',
66  'SIG_SETMASK'
67  );
68 
69  /**
70  * @var boolean True if the daemon is in the process of exiting.
71  * @since 11.1
72  */
73  protected $exiting = false;
74 
75  /**
76  * @var integer The parent process id.
77  * @since 12.1
78  */
79  protected $parentId = 0;
80 
81  /**
82  * @var integer The process id of the daemon.
83  * @since 11.1
84  */
85  protected $processId = 0;
86 
87  /**
88  * @var boolean True if the daemon is currently running.
89  * @since 11.1
90  */
91  protected $running = false;
92 
93  /**
94  * Class constructor.
95  *
96  * @param mixed $input An optional argument to provide dependency injection for the application's
97  * input object. If the argument is a JInputCli object that object will become
98  * the application's input object, otherwise a default input object is created.
99  * @param mixed $config An optional argument to provide dependency injection for the application's
100  * config object. If the argument is a JRegistry object that object will become
101  * the application's config object, otherwise a default config object is created.
102  * @param mixed $dispatcher An optional argument to provide dependency injection for the application's
103  * event dispatcher. If the argument is a JEventDispatcher object that object will become
104  * the application's event dispatcher, if it is null then the default event dispatcher
105  * will be created based on the application's loadDispatcher() method.
106  *
107  * @since 11.1
108  * @throws RuntimeException
109  */
110  public function __construct(JInputCli $input = null, JRegistry $config = null, JEventDispatcher $dispatcher = null)
111  {
112  // Verify that the process control extension for PHP is available.
113  // @codeCoverageIgnoreStart
114  if (!defined('SIGHUP'))
115  {
116  JLog::add('The PCNTL extension for PHP is not available.', JLog::ERROR);
117  throw new RuntimeException('The PCNTL extension for PHP is not available.');
118  }
119 
120  // Verify that POSIX support for PHP is available.
121  if (!function_exists('posix_getpid'))
122  {
123  JLog::add('The POSIX extension for PHP is not available.', JLog::ERROR);
124  throw new RuntimeException('The POSIX extension for PHP is not available.');
125  }
126  // @codeCoverageIgnoreEnd
127 
128  // Call the parent constructor.
129  parent::__construct($input, $config, $dispatcher);
130 
131  // Set some system limits.
132  @set_time_limit($this->config->get('max_execution_time', 0));
133 
134  if ($this->config->get('max_memory_limit') !== null)
135  {
136  ini_set('memory_limit', $this->config->get('max_memory_limit', '256M'));
137  }
138 
139  // Flush content immediately.
140  ob_implicit_flush();
141  }
142 
143  /**
144  * Method to handle POSIX signals.
145  *
146  * @param integer $signal The received POSIX signal.
147  *
148  * @return void
149  *
150  * @since 11.1
151  * @see pcntl_signal()
152  * @throws RuntimeException
153  */
154  public static function signal($signal)
155  {
156  // Log all signals sent to the daemon.
157  JLog::add('Received signal: ' . $signal, JLog::DEBUG);
158 
159  // Let's make sure we have an application instance.
160  if (!is_subclass_of(static::$instance, 'JApplicationDaemon'))
161  {
162  JLog::add('Cannot find the application instance.', JLog::EMERGENCY);
163  throw new RuntimeException('Cannot find the application instance.');
164  }
165 
166  // Fire the onReceiveSignal event.
167  static::$instance->triggerEvent('onReceiveSignal', array($signal));
168 
169  switch ($signal)
170  {
171  case SIGINT:
172  case SIGTERM:
173  // Handle shutdown tasks
174  if (static::$instance->running && static::$instance->isActive())
175  {
176  static::$instance->shutdown();
177  }
178  else
179  {
180  static::$instance->close();
181  }
182  break;
183  case SIGHUP:
184  // Handle restart tasks
185  if (static::$instance->running && static::$instance->isActive())
186  {
187  static::$instance->shutdown(true);
188  }
189  else
190  {
191  static::$instance->close();
192  }
193  break;
194  case SIGCHLD:
195  // A child process has died
196  while (static::$instance->pcntlWait($signal, WNOHANG || WUNTRACED) > 0)
197  {
198  usleep(1000);
199  }
200  break;
201  case SIGCLD:
202  while (static::$instance->pcntlWait($signal, WNOHANG) > 0)
203  {
204  $signal = static::$instance->pcntlChildExitStatus($signal);
205  }
206  break;
207  default:
208  break;
209  }
210  }
211 
212  /**
213  * Check to see if the daemon is active. This does not assume that $this daemon is active, but
214  * only if an instance of the application is active as a daemon.
215  *
216  * @return boolean True if daemon is active.
217  *
218  * @since 11.1
219  */
220  public function isActive()
221  {
222  // Get the process id file location for the application.
223  $pidFile = $this->config->get('application_pid_file');
224 
225  // If the process id file doesn't exist then the daemon is obviously not running.
226  if (!is_file($pidFile))
227  {
228  return false;
229  }
230 
231  // Read the contents of the process id file as an integer.
232  $fp = fopen($pidFile, 'r');
233  $pid = fread($fp, filesize($pidFile));
234  $pid = (int) $pid;
235  fclose($fp);
236 
237  // Check to make sure that the process id exists as a positive integer.
238  if (!$pid)
239  {
240  return false;
241  }
242 
243  // Check to make sure the process is active by pinging it and ensure it responds.
244  if (!posix_kill($pid, 0))
245  {
246  // No response so remove the process id file and log the situation.
247  @ unlink($pidFile);
248  JLog::add('The process found based on PID file was unresponsive.', JLog::WARNING);
249 
250  return false;
251  }
252 
253  return true;
254  }
255 
256  /**
257  * Load an object or array into the application configuration object.
258  *
259  * @param mixed $data Either an array or object to be loaded into the configuration object.
260  *
261  * @return JCli Instance of $this to allow chaining.
262  *
263  * @since 11.1
264  */
265  public function loadConfiguration($data)
266  {
267  // Execute the parent load method.
268  parent::loadConfiguration($data);
269 
270  /*
271  * Setup some application metadata options. This is useful if we ever want to write out startup scripts
272  * or just have some sort of information available to share about things.
273  */
274 
275  // The application author name. This string is used in generating startup scripts and has
276  // a maximum of 50 characters.
277  $tmp = (string) $this->config->get('author_name', 'Joomla Platform');
278  $this->config->set('author_name', (strlen($tmp) > 50) ? substr($tmp, 0, 50) : $tmp);
279 
280  // The application author email. This string is used in generating startup scripts.
281  $tmp = (string) $this->config->get('author_email', 'admin@joomla.org');
282  $this->config->set('author_email', filter_var($tmp, FILTER_VALIDATE_EMAIL));
283 
284  // The application name. This string is used in generating startup scripts.
285  $tmp = (string) $this->config->get('application_name', 'JApplicationDaemon');
286  $this->config->set('application_name', (string) preg_replace('/[^A-Z0-9_-]/i', '', $tmp));
287 
288  // The application description. This string is used in generating startup scripts.
289  $tmp = (string) $this->config->get('application_description', 'A generic Joomla Platform application.');
290  $this->config->set('application_description', filter_var($tmp, FILTER_SANITIZE_STRING));
291 
292  /*
293  * Setup the application path options. This defines the default executable name, executable directory,
294  * and also the path to the daemon process id file.
295  */
296 
297  // The application executable daemon. This string is used in generating startup scripts.
298  $tmp = (string) $this->config->get('application_executable', basename($this->input->executable));
299  $this->config->set('application_executable', $tmp);
300 
301  // The home directory of the daemon.
302  $tmp = (string) $this->config->get('application_directory', dirname($this->input->executable));
303  $this->config->set('application_directory', $tmp);
304 
305  // The pid file location. This defaults to a path inside the /tmp directory.
306  $name = $this->config->get('application_name');
307  $tmp = (string) $this->config->get('application_pid_file', strtolower('/tmp/' . $name . '/' . $name . '.pid'));
308  $this->config->set('application_pid_file', $tmp);
309 
310  /*
311  * Setup the application identity options. It is important to remember if the default of 0 is set for
312  * either UID or GID then changing that setting will not be attempted as there is no real way to "change"
313  * the identity of a process from some user to root.
314  */
315 
316  // The user id under which to run the daemon.
317  $tmp = (int) $this->config->get('application_uid', 0);
318  $options = array('options' => array('min_range' => 0, 'max_range' => 65000));
319  $this->config->set('application_uid', filter_var($tmp, FILTER_VALIDATE_INT, $options));
320 
321  // The group id under which to run the daemon.
322  $tmp = (int) $this->config->get('application_gid', 0);
323  $options = array('options' => array('min_range' => 0, 'max_range' => 65000));
324  $this->config->set('application_gid', filter_var($tmp, FILTER_VALIDATE_INT, $options));
325 
326  // Option to kill the daemon if it cannot switch to the chosen identity.
327  $tmp = (bool) $this->config->get('application_require_identity', 1);
328  $this->config->set('application_require_identity', $tmp);
329 
330  /*
331  * Setup the application runtime options. By default our execution time limit is infinite obviously
332  * because a daemon should be constantly running unless told otherwise. The default limit for memory
333  * usage is 128M, which admittedly is a little high, but remember it is a "limit" and PHP's memory
334  * management leaves a bit to be desired :-)
335  */
336 
337  // The maximum execution time of the application in seconds. Zero is infinite.
338  $tmp = $this->config->get('max_execution_time');
339 
340  if ($tmp !== null)
341  {
342  $this->config->set('max_execution_time', (int) $tmp);
343  }
344 
345  // The maximum amount of memory the application can use.
346  $tmp = $this->config->get('max_memory_limit', '256M');
347 
348  if ($tmp !== null)
349  {
350  $this->config->set('max_memory_limit', (string) $tmp);
351  }
352 
353  return $this;
354  }
355 
356  /**
357  * Execute the daemon.
358  *
359  * @return void
360  *
361  * @since 11.1
362  */
363  public function execute()
364  {
365  // Trigger the onBeforeExecute event.
366  $this->triggerEvent('onBeforeExecute');
367 
368  // Enable basic garbage collection.
369  gc_enable();
370 
371  JLog::add('Starting ' . $this->name, JLog::INFO);
372 
373  // Set off the process for becoming a daemon.
374  if ($this->daemonize())
375  {
376  // Declare ticks to start signal monitoring. When you declare ticks, PCNTL will monitor
377  // incoming signals after each tick and call the relevant signal handler automatically.
378  declare (ticks = 1);
379 
380  // Start the main execution loop.
381  while (true)
382  {
383  // Perform basic garbage collection.
384  $this->gc();
385 
386  // Don't completely overload the CPU.
387  usleep(1000);
388 
389  // Execute the main application logic.
390  $this->doExecute();
391  }
392  }
393  // We were not able to daemonize the application so log the failure and die gracefully.
394  else
395  {
396  JLog::add('Starting ' . $this->name . ' failed', JLog::INFO);
397  }
398 
399  // Trigger the onAfterExecute event.
400  $this->triggerEvent('onAfterExecute');
401  }
402 
403  /**
404  * Restart daemon process.
405  *
406  * @return void
407  *
408  * @codeCoverageIgnore
409  * @since 11.1
410  */
411  public function restart()
412  {
413  JLog::add('Stopping ' . $this->name, JLog::INFO);
414  $this->shutdown(true);
415  }
416 
417  /**
418  * Stop daemon process.
419  *
420  * @return void
421  *
422  * @codeCoverageIgnore
423  * @since 11.1
424  */
425  public function stop()
426  {
427  JLog::add('Stopping ' . $this->name, JLog::INFO);
428  $this->shutdown();
429  }
430 
431  /**
432  * Method to change the identity of the daemon process and resources.
433  *
434  * @return boolean True if identity successfully changed
435  *
436  * @since 11.1
437  * @see posix_setuid()
438  */
439  protected function changeIdentity()
440  {
441  // Get the group and user ids to set for the daemon.
442  $uid = (int) $this->config->get('application_uid', 0);
443  $gid = (int) $this->config->get('application_gid', 0);
444 
445  // Get the application process id file path.
446  $file = $this->config->get('application_pid_file');
447 
448  // Change the user id for the process id file if necessary.
449  if ($uid && (fileowner($file) != $uid) && (!@ chown($file, $uid)))
450  {
451  JLog::add('Unable to change user ownership of the process id file.', JLog::ERROR);
452 
453  return false;
454  }
455 
456  // Change the group id for the process id file if necessary.
457  if ($gid && (filegroup($file) != $gid) && (!@ chgrp($file, $gid)))
458  {
459  JLog::add('Unable to change group ownership of the process id file.', JLog::ERROR);
460 
461  return false;
462  }
463 
464  // Set the correct home directory for the process.
465  if ($uid && ($info = posix_getpwuid($uid)) && is_dir($info['dir']))
466  {
467  system('export HOME="' . $info['dir'] . '"');
468  }
469 
470  // Change the user id for the process necessary.
471  if ($uid && (posix_getuid($file) != $uid) && (!@ posix_setuid($uid)))
472  {
473  JLog::add('Unable to change user ownership of the proccess.', JLog::ERROR);
474 
475  return false;
476  }
477 
478  // Change the group id for the process necessary.
479  if ($gid && (posix_getgid($file) != $gid) && (!@ posix_setgid($gid)))
480  {
481  JLog::add('Unable to change group ownership of the proccess.', JLog::ERROR);
482 
483  return false;
484  }
485 
486  // Get the user and group information based on uid and gid.
487  $user = posix_getpwuid($uid);
488  $group = posix_getgrgid($gid);
489 
490  JLog::add('Changed daemon identity to ' . $user['name'] . ':' . $group['name'], JLog::INFO);
491 
492  return true;
493  }
494 
495  /**
496  * Method to put the application into the background.
497  *
498  * @return boolean
499  *
500  * @since 11.1
501  * @throws RuntimeException
502  */
503  protected function daemonize()
504  {
505  // Is there already an active daemon running?
506  if ($this->isActive())
507  {
508  JLog::add($this->name . ' daemon is still running. Exiting the application.', JLog::EMERGENCY);
509 
510  return false;
511  }
512 
513  // Reset Process Information
514  $this->safeMode = !!@ ini_get('safe_mode');
515  $this->processId = 0;
516  $this->running = false;
517 
518  // Detach process!
519  try
520  {
521  // Check if we should run in the foreground.
522  if (!$this->input->get('f'))
523  {
524  // Detach from the terminal.
525  $this->detach();
526  }
527  else
528  {
529  // Setup running values.
530  $this->exiting = false;
531  $this->running = true;
532 
533  // Set the process id.
534  $this->processId = (int) posix_getpid();
535  $this->parentId = $this->processId;
536  }
537  }
538  catch (RuntimeException $e)
539  {
540  JLog::add('Unable to fork.', JLog::EMERGENCY);
541 
542  return false;
543  }
544 
545  // Verify the process id is valid.
546  if ($this->processId < 1)
547  {
548  JLog::add('The process id is invalid; the fork failed.', JLog::EMERGENCY);
549 
550  return false;
551  }
552 
553  // Clear the umask.
554  @ umask(0);
555 
556  // Write out the process id file for concurrency management.
557  if (!$this->writeProcessIdFile())
558  {
559  JLog::add('Unable to write the pid file at: ' . $this->config->get('application_pid_file'), JLog::EMERGENCY);
560 
561  return false;
562  }
563 
564  // Attempt to change the identity of user running the process.
565  if (!$this->changeIdentity())
566  {
567 
568  // If the identity change was required then we need to return false.
569  if ($this->config->get('application_require_identity'))
570  {
571  JLog::add('Unable to change process owner.', JLog::CRITICAL);
572 
573  return false;
574  }
575  else
576  {
577  JLog::add('Unable to change process owner.', JLog::WARNING);
578  }
579  }
580 
581  // Setup the signal handlers for the daemon.
582  if (!$this->setupSignalHandlers())
583  {
584  return false;
585  }
586 
587  // Change the current working directory to the application working directory.
588  @ chdir($this->config->get('application_directory'));
589 
590  return true;
591  }
592 
593  /**
594  * This is truly where the magic happens. This is where we fork the process and kill the parent
595  * process, which is essentially what turns the application into a daemon.
596  *
597  * @return void
598  *
599  * @since 12.1
600  * @throws RuntimeException
601  */
602  protected function detach()
603  {
604  JLog::add('Detaching the ' . $this->name . ' daemon.', JLog::DEBUG);
605 
606  // Attempt to fork the process.
607  $pid = $this->fork();
608 
609  // If the pid is positive then we successfully forked, and can close this application.
610  if ($pid)
611  {
612  // Add the log entry for debugging purposes and exit gracefully.
613  JLog::add('Ending ' . $this->name . ' parent process', JLog::DEBUG);
614  $this->close();
615  }
616  // We are in the forked child process.
617  else
618  {
619  // Setup some protected values.
620  $this->exiting = false;
621  $this->running = true;
622 
623  // Set the parent to self.
624  $this->parentId = $this->processId;
625  }
626  }
627 
628  /**
629  * Method to fork the process.
630  *
631  * @return integer The child process id to the parent process, zero to the child process.
632  *
633  * @since 11.1
634  * @throws RuntimeException
635  */
636  protected function fork()
637  {
638  // Attempt to fork the process.
639  $pid = $this->pcntlFork();
640 
641  // If the fork failed, throw an exception.
642  if ($pid === -1)
643  {
644  throw new RuntimeException('The process could not be forked.');
645  }
646  // Update the process id for the child.
647  elseif ($pid === 0)
648  {
649  $this->processId = (int) posix_getpid();
650  }
651  // Log the fork in the parent.
652  else
653  {
654  // Log the fork.
655  JLog::add('Process forked ' . $pid, JLog::DEBUG);
656  }
657 
658  // Trigger the onFork event.
659  $this->postFork();
660 
661  return $pid;
662  }
663 
664  /**
665  * Method to perform basic garbage collection and memory management in the sense of clearing the
666  * stat cache. We will probably call this method pretty regularly in our main loop.
667  *
668  * @return void
669  *
670  * @codeCoverageIgnore
671  * @since 11.1
672  */
673  protected function gc()
674  {
675  // Perform generic garbage collection.
676  gc_collect_cycles();
677 
678  // Clear the stat cache so it doesn't blow up memory.
679  clearstatcache();
680  }
681 
682  /**
683  * Method to attach the JApplicationDaemon signal handler to the known signals. Applications
684  * can override these handlers by using the pcntl_signal() function and attaching a different
685  * callback method.
686  *
687  * @return boolean
688  *
689  * @since 11.1
690  * @see pcntl_signal()
691  */
692  protected function setupSignalHandlers()
693  {
694  // We add the error suppression for the loop because on some platforms some constants are not defined.
695  foreach (self::$signals as $signal)
696  {
697  // Ignore signals that are not defined.
698  if (!defined($signal) || !is_int(constant($signal)) || (constant($signal) === 0))
699  {
700  // Define the signal to avoid notices.
701  JLog::add('Signal "' . $signal . '" not defined. Defining it as null.', JLog::DEBUG);
702  define($signal, null);
703 
704  // Don't listen for signal.
705  continue;
706  }
707 
708  // Attach the signal handler for the signal.
709  if (!$this->pcntlSignal(constant($signal), array('JApplicationDaemon', 'signal')))
710  {
711  JLog::add(sprintf('Unable to reroute signal handler: %s', $signal), JLog::EMERGENCY);
712 
713  return false;
714  }
715  }
716 
717  return true;
718  }
719 
720  /**
721  * Method to shut down the daemon and optionally restart it.
722  *
723  * @param boolean $restart True to restart the daemon on exit.
724  *
725  * @return void
726  *
727  * @since 11.1
728  */
729  protected function shutdown($restart = false)
730  {
731  // If we are already exiting, chill.
732  if ($this->exiting)
733  {
734  return;
735  }
736  // If not, now we are.
737  else
738  {
739  $this->exiting = true;
740  }
741 
742  // If we aren't already daemonized then just kill the application.
743  if (!$this->running && !$this->isActive())
744  {
745  JLog::add('Process was not daemonized yet, just halting current process', JLog::INFO);
746  $this->close();
747  }
748 
749  // Only read the pid for the parent file.
750  if ($this->parentId == $this->processId)
751  {
752  // Read the contents of the process id file as an integer.
753  $fp = fopen($this->config->get('application_pid_file'), 'r');
754  $pid = fread($fp, filesize($this->config->get('application_pid_file')));
755  $pid = (int) $pid;
756  fclose($fp);
757 
758  // Remove the process id file.
759  @ unlink($this->config->get('application_pid_file'));
760 
761  // If we are supposed to restart the daemon we need to execute the same command.
762  if ($restart)
763  {
764  $this->close(exec(implode(' ', $GLOBALS['argv']) . ' > /dev/null &'));
765  }
766  // If we are not supposed to restart the daemon let's just kill -9.
767  else
768  {
769  passthru('kill -9 ' . $pid);
770  $this->close();
771  }
772  }
773  }
774 
775  /**
776  * Method to write the process id file out to disk.
777  *
778  * @return boolean
779  *
780  * @since 11.1
781  */
782  protected function writeProcessIdFile()
783  {
784  // Verify the process id is valid.
785  if ($this->processId < 1)
786  {
787  JLog::add('The process id is invalid.', JLog::EMERGENCY);
788 
789  return false;
790  }
791 
792  // Get the application process id file path.
793  $file = $this->config->get('application_pid_file');
794 
795  if (empty($file))
796  {
797  JLog::add('The process id file path is empty.', JLog::ERROR);
798 
799  return false;
800  }
801 
802  // Make sure that the folder where we are writing the process id file exists.
803  $folder = dirname($file);
804 
805  if (!is_dir($folder) && !JFolder::create($folder))
806  {
807  JLog::add('Unable to create directory: ' . $folder, JLog::ERROR);
808 
809  return false;
810  }
811 
812  // Write the process id file out to disk.
813  if (!file_put_contents($file, $this->processId))
814  {
815  JLog::add('Unable to write proccess id file: ' . $file, JLog::ERROR);
816 
817  return false;
818  }
819 
820  // Make sure the permissions for the proccess id file are accurate.
821  if (!chmod($file, 0644))
822  {
823  JLog::add('Unable to adjust permissions for the proccess id file: ' . $file, JLog::ERROR);
824 
825  return false;
826  }
827 
828  return true;
829  }
830 
831  /**
832  * Method to handle post-fork triggering of the onFork event.
833  *
834  * @return void
835  *
836  * @since 12.1
837  */
838  protected function postFork()
839  {
840  // Trigger the onFork event.
841  $this->triggerEvent('onFork');
842  }
843 
844  /**
845  * Method to return the exit code of a terminated child process.
846  *
847  * @param integer $status The status parameter is the status parameter supplied to a successful call to pcntl_waitpid().
848  *
849  * @return integer The child process exit code.
850  *
851  * @codeCoverageIgnore
852  * @see pcntl_wexitstatus()
853  * @since 11.3
854  */
855  protected function pcntlChildExitStatus($status)
856  {
857  return pcntl_wexitstatus($status);
858  }
859 
860  /**
861  * Method to return the exit code of a terminated child process.
862  *
863  * @return integer On success, the PID of the child process is returned in the parent's thread
864  * of execution, and a 0 is returned in the child's thread of execution. On
865  * failure, a -1 will be returned in the parent's context, no child process
866  * will be created, and a PHP error is raised.
867  *
868  * @codeCoverageIgnore
869  * @see pcntl_fork()
870  * @since 11.3
871  */
872  protected function pcntlFork()
873  {
874  return pcntl_fork();
875  }
876 
877  /**
878  * Method to install a signal handler.
879  *
880  * @param integer $signal The signal number.
881  * @param callable $handler The signal handler which may be the name of a user created function,
882  * or method, or either of the two global constants SIG_IGN or SIG_DFL.
883  * @param boolean $restart Specifies whether system call restarting should be used when this
884  * signal arrives.
885  *
886  * @return boolean True on success.
887  *
888  * @codeCoverageIgnore
889  * @see pcntl_signal()
890  * @since 11.3
891  */
892  protected function pcntlSignal($signal , $handler, $restart = true)
893  {
894  return pcntl_signal($signal, $handler, $restart);
895  }
896 
897  /**
898  * Method to wait on or return the status of a forked child.
899  *
900  * @param integer &$status Status information.
901  * @param integer $options If wait3 is available on your system (mostly BSD-style systems),
902  * you can provide the optional options parameter.
903  *
904  * @return integer The process ID of the child which exited, -1 on error or zero if WNOHANG
905  * was provided as an option (on wait3-available systems) and no child was available.
906  *
907  * @codeCoverageIgnore
908  * @see pcntl_wait()
909  * @since 11.3
910  */
911  protected function pcntlWait(&$status, $options = 0)
912  {
913  return pcntl_wait($status, $options);
914  }
915 }