File: src/unreal4u/pid.php

Recommend this page to a friend!
  Classes of Camilo Sperberg  >  PID process check  >  src/unreal4u/pid.php  >  Download  
File: src/unreal4u/pid.php
Role: Class source
Content type: text/plain
Description: Class source
Class: PID process check
Check if a PHP script is running using PID files
Author: By
Last change: Support for infinite running jobs

Support for infinite running jobs
fix for CLI timeout setting

Fix for the follow code:
$this->pid = new unreal4u\pid(['directory' => 'data/', 'filename' => CURRENT_SITE_KEY.$prefix.$this->id, 'timeout' => 0]);

The timeout in this case was ignored :(
Date: 5 years ago
Size: 10,784 bytes
 

Contents

Class file image Download
<?php
namespace unreal4u;

include (dirname(__FILE__) . '/exceptions.php');

/**
 * Determines in any OS whether the script is already running or not
 *
 * @package pid
 * @author Camilo Sperberg - http://unreal4u.com/
 * @author http://www.electrictoolbox.com/check-php-script-already-running/
 * @version 2.0.4
 * @license BSD License. Feel free to modify
 */
class pid
{

    /**
     * The version of this class
     *
     * @var string
     */
    private $_version = '2.0.4';

    /**
     * The filename of the PID
     *
     * @var string
     */
    protected $_filename = '';

    /**
     * After how many time we can consider the pid file to be stalled
     *
     * @var int
     */
    protected $_timeout = 30;

    /**
     * The passed on parameters, defaults are in it as well
     *
     * @var array
     */
    private $_parameters = array(
        'directory' => '',
        'filename' => '',
        'timeout' => null,
        'checkOnConstructor' => true
    );

    /**
     * The original timeout, gets restored on the destructor
     *
     * @var int
     */
    private $_originalTimeout = 0;

    /**
     * Value of script already running or not
     *
     * @var boolean
     */
    public $alreadyRunning = false;

    /**
     * Contains the PID of the script
     *
     * @var integer $pid
     */
    public $pid = 0;

    /**
     * The main function that does it all
     *
     * Due to mayor BC break, this function receives two types of data. The old way, with 4 arguments and the new way,
     * passing along an array. This array will need the following convention, all arguments are optional:
     * array (
     *   'directory' => '',
     *   'filename' => '',
     *   'timeout' => 30,
     *   'checkOnConstructor' => true,
     * );
     *
     * The old way, AKA, with 4 arguments, will receive the
     *
     * @param mixed $directory
     *            Either an array with data or a string with location of PID directory, without trailing slash
     * @param string $filename
     *            The filename of the PID file
     * @param int $timeout
     *            The timeout for this script
     * @param boolean $checkOnConstructor
     *            Whether to check immediatly or save the effort for later
     */
    public function __construct($directory = '', $filename = '', $timeout = null, $checkOnConstructor = true)
    {
        // First argument can be an array
        $allowedValues = array(
            'directory',
            'filename',
            'timeout',
            'checkOnConstructor'
        );
        $this->_setParameters($allowedValues, func_get_args());

        if ($this->_parameters['checkOnConstructor'] === true) {
            $this->checkPid($this->_parameters);
        }
    }

    /**
     * Destroys the PID file
     */
    public function __destruct()
    {
        // Destruct PID only if we can and we are the current running script
        if (empty($this->alreadyRunning) && is_writable($this->_filename) && (int) file_get_contents($this->_filename) === $this->pid) {
            unlink($this->_filename);
        }
        ini_set('max_execution_time', $this->_originalTimeout);
    }

    /**
     * Magic toString method.
     * Will return current version of this class
     *
     * @return string
     */
    public function __toString()
    {
        return basename(__FILE__) . ' v' . $this->_version . ' by Camilo Sperberg - http://unreal4u.com/';
    }

    /**
     * Checks the parameters and saves them into our internal array
     *
     * @param array $validParameters
     * @param array $parameters
     * @return array
     */
    private function _setParameters(array $validParameters, array $parameters)
    {
        $j = count($validParameters);
        for ($i = 0; $i < $j; $i ++) {
            $validParameter = $validParameters[$i];
            if (isset($parameters[$i]) && !is_array($parameters[$i])) {
                $this->_parameters[$validParameter] = $parameters[$i];
            } elseif (isset($parameters[0]) && is_array($parameters[0]) && isset($parameters[0][$validParameter])) {
                /*
                 * @TODO A little note about the condition above:
                 * The above condition must comply with both conditions for PHP5.3. If you use PHP5.4+ it is sufficient
                 * to simply check with isset(), but in 5.3 this WILL fail some tests and even worse: it will kind of
                 * corrupt our $this->_parameters array.
                 * So, when we drop support for PHP5.3, change back this condition into a simple isset() instead of
                 * checking that we have a valid array first.
                 */
                $this->_parameters[$validParameter] = $parameters[0][$validParameter];
            }
        }

        return $this->_parameters;
    }

    /**
     * Verifies the PID on whatever system we may have, for now, only Windows and UNIX variants
     */
    private function _verifyPID()
    {
        $this->pid = (int) trim(file_get_contents($this->_filename));
        if (strtolower(substr(PHP_OS, 0, 3)) == 'win') {
            $this->_verifyPIDWindows();
        } else {
            $this->_verifyPIDUnix();
        }

        return $this->alreadyRunning;
    }

    /**
     * Windows' way of dealing with PIDs
     */
    private function _verifyPIDWindows()
    {
        $wmi = new \COM('winmgmts://');
        $processes = $wmi->ExecQuery('SELECT ProcessId FROM Win32_Process WHERE ProcessId = \'' . (int) $this->pid . '\'');
        if (count($processes) > 0) {
            $i = 0;
            foreach ($processes as $a) {
                $i ++;
            }

            if ($i > 0) {
                $this->alreadyRunning = true;
            }
        }

        return $this->alreadyRunning;
    }

    /**
     * Unix's way of dealing with PIDs
     */
    private function _verifyPIDUnix()
    {
        if (posix_kill($this->pid, 0)) {
            $this->alreadyRunning = true;
            if (! is_null($this->_timeout)) {
                $fileModificationTime = $this->getTimestampPidFile();
                if ($fileModificationTime + $this->_timeout < time() && $this->_timeout !== 0) {
                    $this->alreadyRunning = false;
                    unlink($this->_filename);
                }
            }
        }

        return $this->alreadyRunning;
    }

    /**
     * Sets the absolute directory name
     *
     * @return string
     */
    private function _setDirectory()
    {
        if (empty($this->_parameters['directory']) || ! is_string($this->_parameters['directory'])) {
            $this->_parameters['directory'] = sys_get_temp_dir();
        }

        $this->_parameters['directory'] = rtrim($this->_parameters['directory'], '/');
        return $this->_parameters['directory'];
    }

    /**
     * Sets the absolute filename
     *
     * @return string
     */
    private function _setFilename()
    {
        if (empty($this->_parameters['filename']) || ! is_string($this->_parameters['filename'])) {
            $this->_parameters['filename'] = basename($_SERVER['PHP_SELF']);
        }

        return $this->_parameters['filename'];
    }

    /**
     * Sets the internal PID name
     *
     * @return string
     */
    public function setFilename($directory = '', $filename = '')
    {
        $validParameters = array(
            'directory',
            'filename'
        );
        $this->_setParameters($validParameters, func_get_args());
        $this->_setDirectory();
        $this->_setFilename();
        $this->_filename = $this->_parameters['directory'] . '/' . $this->_parameters['filename'] . '.pid';

        return $this->_filename;
    }

    /**
     * Does the actual check
     *
     * @param string $directory
     *            The directory where the pid file is stored
     * @param string $filename
     *            Name of the pid file
     * @param int $timeout
     *            The time after which a pid file is considered "stalled"
     * @return int
     *            Returns the PID of the running process (or 1 in case of error)
     */
    public function checkPid($directory = '', $filename = '', $timeout = null)
    {
        // Why check twice when we can check only once?
        if (!empty($this->_parameters['checkOnConstructor'])) {
            $validParameters = array(
                'directory',
                'filename',
                'timeout'
            );
            $this->_setParameters($validParameters, func_get_args());

            $this->setFilename($this->_parameters);
            
            // if we give the timeout as CLI arg we should actually use it instead of ignoring it
            if ($timeout === null && isset($this->_parameters['timeout'])) {
				$timeout = $this->_parameters['timeout'];
			}
            $this->setTimeout($timeout);
        }

        if (is_writable($this->_filename) || is_writable(dirname($this->_filename))) {
            if (! file_exists($this->_filename) || ! $this->_verifyPID()) {
                $this->pid = getmypid();
                file_put_contents($this->_filename, $this->pid);
            }
        } else {
            throw new pidWriteException(sprintf('Cannot write to pid file "%s".', $this->_filename), 3);
        }

        return $this->pid;
    }

    /**
     * Gets the last modified date of the pid file
     *
     * @return int Returns the timestamp
     */
    public function getTimestampPidFile()
    {
        if (empty($this->pid) || !file_exists($this->_filename)) {
            throw new pidException(sprintf('Execute checkPid() function first'), 1);
        }

        return filemtime($this->_filename);
    }

    /**
     * Sets a timeout
     *
     * If a numeric value is provided, it will set it to that timeout. In other case, it will set it to current time
     * limit. This will however be 0 in CLI mode, so that value will defeat the purpose of this class entirely. In that
     * case, the script will set a default timeout time of 30 seconds.
     *
     * This method will also set a max_execution_time, why other reason should be set a timeout time otherwise?
     *
     * @param $ttl int
     * @return int Returns the timeout to what is was set
     */
    public function setTimeout($ttl = 30)
    {
        $this->_originalTimeout = ini_get('max_execution_time');
        if (is_numeric($ttl) && $ttl >= 0) {
            $this->_timeout = $ttl;
        } else {
            $this->_timeout = 30;
            if (! empty($this->_originalTimeout)) {
                $this->_timeout = $this->_originalTimeout;
            }
        }

        ini_set('max_execution_time', $this->_timeout);
        return $this->_timeout;
    }
}

For more information send a message to info at phpclasses dot org.