PHP--Fork
[ class tree: PHP--Fork ] [ index: PHP--Fork ] [ all elements ]

Source for file Fork.php

Documentation is available at Fork.php

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 - 5                                                       |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Author: Luca Mariano <luca.mariano@email.it>                         |
  17. // +----------------------------------------------------------------------+
  18. // $Id: Fork.php
  19.  
  20. /**
  21.  * Constant values to set type of method call we want to emulate.
  22.  *
  23.  * When calling a pseudo-thread method we try to emulate the behaviour of a real thread;
  24.  * we need to know if the method to emulate can return any value or is a void method.
  25.  *
  26.  */
  27. define ('PHP_FORK_VOID_METHOD',     -1);
  28. define ('PHP_FORK_RETURN_METHOD',     -2);
  29.  
  30. /**
  31.  * PHP_Fork class. Wrapper around the pcntl_fork() stuff
  32.  * with a API set like Java language.
  33.  * Practical usage is done by extending this class, and re-defining
  34.  * the run() method.
  35.  * Example:
  36.  * <code>
  37.  *  class executeThread extends PHP_Fork {
  38.  *     var $counter;
  39.  *
  40.  *     function executeThread($name)
  41.  *     {
  42.  *         $this->PHP_Fork($name);
  43.  *         $this->counter = 0;
  44.  *     }
  45.  *
  46.  *     function run()
  47.  *     {
  48.  *         $i = 0;
  49.  *         while ($i < 10) {
  50.  *             print time() . "-(" . $this->getName() . ")-" . $this->counter++ . "\n";
  51.  *             sleep(1);
  52.  *             $i++;
  53.  *         }
  54.  *     }
  55.  * }
  56.  * </code>
  57.  *
  58.  * This way PHP developers can enclose logic into a class that extends
  59.  * PHP_Fork, then execute the start() method that forks a child process.
  60.  * Communications with the forked process is ensured by using a Shared Memory
  61.  * Segment; by using a user-defined signal and this shared memory developers
  62.  * can access to child process methods that returns a serializable variable.
  63.  *
  64.  * The shared variable space can be accessed with the tho methods:
  65.  *
  66.  * o void setVariable($name, $value)
  67.  * o mixed getVariable($name)
  68.  *
  69.  * $name must be a valid PHP variable name;
  70.  * $value must be a variable or a serializable object.
  71.  * Resources (db connections, streams, etc.) cannot be serialized and so they're not correctly handled.
  72.  *
  73.  * Requires PHP build with --enable-cli --with-pcntl --enable-shmop.<br>
  74.  * Only runs on *NIX systems, because Windows lacks of the pcntl ext.
  75.  *
  76.  * @example simple_controller.php shows how to attach a controller to started pseudo-threads.
  77.  * @example exec_methods.php shows a workaround to execute methods into the child process.
  78.  * @example passing_vars.php shows variable exchange between the parent process and started pseudo-threads.
  79.  * @example basic.php a basic example, only two pseudo-threads that increment a counter simultaneously.
  80.  *
  81.  * @author Luca Mariano <luca.mariano@email.it>
  82.  * @version 1.0
  83.  * @package PHP::Fork
  84.  */
  85. class PHP_Fork {
  86.     /**
  87.      * The pseudo-thread name: must be unique between PHP processes
  88.      *
  89.      * @var string 
  90.      * @access private
  91.      */
  92.     var $_name;
  93.  
  94.     /**
  95.      * PID of the child process.
  96.      *
  97.      * @var integer 
  98.      * @access private
  99.      */
  100.     var $_pid;
  101.  
  102.     /**
  103.      * PUID of the child process owner; if you want to set this you must create and
  104.      * start() the pseudo-thread as root.
  105.      *
  106.      * @var integer 
  107.      * @access private
  108.      */
  109.     var $_puid;
  110.  
  111.     /**
  112.      * GUID of the child process owner; if you want to set this you must create and
  113.      * start() the pseudo-thread as root.
  114.      *
  115.      * @var integer 
  116.      * @access private
  117.      */
  118.     var $_guid;
  119.  
  120.     /**
  121.      * Are we into the child process?
  122.      *
  123.      * @var boolean 
  124.      * @access private
  125.      */
  126.     var $_isChild;
  127.  
  128.     /**
  129.      * A data structure to hold data for Inter Process Communications
  130.      *
  131.      * It's an associative array, some keys are reserved and cannot be used:
  132.      * _call_method, _call_input, _call_output, _call_type, _pingTime;
  133.      *
  134.      * @var array 
  135.      * @access private
  136.      */
  137.     var $_internal_ipc_array;
  138.  
  139.     /**
  140.      * KEY to access to Shared Memory Area.
  141.      *
  142.      * @var integer 
  143.      * @access private
  144.      */
  145.     var $_internal_ipc_key;
  146.  
  147.     /**
  148.      * KEY to access to Sync Semaphore.
  149.      *
  150.      * The semaphore is emulated with a boolean stored into a
  151.      * shared memory segment, because we don't want to add sem_*
  152.      * support to PHP interpreter.
  153.      *
  154.      * @var integer 
  155.      * @access private
  156.      */
  157.     var $_internal_sem_key;
  158.  
  159.     /**
  160.      * Is Shared Memory Area OK? If not, the start() method will block
  161.      * otherwise we'll have a running child without any communication channel.
  162.      *
  163.      * @var boolean 
  164.      * @access private
  165.      */
  166.     var $_ipc_is_ok;
  167.  
  168.     /**
  169.      * Whether the process is yet forked or not
  170.      *
  171.      * @var boolean 
  172.      * @access private
  173.      */
  174.     var $_running;
  175.  
  176.     /**
  177.      * PHP_Fork::PHP_Fork()
  178.      * Allocates a new pseudo-thread object and set its name to $name.
  179.      * Optionally, set a PUID, a GUID and a UMASK for the child process.
  180.      * This also initialize Shared Memory Segments for process communications.
  181.      *
  182.      * Supposing that you've defined the executeThread class as above,
  183.      * creating the pseudo-threads instances is very simple:
  184.      *
  185.      * <code>
  186.      *    ...
  187.      *    $executeThread1 = new executeThread ("executeThread-1");
  188.      *    $executeThread2 = new executeThread ("executeThread-2");
  189.      *  ...
  190.      * </code>
  191.      * The pseudo-thread name must be unique; you can create and start as many pseudo-threads as you want;
  192.      * the limit is (of course) system resources.
  193.      *
  194.      * @param string $name The name of this pseudo-thread; must be unique between PHP processes running on the same server.
  195.      * @param integer $puid Owner of the forked process; if none, the user will be the same that created the pseudo-thread
  196.      * @param integer $guid Group of the forked process; if none, the group will be the same of the user that created the pseudo-thread
  197.      * @param integer $umask the umask of the new process; if none, the umask will be the same of the user  that created the pseudo-thread
  198.      * @access public
  199.      * @return bool true if the Shared Memory Segments are OK, false otherwise.<br>Notice that only if shared mem is ok the process will be forked.
  200.      */
  201.     function PHP_Fork($name$puid = 0$guid = 0$umask = -1)
  202.     {
  203.         $this->_running = false;
  204.  
  205.         $this->_name $name;
  206.         $this->_guid $guid;
  207.         $this->_puid $puid;
  208.  
  209.         if ($umask != -1umask($umask);
  210.  
  211.         $this->_isChild = false;
  212.         $this->_internal_ipc_array = array();
  213.         // try to create the shared memory segment
  214.         // the variable $this->_ipc_is_ok contains the return code of this
  215.         // operation and MUST be checked before forking
  216.         if ($this->_createIPCsegment(&& $this->_createIPCsemaphore())
  217.             $this->_ipc_is_ok = true;
  218.         else
  219.             $this->_ipc_is_ok = false;
  220.     }
  221.  
  222.     /**
  223.      * PHP_Fork::isRunning()
  224.      * Test if the pseudo-thread is already started.
  225.      * A pseudo-thread that is instantiated but not started only exist as an instance of
  226.      * PHP_Fork class into parent process; no forking is done until the start() method
  227.      * is called.
  228.      *
  229.      * @return boolean true is the child is already forked.
  230.      */
  231.     function isRunning()
  232.     {
  233.         if ($this->_running)
  234.             return true;
  235.         else
  236.             return false;
  237.     }
  238.  
  239.     /**
  240.      * PHP_Fork::isActive()
  241.      *
  242.      * Check if the pseudo-thread is actually doing its job
  243.      * as defined into its run() method.
  244.      * This is set to FALSE before entering into the child run(),
  245.      * It become TRUE after run() exit.
  246.      *
  247.      * @return boolean true is the child is actually into its run() cycle
  248.      */    
  249.     function isActive()
  250.     {
  251.         return !$this->getVariable('_has_finished');
  252.     }
  253.     
  254.     /**
  255.      * PHP_Fork::setVariable()
  256.      *
  257.      * Set a variable into the shared memory segment so that it can accessed
  258.      * both from the parent & from the child process.
  259.      *
  260.      * @see PHP_Fork::getVariable()
  261.      */
  262.     function setVariable($name$value)
  263.     {
  264.         $this->_internal_ipc_array[$name$value;
  265.         $this->_writeToIPCsegment();
  266.     }
  267.  
  268.     /**
  269.      * PHP_Fork::getVariable()
  270.      *
  271.      * Get a variable from the shared memory segment
  272.      *
  273.      * @see PHP_Fork::setVariable()
  274.      * @return mixed the requested variable (or NULL if it doesn't exists).
  275.      */
  276.     function getVariable($name)
  277.     {
  278.         $this->_readFromIPCsegment();
  279.         return $this->_internal_ipc_array[$name];
  280.     }
  281.  
  282.     /**
  283.      * PHP_Fork::setAlive()
  284.      *
  285.      * Set a pseudo-thread property that can be read from parent process
  286.      * in order to know the child activity.
  287.      *
  288.      * Practical usage requires that child process calls this method at regular
  289.      * time intervals; parent will use the getLastAlive() method to know
  290.      * the elapsed time since the last pseudo-thread life signals...
  291.      *
  292.      * @see PHP_Fork::getLastAlive()
  293.      */
  294.     function setAlive()
  295.     {
  296.         $this->setVariable('_pingTime'time());
  297.     }
  298.  
  299.     /**
  300.      * PHP_Fork::getLastAlive()
  301.      *
  302.      * Read the time elapsed since the last child setAlive() call.
  303.      *
  304.      * This method is useful because often we have a pseudo-thread pool and we
  305.      * need to know each pseudo-thread status.
  306.      * if the child executes the setAlive() method, the parent with
  307.      * getLastAlive() can know that child is alive.
  308.      *
  309.      * @see PHP_Fork::setAlive()
  310.      * @return integer the elapsed seconds since the last child setAlive() call.
  311.      */
  312.     function getLastAlive()
  313.     {
  314.         $timestamp intval($this->getVariable('_pingTime'));
  315.         if ($timestamp == 0)
  316.             return 0;
  317.         else
  318.             return (time($timestamp);
  319.     }
  320.  
  321.     /**
  322.      * PHP_Fork::getName()
  323.      * Returns this pseudo-thread's name.
  324.      *
  325.      * @see PHP_Fork::setName()
  326.      * @return string the name of the pseudo-thread.
  327.      */
  328.  
  329.     function getName()
  330.     {
  331.         return $this->_name;
  332.     }
  333.  
  334.     /**
  335.      * PHP_Fork::getPid()
  336.      * Return the PID of the current pseudo-thread.
  337.      *
  338.      * @return integer the PID.
  339.      */
  340.  
  341.     function getPid()
  342.     {
  343.         return $this->_pid;
  344.     }
  345.  
  346.     /**
  347.      * PHP_Fork::register_callback_func()
  348.      *
  349.      * This is called from within the parent method; all the communication stuff is done here...
  350.      *
  351.      * @example exec_methods.php
  352.      * @param  $arglist 
  353.      * @param  $methodname 
  354.      */
  355.     function register_callback_func($arglist$methodname)
  356.     {
  357.         // this is the parent, so we really cannot execute the method...
  358.         // check arguments passed to the method...
  359.         if (is_array($arglist&& count ($arglist> 1{
  360.             if ($arglist[1== PHP_FORK_RETURN_METHOD)
  361.                 $this->_internal_ipc_array['_call_type'PHP_FORK_RETURN_METHOD;
  362.             else
  363.                 $this->_internal_ipc_array['_call_type'PHP_FORK_VOID_METHOD;
  364.         else $this->_internal_ipc_array['_call_type'PHP_FORK_VOID_METHOD;
  365.         // These setting are common to both the calling types
  366.         $this->_internal_ipc_array['_call_method'$methodname// '_call_method' is the name of the called method
  367.         $this->_internal_ipc_array['_call_input'$arglist// '_call_input' is the input array
  368.  
  369.         // Write the IPC data to the shared segment
  370.         $this->_writeToIPCsegment();
  371.         // Now we need to differentiate a bit...
  372.         switch ($this->_internal_ipc_array['_call_type']{
  373.             case PHP_FORK_VOID_METHOD:
  374.                 // notify the child so it can process the request
  375.                 $this->_sendSigUsr1();
  376.                 break;
  377.  
  378.             case PHP_FORK_RETURN_METHOD:
  379.                 // locks the pseudo-semaphore
  380.                 shmop_write($this->_internal_sem_key00);
  381.                 // notify the child so it can process the request
  382.                 $this->_sendSigUsr1();
  383.                 // blocks until the child process return
  384.                 $this->_waitIPCSemaphore();
  385.                 // read from the SHM segment...
  386.                 // the result is stored into $this->_internal_ipc_key['_call_output']
  387.                 $this->_readFromIPCsegment();
  388.                 // now return the result...
  389.                 return $this->_internal_ipc_array['_call_output'];
  390.                 break;
  391.         }
  392.     }
  393.  
  394.     /**
  395.      * PHP_Fork::run()
  396.      *
  397.      * This method actually implements the pseudo-thread logic.<BR>
  398.      * Subclasses of PHP_Fork MUST override this method as v.0.2
  399.      *
  400.      * @abstract
  401.      */
  402.  
  403.     function run()
  404.     {
  405.         die ("Fatal error: PHP_Fork class cannot be run by itself!\nPlease extend it and override the run() method");
  406.     }
  407.  
  408.     /**
  409.      * PHP_Fork::setName()
  410.      * Changes the name of this thread to the given name.
  411.      *
  412.      * @see PHP_Fork::getName()
  413.      * @param  $name 
  414.      */
  415.  
  416.     function setName($name)
  417.     {
  418.         $this->_name $name;
  419.     }
  420.  
  421.     /**
  422.      * PHP_Fork::start()
  423.      * Causes this pseudo-thread to begin parallel execution.
  424.      *
  425.      * <code>
  426.      *    ...
  427.      *    $executeThread1->start();
  428.      *  ...
  429.      * </code>
  430.      *
  431.      * This method check first of all the Shared Memory Segment; if ok, if forks
  432.      * the child process, attach signal handler and returns immediatly.
  433.      * The status is set to running, and a PID is assigned.
  434.      * The result is that two pseudo-threads are running concurrently: the current thread (which returns from the call to the start() method) and the other thread (which executes its run() method).
  435.      */
  436.  
  437.     function start()
  438.     {
  439.         if (!$this->_ipc_is_ok{
  440.              die ('Fatal error, unable to create SHM segments for process communications');
  441.              }
  442.     
  443.         pcntl_signal(SIGCHLDSIG_IGN);
  444. //     pcntl_signal(SIGALRM, SIG_IGN);
  445.     
  446.         $pid pcntl_fork();
  447.         if ($pid == 0{
  448.             // this is the child
  449.             $this->_isChild = true;
  450.             sleep(1);
  451.             
  452.             // install the signal handler
  453.             pcntl_signal(SIGUSR1array($this"_sig_handler"));
  454. /*        pcntl_signal(SIGALRM, array($this, "_sig_handler")); 
  455.         pcntl_alarm(1);                        */
  456.         
  457.             // if requested, change process identity
  458.             if ($this->_guid != 0)
  459.                 posix_setgid($this->_guid);
  460.  
  461.             if ($this->_puid != 0)
  462.                 posix_setuid($this->_puid);
  463.  
  464.         $this->setVariable('_has_finished'false);        
  465.                
  466.             $this->run();
  467.         
  468.         $this->setVariable('_has_finished'true);        
  469.             // Added 21/Oct/2003: destroy the child after run() execution
  470.             // needed to avoid unuseful child processes after execution
  471.             exit(0);
  472.         else {
  473.             // this is the parent
  474.             $this->_isChild = false;
  475.             $this->_running = true;
  476.             $this->_pid $pid;
  477.         }
  478.     }
  479.  
  480.     /**
  481.      * PHP_Fork::stop()
  482.      * Causes the current thread to die.
  483.      *
  484.      *
  485.      * <code>
  486.      *    ...
  487.      *    $executeThread1->stop();
  488.      *  ...
  489.      * </code>
  490.      *
  491.      * The relative process is killed and disappears immediately from the processes list.
  492.      *
  493.      * @return boolean true if the process is succesfully stopped, false otherwise.
  494.      */
  495.  
  496.     function stop()
  497.     {
  498.         $success = false;
  499.  
  500.         if ($this->_pid > 0{
  501.             posix_kill ($this->_pid9);
  502.             pcntl_waitpid ($this->_pid$temp = 0WNOHANG);
  503.             $success pcntl_wifexited ($temp;
  504.             $this->_cleanThreadContext();
  505.         }
  506.  
  507.         return $success;
  508.     }
  509.     // PRIVATE METHODS BEGIN
  510.     /**
  511.      * PHP_Fork::_cleanThreadContext()
  512.      *
  513.      * Internal method: destroy thread context and free relative resources.
  514.      *
  515.      * @access private
  516.      */
  517.  
  518.     function _cleanThreadContext()
  519.     {
  520.         @shmop_delete($this->_internal_ipc_key);
  521.         @shmop_delete($this->_internal_sem_key);        
  522.     @shmop_close($this->_internal_ipc_key);
  523.         @shmop_close($this->_internal_sem_key);
  524.         $this->_running = false;
  525.         unset($this->_pid);
  526.     }
  527.  
  528.     /**
  529.      * PHP_Fork::_sig_handler()
  530.      *
  531.      * This is the signal handler that make the communications between client and server possible.<BR>
  532.      * DO NOT override this method, otherwise the thread system will stop working...
  533.      *
  534.      * @param  $signo 
  535.      * @access private
  536.      */
  537.     function _sig_handler($signo)
  538.     {
  539.         switch ($signo{
  540.             case SIGTERM:
  541.                 // handle shutdown tasks
  542.                 exit;
  543.                 break;
  544.             case SIGHUP:
  545.                 // handle restart tasks
  546.                 break;
  547.             case SIGUSR1:
  548.                 // this is the User-defined signal we'll use.
  549.                 // read the SHM segment...
  550.                 $this->_readFromIPCsegment();
  551.  
  552.                 $method $this->_internal_ipc_array['_call_method'];
  553.                 $params $this->_internal_ipc_array['_call_input'];
  554.  
  555.                 switch ($this->_internal_ipc_array['_call_type']{
  556.                     case PHP_FORK_VOID_METHOD:
  557.                         // simple call the (void) method and return immediatly
  558.                         // no semaphore is placed into parent, so the processing is async
  559.                         $this->$method($params);
  560.                         break;
  561.  
  562.                     case PHP_FORK_RETURN_METHOD:
  563.                         // process the request...
  564.                         $this->_internal_ipc_array['_call_output'$this->$method($params);
  565.                         // write the result into IPC segment
  566.                         $this->_writeToIPCsegment();
  567.                         // unlock the semaphore
  568.                         shmop_write($this->_internal_sem_key10);
  569.                         break;
  570.                 }
  571.                 break;
  572.         case SIGALRM :
  573.             $this->_internal_ipc_array['_pingTime'time();
  574.                 $this->_writeToIPCsegment();    
  575.             //echo $this->getVariable('_pingTime');
  576.                                     
  577. //             pcntl_alarm(1);
  578.         break;            
  579.  
  580.             default:
  581.                 // handle all other signals
  582.         }
  583.     }
  584.  
  585.     /**
  586.      * PHP_Fork::_sendSigUsr1()
  587.      *
  588.      * Sends signal to the child process
  589.      *
  590.      * @access private
  591.      */
  592.     function _sendSigUsr1()
  593.     {
  594.         if ($this->_pid > 0)
  595.             posix_kill ($this->_pidSIGUSR1);
  596.     }
  597.  
  598.     /**
  599.      * PHP_Fork::_waitIPCSemaphore()
  600.      *
  601.      * @access private
  602.      */
  603.     function _waitIPCSemaphore()
  604.     {
  605.         while (true{
  606.             $ok shmop_read($this->_internal_sem_key01);
  607.  
  608.             if ($ok == 1)
  609.                 break;
  610.             else
  611.                 usleep(10);
  612.         }
  613.     }
  614.  
  615.     /**
  616.      * PHP_Fork::_readFromIPCsegment()
  617.      *
  618.      * @access private
  619.      */
  620.     function _readFromIPCsegment()
  621.     {
  622.         $serialized_IPC_array shmop_read($this->_internal_ipc_key0shmop_size($this->_internal_ipc_key));
  623.  
  624.         if (!$serialized_IPC_array)
  625.             print("Fatal exception reading SHM segment (shmop_read)\n");
  626.         // shmop_delete($this->_internal_ipc_key);
  627.         unset($this->_internal_ipc_array);
  628.         $this->_internal_ipc_array @unserialize($serialized_IPC_array);
  629.     }
  630.  
  631.     /**
  632.      * PHP_Fork::_writeToIPCsegment()
  633.      *
  634.      * @access private
  635.      */
  636.     function _writeToIPCsegment()
  637.     {
  638.         $serialized_IPC_array serialize($this->_internal_ipc_array);
  639.         // set the exchange array (IPC) into the shared segment
  640.         $shm_bytes_written shmop_write($this->_internal_ipc_key$serialized_IPC_array0);
  641.         // check if lenght of SHM segment is enougth to contain data...
  642.         if ($shm_bytes_written != strlen($serialized_IPC_array))
  643.             die("Fatal exception writing SHM segment (shmop_write)" strlen($serialized_IPC_array"-" shmop_size($this->_internal_ipc_key));
  644.     }
  645.  
  646.     /**
  647.      * PHP_Fork::_createIPCsegment()
  648.      *
  649.      * @return boolean true if the operation succeeded, false otherwise.
  650.      * @access private
  651.      */
  652.     function _createIPCsegment()
  653.     {
  654.         $tmp_file_key "/tmp/" md5($this->getName()) ".shm";
  655.         touch ($tmp_file_key);
  656.         $shm_key ftok($tmp_file_key'a');
  657.         if ($shm_key == -1)
  658.             die ("Fatal exception creating SHM segment (ftok)");
  659.  
  660.         $this->_internal_ipc_key @shmop_open($shm_key"c"06444096);
  661.         if (!$this->_internal_ipc_key{
  662.             return false;
  663.         }
  664.         return true;
  665.     }
  666.  
  667.     /**
  668.      * PHP_Fork::_createIPCsemaphore()
  669.      *
  670.      * @return boolean true if the operation succeeded, false otherwise.
  671.      * @access private
  672.      */
  673.     function _createIPCsemaphore()
  674.     {
  675.         $tmp_file_key "/tmp/" md5($this->getName()) ".sem";
  676.         touch ($tmp_file_key);
  677.         $sem_key ftok($tmp_file_key'a');
  678.         if ($sem_key == -1)
  679.             die ("Fatal exception creating semaphore (ftok)");
  680.         $this->_internal_sem_key @shmop_open($sem_key"c"064410);
  681.         if (!$this->_internal_sem_key{
  682.             return false;
  683.         }
  684.         return true;
  685.     }
  686. }

Documentation generated on Mon, 11 Mar 2019 15:41:27 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.