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

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