Source of file start_cron.php

Size: 9,444 Bytes - Last Modified: 2020-10-24T02:46:31+00:00

/home/travis/build/NextDom/nextdom-core/src/Api/start_cron.php

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
<?php

/* This file is part of Jeedom.
 *
 * Jeedom is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jeedom is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jeedom. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * Execute one or all tasks
 *
 * Usage :
 *  - start_cron.php [ cron_id=CRON_ID ]
 *
 * Without arguments, all tasks will be executed.
 *
 * Parameters :
 *  - CRON_ID : Id of the task to execute
 */

namespace NextDom;

use NextDom\Enums\CronState;
use NextDom\Exceptions\CoreException;
use NextDom\Helpers\LogHelper;
use NextDom\Helpers\NextDomHelper;
use NextDom\Helpers\ScriptHelper;
use NextDom\Helpers\Utils;
use NextDom\Managers\ConfigManager;
use NextDom\Managers\CronManager;
use NextDom\Model\Entity\Cron;

const MAX_DURATION_TIME = 59;
const GARBAGE_COLLECTOR_LIMIT = 30;

require_once __DIR__ . "/../../src/core.php";

ScriptHelper::cliOrCrash();
ScriptHelper::parseArgumentsToGET();

/**
 * Set cron in error and stop the execution of the script
 *
 * @param Cron $cron Cron object
 * @param string $msg Message to log
 * @param int $startTime Start time of the cron
 * @throws \Exception
 */
function setCronErrorAndDie($cron, $msg, $startTime)
{
    $cron->setState(CronState::NOT_FOUND);
    $cron->setPID();
    $cron->setCache('runtime', strtotime('now') - $startTime);
    LogHelper::addError('cron', __($msg) . $cron->getName());
    die();
}

/**
 * Set cron error on exception
 *
 * @param Cron $cron Cron object
 * @param \Exception $e Exception informations
 * @param string $logChannel Target log channel
 * @param int $startTime Start time of the cron
 * @throws \Exception
 */
function setCronErrorOnException($cron, $e, $logChannel, $startTime)
{
    $cron->setState('error');
    $cron->setPID('');
    $cron->setCache('runtime', strtotime('now') - $startTime);
    $logicalId = ConfigManager::genKey();
    if ($e->getCode() != 0) {
        $logicalId = $cron->getName() . '::' . $e->getCode();
    }
    echo __('common.error-b') . $cron->getName() . ' : ' . LogHelper::exception($e);
    LogHelper::addError($logChannel, __('scripts.error-on') . $cron->getName() . ' : ' . LogHelper::exception($e), $logicalId);
}

/**
 * Start cron where the target is a class method
 *
 * @param Cron $cron Cron object
 * @param array $option Execution option
 * @param int $startTime Start time.
 * @throws \Exception
 */
function startCronTargetMethod($cron, $option, $startTime)
{
    $classToCall = '';
    try {
        $classToCall = $cron->getClass();
        $methodToCall = $cron->getFunction();
        if (class_exists($classToCall) && method_exists($classToCall, $methodToCall)) {
            if ($cron->getDeamon() == 0) {
                if ($option !== null) {
                    $classToCall::$methodToCall($option);
                } else {
                    $classToCall::$methodToCall();
                }
            } else {
                $gc = 0;
                while (true) {
                    $cycleStartTime = Utils::getMicrotime();
                    if ($option !== null) {
                        $classToCall::$methodToCall($option);
                    } else {
                        $classToCall::$methodToCall();
                    }
                    $gc++;
                    if ($gc > GARBAGE_COLLECTOR_LIMIT) {
                        gc_collect_cycles();
                        $gc = 0;
                    }
                    if ($cron->getDeamonSleepTime() > 1) {
                        sleep($cron->getDeamonSleepTime());
                    } else {
                        $cycleDuration = Utils::getMicrotime() - $cycleStartTime;
                        if ($cycleDuration < $cron->getDeamonSleepTime()) {
                            usleep(round(($cron->getDeamonSleepTime() - $cycleDuration) * 1000000));
                        }
                    }
                }
            }
        } else {
            setCronErrorAndDie($cron, 'scripts.cron-class-or-function-not-found', $startTime);
        }
    } catch (\Exception $e) {
        setCronErrorOnException($cron, $e, $classToCall, $startTime);
    }
}

/**
 * Start cron where the target is a global function
 *
 * @param Cron $cron Cron object
 * @param array $option Execution option
 * @param int $startTime Start time.
 * @throws \Exception
 */
