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