wp = $wp; $this->daemon = $daemon; $this->wordpressTrigger = $wordpressTrigger; $this->cronHelper = $cronHelper; $this->remoteExecutorHandler = $remoteExecutorHandler; $this->actionScheduler = $actionScheduler; $this->loggerFactory = $loggerFactory; } public function init(): void { $this->wp->addAction(self::NAME, [$this, 'process']); $this->wp->addFilter('action_scheduler_maximum_execution_time_likely_to_be_exceeded', [$this, 'storeRemainingExecutionLimit'], 10, 5); } /** * Run daemon that processes scheduled tasks for limited time */ public function process(): void { $this->wp->addAction('action_scheduler_after_process_queue', [$this, 'afterProcess']); $this->wp->addAction('mailpoet_cron_get_execution_limit', [$this, 'getDaemonExecutionLimit']); $this->lastRunDuration = 0; $startTime = time(); $this->daemon->run($this->cronHelper->createDaemon($this->cronHelper->createToken())); $this->lastRunDuration = time() - $startTime; } /** * Callback for setting the remaining execution time for the cron daemon (MailPoet\Cron\Daemon) */ public function getDaemonExecutionLimit(): float { return $this->remainingExecutionLimit; } /** * After Action Scheduler finishes its work we need to check if there is more work and in case there is we trigger additional runner. */ public function afterProcess(): void { $hasJobsToDo = $this->wordpressTrigger->checkExecutionRequirements(); if (!$hasJobsToDo) { return; } // The $lastDurationWasTooShort check prevents scheduling the next immediate action in case the last run was suspiciously short. // If there was still some execution time left, the daemon should have been continued. $lastDurationWasTooShort = ($this->lastRunDuration < self::SHORT_DURATION_THRESHOLD) && ($this->remainingExecutionLimit > 0); if ($lastDurationWasTooShort) { $this->loggerFactory->getLogger(LoggerFactory::TOPIC_CRON)->info('Daemon run ended too early!', [ 'duration' => $this->lastRunDuration, 'remainingLimit' => $this->remainingExecutionLimit, ]); return; } $this->actionScheduler->scheduleImmediateSingleAction(self::NAME); // Chaining async requests can crash MySQL. A brief sleep call in PHP prevents that. // @see https://github.com/woocommerce/action-scheduler/blob/6633378283d33746eec7314586783f58deee5375/classes/ActionScheduler_AsyncRequest_QueueRunner.php#L91-L96 sleep(2); $this->remoteExecutorHandler->triggerExecutor(); } /** * This method is hooked into action_scheduler_maximum_execution_time_likely_to_be_exceeded * It checks how much execution time is left for the daemon to run */ public function storeRemainingExecutionLimit($likelyExceeded, $runner, $processedActions, $executionTime, $maxExecutionTime): bool { $newLimit = ($maxExecutionTime - $executionTime) - self::EXECUTION_LIMIT_MARGIN; $this->remainingExecutionLimit = max($newLimit, 0); return (bool)$likelyExceeded; } }