function startCronTargetFunction($cron, $option, $startTime)
{
    $functionToCall = '';
    try {
        $functionToCall = $cron->getFunction();
        if (function_exists($functionToCall)) {
            if ($cron->getDeamon() == 0) {
                if ($option !== null) {
                    $functionToCall($option);
                } else {
                    $functionToCall();
                }
            } else {
                $gc = 0;
                while (true) {
                    $cycleStartTime = Utils::getMicrotime();
                    if ($option !== null) {
                        $functionToCall($option);
                    } else {
                        $functionToCall();
                    }
                    $gc++;
                    if ($gc > GARBAGE_COLLECTOR_LIMIT) {
                        gc_collect_cycles();
                        $gc = 0;
                    }
                    $cycleDuration = Utils::getMicrotime() - $cycleStartTime;
                    if ($cron->getDeamonSleepTime() > 1) {
                        sleep($cron->getDeamonSleepTime());
                    } else {
                        if ($cycleDuration < $cron->getDeamonSleepTime()) {
                            usleep(round(($cron->getDeamonSleepTime() - $cycleDuration) * 1000000));
                        }
                    }
                }
            }
        } else {
            setCronErrorAndDie($cron, __('common.error-b') . __('common.not-found'), $startTime);
        }
    } catch (\Exception $e) {
        setCronErrorOnException($cron, $e, $functionToCall, $startTime);
    }
}

/**
 * Start single cron job
 *
 * @param int $cronId Id of the cron
 * @throws CoreException
 * @throws \ReflectionException
 */
function startSingleCron($cronId)
{
    if (NextDomHelper::isStarted() && ConfigManager::byKey('enableCron', 'core', 1, true) == 0) {
        die(__('scripts.cron-disable'));
    }
    $cron = CronManager::byId($cronId);
    if (!is_object($cron)) {
        die();
    }
    $datetime = date('Y-m-d H:i:s');
    $startTime = strtotime('now');

    $cron->setState('run');
    $cron->setPID(getmypid());
    $cron->setLastRun($datetime);
    $option = $cron->getOption();
    if ($cron->getClass() != '') {
        startCronTargetMethod($cron, $option, $startTime);
    } else {
        startCronTargetFunction($cron, $option, $startTime);
    }
    if ($cron->getOnce() == 1) {
        $cron->remove(false);
    } else {
        if ($cron->refresh()) {
            $cron->setState(CronState::STOP);
            $cron->setPID();
            $cron->setCache('runtime', strtotime('now') - $startTime);
        }
    }
}

/**
 * Start all crons jobs
 */
function startAllCrons()
{
    if (CronManager::jeeCronRun()) {
        die();
    }
    $started = NextDomHelper::isStarted();

    set_time_limit(MAX_DURATION_TIME);
    CronManager::setPidFile();

    if ($started && ConfigManager::byKey('enableCron', 'core', 1, true) == 0) {
        die(__('scripts.cron-disable'));
    }
    foreach (CronManager::all() as $cron) {
        try {
            if ($cron->getDeamon() == 1) {
                $cron->refresh();
                continue;
            }
            if (!$started && $cron->getClass() != 'nextdom' && $cron->getFunction() != 'cron') {
                continue;
            }
            if (!$cron->refresh()) {
                continue;
            }
            $duration = strtotime('now') - strtotime($cron->getLastRun());
            if ($cron->isEnabled() && $cron->getState() != CronState::RUN && $cron->getState() != CronState::STARTING && $cron->getState() != CronState::STOPPING) {
                if ($cron->isDue()) {
                    $cron->start();
                }
            }
            // Stop cron task if timeout is reached
            if ($cron->getState() == CronState::RUN && ($duration / 60) >= $cron->getTimeout()) {
                $cron->stop();
            }
            switch ($cron->getState()) {
                case CronState::STARTING:
                    $cron->run();
                    break;
                case CronState::STOPPING:
                    $cron->halt();
                    break;
            }
        } catch (\Exception $e) {
            if ($cron->getOnce() != 1) {
                $cron->setState('error');
                $cron->setPID('');
                LogHelper::addError('cron', __('common.error-b') . $cron->getName() . ' : ' . $e->getMessage());
            }
        }
    }
}

/**
 * Entry point
 */
$cronId = Utils::init('cron_id');
if ($cronId != '') {
    startSingleCron($cronId);
} else {
    startAllCrons();
}