| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\Scheduler; |
|
|
| use Piwik\Concurrency\Lock; |
| use Piwik\Piwik; |
| use Piwik\Timer; |
| use Piwik\Log\LoggerInterface; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class Scheduler |
| { |
| |
| |
| |
| |
| private $isRunningTask = false; |
|
|
| |
| |
| |
| |
| private $scheduleRetry = false; |
|
|
| |
| |
| |
| private $timetable; |
|
|
| |
| |
| |
| private $loader; |
|
|
| |
| |
| |
| private $logger; |
|
|
| |
| |
| |
| private $lock; |
|
|
| |
| |
| |
| private $hasReceivedAbortSignal = false; |
|
|
| public function __construct(TaskLoader $loader, LoggerInterface $logger, ScheduledTaskLock $lock) |
| { |
| $this->timetable = new Timetable(); |
| $this->loader = $loader; |
| $this->logger = $logger; |
| $this->lock = $lock; |
| } |
|
|
| public function handleSignal(int $signal): void |
| { |
| $this->hasReceivedAbortSignal = in_array($signal, [\SIGINT, \SIGTERM], true); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function run() |
| { |
| $tasks = $this->loader->loadTasks(); |
|
|
| $this->logger->debug('{count} scheduled tasks loaded', array('count' => count($tasks))); |
|
|
| |
| $this->timetable->removeInactiveTasks($tasks); |
|
|
| $this->logger->info("Starting Scheduled tasks... "); |
|
|
| |
| $executionResults = array(); |
| $readFromOption = true; |
| for ($priority = Task::HIGHEST_PRIORITY; $priority <= Task::LOWEST_PRIORITY; ++$priority) { |
| $this->logger->debug("Executing tasks with priority {priority}:", array('priority' => $priority)); |
|
|
| |
| foreach ($tasks as $task) { |
| if ($this->hasReceivedAbortSignal) { |
| $this->logger->info("Scheduler: Aborting due to received signal"); |
| return $executionResults; |
| } |
|
|
| |
| if ($task->getPriority() != $priority) { |
| continue; |
| } |
|
|
| $taskName = $task->getName(); |
|
|
| if (!$this->acquireLockForTask($taskName, $task->getTTL())) { |
| $this->logger->debug( |
| "Scheduler: '{task}' is currently executed by another process", |
| ['task' => $task->getName()] |
| ); |
| continue; |
| } |
|
|
| if ($readFromOption) { |
| |
| |
| |
| |
| $this->timetable->readFromOption(); |
| $readFromOption = false; |
| } |
|
|
| $shouldExecuteTask = $this->timetable->shouldExecuteTask($taskName); |
|
|
| if ($this->timetable->taskShouldBeRescheduled($taskName)) { |
| $readFromOption = true; |
| $rescheduledDate = $this->timetable->rescheduleTask($task); |
|
|
| $this->logger->debug("Task {task} is scheduled to run again for {date}.", array('task' => $taskName, 'date' => $rescheduledDate)); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| Piwik::postEvent('ScheduledTasks.shouldExecuteTask', array(&$shouldExecuteTask, $task)); |
|
|
| if ($shouldExecuteTask) { |
| $readFromOption = true; |
| $this->scheduleRetry = false; |
| $message = $this->executeTask($task); |
|
|
| |
| if ($this->scheduleRetry) { |
| if ($this->timetable->getRetryCount($taskName) == 3) { |
| |
| $this->timetable->clearRetryCount($taskName); |
|
|
| $this->logger->warning( |
| "Scheduler: '{task}' has already been retried three times, giving up", |
| ['task' => $taskName] |
| ); |
| } else { |
| $readFromOption = true; |
| $rescheduledDate = $this->timetable->rescheduleTaskAndRunInOneHour($task); |
| $this->timetable->incrementRetryCount($taskName); |
|
|
| $this->logger->info( |
| "Scheduler: '{task}' retry scheduled for {date}", |
| ['task' => $taskName, 'date' => $rescheduledDate] |
| ); |
| } |
| $this->scheduleRetry = false; |
| } else { |
| if ($this->timetable->getRetryCount($taskName) > 0) { |
| $this->timetable->clearRetryCount($taskName); |
| } |
| } |
|
|
| $executionResults[] = array('task' => $taskName, 'output' => $message); |
| } |
|
|
| $this->releaseLock(); |
| } |
| } |
|
|
| $this->logger->info("done"); |
|
|
| return $executionResults; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public function runTaskNow($taskName) |
| { |
| $tasks = $this->loader->loadTasks(); |
|
|
| foreach ($tasks as $task) { |
| if ($task->getName() === $taskName) { |
| if (!$this->acquireLockForTask($taskName, $task->getTTL())) { |
| return 'Execution skipped. Another process is currently executing this task.'; |
| } |
|
|
| $result = $this->executeTask($task); |
|
|
| $this->releaseLock(); |
|
|
| return $result; |
| } |
| } |
|
|
| throw new \InvalidArgumentException('Task ' . $taskName . ' not found'); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function rescheduleTask(Task $task) |
| { |
| $this->logger->debug('Rescheduling task {task}', array('task' => $task->getName())); |
|
|
| $this->timetable->rescheduleTask($task); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function rescheduleTaskAndRunTomorrow(Task $task) |
| { |
| $this->logger->debug('Rescheduling task and setting first run for tomorrow {task}', array('task' => $task->getName())); |
|
|
| $this->timetable->rescheduleTaskAndRunTomorrow($task); |
| } |
|
|
| |
| |
| |
| |
| |
| public function isRunningTask() |
| { |
| return $this->isRunningTask; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public function getScheduledTimeForMethod($className, $methodName, $methodParameter = null) |
| { |
| return $this->timetable->getScheduledTimeForMethod($className, $methodName, $methodParameter); |
| } |
|
|
| |
| |
| |
| |
| |
| public function getTaskList() |
| { |
| $tasks = $this->loader->loadTasks(); |
|
|
| return array_map(function (Task $task) { |
| return $task->getName(); |
| }, $tasks); |
| } |
|
|
| private function acquireLockForTask(string $taskName, int $ttlInSeconds): bool |
| { |
| if (-1 === $ttlInSeconds) { |
| |
| return true; |
| } |
|
|
| return $this->lock->acquireLock($taskName, $ttlInSeconds); |
| } |
|
|
| private function releaseLock() |
| { |
| $this->lock->unlock(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| private function executeTask($task) |
| { |
| $this->logger->info("Scheduler: executing task {taskName}...", array( |
| 'taskName' => $task->getName(), |
| )); |
|
|
| $this->isRunningTask = true; |
|
|
| $timer = new Timer(); |
|
|
| |
| |
| |
| |
| |
| Piwik::postEvent('ScheduledTasks.execute', array(&$task)); |
|
|
| try { |
| $callable = array($task->getObjectInstance(), $task->getMethodName()); |
| call_user_func($callable, $task->getMethodParameter()); |
| $message = $timer->__toString(); |
| } catch (\Exception $e) { |
| $this->logger->error( |
| "Scheduler: Error {errorMessage} for task '{task}'", |
| ['errorMessage' => $e->getMessage(), 'task' => $task->getName()] |
| ); |
| $message = 'ERROR: ' . $e->getMessage(); |
|
|
| |
| if ($e instanceof RetryableException) { |
| $this->scheduleRetry = true; |
| } |
| } |
|
|
| $this->isRunningTask = false; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| Piwik::postEvent('ScheduledTasks.execute.end', array(&$task)); |
|
|
| $this->logger->info("Scheduler: finished. {timeElapsed}", array( |
| 'timeElapsed' => $timer, |
| )); |
|
|
| return $message; |
| } |
| } |
|
|