Запуск, прерывание, завершение и статус при длительном исполнении.

Discussion in 'PHP' started by ckpunmkug, 13 Oct 2018.

  1. ckpunmkug

    ckpunmkug Member

    Joined:
    20 Mar 2017
    Messages:
    72
    Likes Received:
    72
    Reputations:
    10
    Для программ типа массовой загрузки страниц, сканирование сети характерно долгое исполнение поставленной задачи. Во время работы таких программ хочется знать сколько осталось до окончания исполнения, иметь возможность прервать и запустить с прерванного места. Для реализации подобного функционала я смастерил класс Job.

    Принцип работы класса следующий: во время инициализации, классу Job передаётся указатель на инициализированный класс, который содержит public $job_array и public function jobAction($key, $value). Класс Job загружает состояние исполненния, переходит в массиве на ещё необработанный элемент и вызывает метод из переданного класса с текущим ключом и содержимым ячейки массива. Метод исполняется, возращает результат исполнения, результат помещается в массив состояния.
    Прерывание исполнения выполняется сигналом SIGINT (Ctrl-C).
    Job.php
    Code:
    <?php
    
    class Job
    {
        public $verbose = true;
        private $state = [];
        private $state_filename = '';
        //config for printStatus
        public $printStatus = [
            'size' => '6', // count down size in sprintf %0{$cdf}d
            'filename' => "php://stderr", // status output filename
        ];
    
    
        public function __construct(&$logger, &$class, string $state_filename)
        {
            $this->logger = &$logger;
            $this->class = &$class;
            $this->state_filename = $state_filename;
            $this->state = $this->getState();
            // Below need for normal exit if pressed Ctrl-C
            declare(ticks = 1);
            pcntl_signal(SIGINT, function($signo) {
                exit();
            });
        }
    
        public function __destruct()
        {
            if (!empty($this->state)) {
                $this->putState();
                $this->state = [];
                return true;
            } else {
                return false;
            }
        }
       
        public function do(string $job_description)
        {
            $count_down = count($this->class->job_array);
            foreach ($this->class->job_array as $key => $value) {
                if (key_exists($key, $this->state)) {
                    $count_down -= 1;
                    continue;
                }
                if ($this->verbose) {
                    $this->printStatus($job_description, $count_down, $key);
                }
                //$this->state[$key] = null;
                $this->state[$key] = $this->class->jobAction($key, $value);
                $count_down -= 1;
            }
            return true;
        }
       
        public function getState()
        {
            if (!file_exists($this->state_filename)) {
                return [];
            }
            if (filesize($this->state_filename) == 0) {
                return [];
            }
            $serialized = file_get_contents($this->state_filename);
            if (!is_string($serialized)) {
                $this->logger->error("can't get serialized state from file", [$this->state_filename]);
                return [];
            }
            $state = unserialize($serialized);
            if (!is_array($state)) {
                $this->logger->error("can't unserialize state", [$serialized]);
                return [];
            }
            return $state;
        }
       
        public function putState()
        {
            $serialized = serialize($this->state);
            if (!is_string($serialized)) {
                $this->logger->error("can't serialize state", [$this->state]);
                return false;
            }
            if (!is_int(file_put_contents($this->state_filename, $serialized))) {
                $this->logger->error("can't put serialized state to file", [$this->state_filename]);
                return false;
            }
            return true;
        }
       
        public function printStatus($job_description, $count_down, $key)
        {
            $string = sprintf(
                "\r{$job_description}:" .
                " Left: %0{$this->printStatus['size']}d" .
                " Key: {$key} ",
                $count_down
            );
            if (is_int(file_put_contents($this->printStatus['filename'], $string, FILE_APPEND))) {
                return true;
            } else {
                return false;
            }
        }
    }
    
    JobTest.php
    Code:
    <?php
    
    class JobTester
    {
        public $job_array = [];
       
        public function jobAction($key, $value)
        {
            sleep(1);
            if ($key == 0) {
                $this->job_array[$key] = 1;
            } else {
                $this->job_array[$key] = $key * $this->job_array[$key-1];
                if ($this->job_array[$key] === INF) {
                    xepHR();
                    $this->print_job_array();
                    die;
                }
            }
        }
       
        public $error = false;
        private $data_filename = '';
       
        public function __construct(&$logger, string $data_filename)
        {
            $this->logger = &$logger;
            $this->data_filename = $data_filename;
            $this->job_array = import($data_filename);
            if (!is_array($this->job_array)) {
                $this->logger->error("can't import data array", [$data_filename]);
                $this->job_array = [];
                $this->error = true;
                return false;
            }
            return true;
        }
       
        public function __destruct()
        {
            if (empty($this->job_array)) {
                return false;
            }
            if (!export($this->data_filename, $this->job_array)) {
                $this->logger->error("can't export data to file", [$this->data_filename]);
                return false;
            }
            $this->job_array = [];
            return true;
        }
           
        public function print_job_array()
        {
            foreach ($this->job_array as $key => $value) {
                echo "{$key}! = {$value}; ";
            }
        }
    }
    
    import_export.php
    Code:
    <?php
    
    /**
     * load and unserialize variable from file
     *
     * @param string $filename file with serialized content
     *
     * @return unserialized variable
     * @access public
     */
    function import(string $filename)
    {
        $serialized = file_get_contents($filename);
        if (!is_string($serialized)) {
            trigger_error("can't get serialized contents from file", E_USER_WARNING);
            return false;
        }
        $variable = unserialize($serialized);
        if($variable === false) {
            trigger_error("can't unserialize variable", E_USER_WARNING);
            return false;
        }
        return $variable;
    }
    
    /**
     * serialize and save variable to file
     *
     * @param string $filename file for serialized content
     * @param void $variable variable to serialize
     *
     * @return true if success false if fail
     * @access public
     */
    function export(string $filename, $variable)
    {
        $serialized = serialize($variable);
        if (!is_string($serialized)) {
            trigger_error("can't serialize variable", E_USER_WARNING);
            return false;
        }
        $return = file_put_contents($filename, $serialized);
        if(!is_int($return)) {
            trigger_error("can't put serialized contents to file", E_USER_WARNING);
            return false;
        }
        return true;
    }
    
    Logger.php
    Code:
    <?php
    
    class Logger
    {
        public function echo(string $message, array $context = [])
        {
            file_put_contents("php://stderr", "{$message}\n", FILE_APPEND);
            if (!empty($context)) {
                file_put_contents("php://stderr", var_export($context, true), FILE_APPEND);
            }
            return true;
        }
    
        public function error(string $message, array $context = [])
        {
            return $this->echo("Error: {$message}", $context);
        }
    }
    
    start.php
    Code:
    set_include_path(__DIR__);
    require_once('import_export.php');
    require_once('Logger.php');
    require_once('JobTester.php');
    require_once('Job.php');
    
    define('JOBTESTER_FN', './factorial.arr');
    define('STATE_FN', './state.arr');
    $logger = new Logger;
    
    if (!file_exists(JOBTESTER_FN)) {
        $array = [];
        for ($index = 0; $index < 0x100; $index++) {
            $array[$index] = 0;
        }
        if (!export(JOBTESTER_FN, $array)) {
            $logger->error("can't export empty factorial array", [JOBTESTER_FN]);
            exit(1);
        }
    }
    
    $job_tester = new JobTester($logger, JOBTESTER_FN);
    $job = new Job($logger, $job_tester, STATE_FN);
    
    if (@$argv[1] == 'print_data') {
        $job_tester->print_job_array();
        exit(0);
    }
    
    $job->do("Calculate factorial");
    
    У меня вылезла опечатка которая вызвала несуществующую функцию и привела к фатальному ерору, прога завершилась, а деструкты не выполнинились, на этот случай можно вставить следующий код:
    Code:
    register_shutdown_function(function (){ //EcJLu cJLy4uTcR xepHR
        global $job_tester, $job;
        $job_tester->__destruct();
        $job->__destruct();
    });