PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.0.3
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.0.3
5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Scheduler / Scheduler.php
matomo / app / core / Scheduler Last commit date
Schedule 2 years ago RetryableException.php 2 years ago Scheduler.php 2 years ago Task.php 2 years ago TaskLoader.php 2 years ago Timetable.php 2 years ago
Scheduler.php
285 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\Scheduler;
10
11 use Piwik\Piwik;
12 use Piwik\Timer;
13 use Piwik\Log\LoggerInterface;
14 /**
15 * Schedules task execution.
16 *
17 * A scheduled task is a callback that should be executed every so often (such as daily,
18 * weekly, monthly, etc.). They are registered by extending {@link \Piwik\Plugin\Tasks}.
19 *
20 * Tasks are executed when the `core:archive` command is executed.
21 *
22 * ### Examples
23 *
24 * **Scheduling a task**
25 *
26 * class Tasks extends \Piwik\Plugin\Tasks
27 * {
28 * public function schedule()
29 * {
30 * $this->hourly('myTask'); // myTask() will be executed once every hour
31 * }
32 * public function myTask()
33 * {
34 * // do something
35 * }
36 * }
37 *
38 * **Executing all pending tasks**
39 *
40 * $results = $scheduler->run();
41 * $task1Result = $results[0];
42 * $task1Name = $task1Result['task'];
43 * $task1Output = $task1Result['output'];
44 *
45 * echo "Executed task '$task1Name'. Task output:\n$task1Output";
46 */
47 class Scheduler
48 {
49 /**
50 * Is the scheduler running any task.
51 * @var bool
52 */
53 private $isRunningTask = false;
54 /**
55 * Should the last run task be scheduled for a retry
56 * @var bool
57 */
58 private $scheduleRetry = false;
59 /**
60 * @var Timetable
61 */
62 private $timetable;
63 /**
64 * @var TaskLoader
65 */
66 private $loader;
67 /**
68 * @var LoggerInterface
69 */
70 private $logger;
71 public function __construct(\Piwik\Scheduler\TaskLoader $loader, LoggerInterface $logger)
72 {
73 $this->timetable = new \Piwik\Scheduler\Timetable();
74 $this->loader = $loader;
75 $this->logger = $logger;
76 }
77 /**
78 * Executes tasks that are scheduled to run, then reschedules them.
79 *
80 * @return array An array describing the results of scheduled task execution. Each element
81 * in the array will have the following format:
82 *
83 * ```
84 * array(
85 * 'task' => 'task name',
86 * 'output' => '... task output ...'
87 * )
88 * ```
89 */
90 public function run()
91 {
92 $tasks = $this->loader->loadTasks();
93 $this->logger->debug('{count} scheduled tasks loaded', array('count' => count($tasks)));
94 // remove from timetable tasks that are not active anymore
95 $this->timetable->removeInactiveTasks($tasks);
96 $this->logger->info("Starting Scheduled tasks... ");
97 // for every priority level, starting with the highest and concluding with the lowest
98 $executionResults = array();
99 $readFromOption = true;
100 for ($priority = \Piwik\Scheduler\Task::HIGHEST_PRIORITY; $priority <= \Piwik\Scheduler\Task::LOWEST_PRIORITY; ++$priority) {
101 $this->logger->debug("Executing tasks with priority {priority}:", array('priority' => $priority));
102 // loop through each task
103 foreach ($tasks as $task) {
104 // if the task does not have the current priority level, don't execute it yet
105 if ($task->getPriority() != $priority) {
106 continue;
107 }
108 if ($readFromOption) {
109 // because other jobs might execute the scheduled tasks as well we have to read the up to date time table to not handle the same task twice
110 // ideally we would read from option every time but using $readFromOption as a minor performance tweak. There can be easily 100 tasks
111 // of which we only execute very few and it's unlikely that the timetable changes too much in between while iterating over the loop and triggering the event.
112 // this way we only read from option when we actually execute or reschedule a task as this can take a few seconds.
113 $this->timetable->readFromOption();
114 $readFromOption = false;
115 }
116 $taskName = $task->getName();
117 $shouldExecuteTask = $this->timetable->shouldExecuteTask($taskName);
118 if ($this->timetable->taskShouldBeRescheduled($taskName)) {
119 $readFromOption = true;
120 $rescheduledDate = $this->timetable->rescheduleTask($task);
121 $this->logger->debug("Task {task} is scheduled to run again for {date}.", array('task' => $taskName, 'date' => $rescheduledDate));
122 }
123 /**
124 * Triggered before a task is executed.
125 *
126 * A plugin can listen to it and modify whether a specific task should be executed or not. This way
127 * you can force certain tasks to be executed more often or for example to be never executed.
128 *
129 * @param bool &$shouldExecuteTask Decides whether the task will be executed.
130 * @param Task $task The task that is about to be executed.
131 */
132 Piwik::postEvent('ScheduledTasks.shouldExecuteTask', array(&$shouldExecuteTask, $task));
133 if ($shouldExecuteTask) {
134 $readFromOption = true;
135 $this->scheduleRetry = false;
136 $message = $this->executeTask($task);
137 // Task has thrown an exception and should be scheduled for a retry
138 if ($this->scheduleRetry) {
139 if ($this->timetable->getRetryCount($task->getName()) == 3) {
140 // Task has already been retried three times, give up
141 $this->timetable->clearRetryCount($task->getName());
142 $this->logger->warning("Scheduler: '{task}' has already been retried three times, giving up", ['task' => $task->getName()]);
143 } else {
144 $readFromOption = true;
145 $rescheduledDate = $this->timetable->rescheduleTaskAndRunInOneHour($task);
146 $this->timetable->incrementRetryCount($task->getName());
147 $this->logger->info("Scheduler: '{task}' retry scheduled for {date}", ['task' => $task->getName(), 'date' => $rescheduledDate]);
148 }
149 $this->scheduleRetry = false;
150 } else {
151 if ($this->timetable->getRetryCount($task->getName()) > 0) {
152 $this->timetable->clearRetryCount($task->getName());
153 }
154 }
155 $executionResults[] = array('task' => $taskName, 'output' => $message);
156 }
157 }
158 }
159 $this->logger->info("done");
160 return $executionResults;
161 }
162 /**
163 * Run a specific task now. Will ignore the schedule completely.
164 *
165 * @param string $taskName
166 * @return string Task output.
167 */
168 public function runTaskNow($taskName)
169 {
170 $tasks = $this->loader->loadTasks();
171 foreach ($tasks as $task) {
172 if ($task->getName() === $taskName) {
173 return $this->executeTask($task);
174 }
175 }
176 throw new \InvalidArgumentException('Task ' . $taskName . ' not found');
177 }
178 /**
179 * Determines a task's scheduled time and persists it, overwriting the previous scheduled time.
180 *
181 * Call this method if your task's scheduled time has changed due to, for example, an option that
182 * was changed.
183 *
184 * @param Task $task Describes the scheduled task being rescheduled.
185 * @api
186 */
187 public function rescheduleTask(\Piwik\Scheduler\Task $task)
188 {
189 $this->logger->debug('Rescheduling task {task}', array('task' => $task->getName()));
190 $this->timetable->rescheduleTask($task);
191 }
192 /**
193 * Determines a task's scheduled time and persists it, overwriting the previous scheduled time.
194 *
195 * Call this method if your task's scheduled time has changed due to, for example, an option that
196 * was changed.
197 *
198 * The task will be run the first time tomorrow.
199 *
200 * @param Task $task Describes the scheduled task being rescheduled.
201 * @api
202 */
203 public function rescheduleTaskAndRunTomorrow(\Piwik\Scheduler\Task $task)
204 {
205 $this->logger->debug('Rescheduling task and setting first run for tomorrow {task}', array('task' => $task->getName()));
206 $this->timetable->rescheduleTaskAndRunTomorrow($task);
207 }
208 /**
209 * Returns true if the scheduler is currently running a task.
210 *
211 * @return bool
212 */
213 public function isRunningTask()
214 {
215 return $this->isRunningTask;
216 }
217 /**
218 * Return the next scheduled time given the class and method names of a scheduled task.
219 *
220 * @param string $className The name of the class that contains the scheduled task method.
221 * @param string $methodName The name of the scheduled task method.
222 * @param string|null $methodParameter Optional method parameter.
223 * @return mixed int|bool The time in milliseconds when the scheduled task will be executed
224 * next or false if it is not scheduled to run.
225 */
226 public function getScheduledTimeForMethod($className, $methodName, $methodParameter = null)
227 {
228 return $this->timetable->getScheduledTimeForMethod($className, $methodName, $methodParameter);
229 }
230 /**
231 * Returns the list of the task names.
232 *
233 * @return string[]
234 */
235 public function getTaskList()
236 {
237 $tasks = $this->loader->loadTasks();
238 return array_map(function (\Piwik\Scheduler\Task $task) {
239 return $task->getName();
240 }, $tasks);
241 }
242 /**
243 * Executes the given task
244 *
245 * @param Task $task
246 * @return string
247 */
248 private function executeTask($task)
249 {
250 $this->logger->info("Scheduler: executing task {taskName}...", array('taskName' => $task->getName()));
251 $this->isRunningTask = true;
252 $timer = new Timer();
253 /**
254 * Triggered directly before a scheduled task is executed
255 *
256 * @param Task $task The task that is about to be executed
257 */
258 Piwik::postEvent('ScheduledTasks.execute', array(&$task));
259 try {
260 $callable = array($task->getObjectInstance(), $task->getMethodName());
261 call_user_func($callable, $task->getMethodParameter());
262 $message = $timer->__toString();
263 } catch (\Exception $e) {
264 $this->logger->error("Scheduler: Error {errorMessage} for task '{task}'", ['errorMessage' => $e->getMessage(), 'task' => $task->getName()]);
265 $message = 'ERROR: ' . $e->getMessage();
266 // If the task has indicated that retrying on exception is safe then flag for rescheduling
267 if ($e instanceof \Piwik\Scheduler\RetryableException) {
268 $this->scheduleRetry = true;
269 }
270 }
271 $this->isRunningTask = false;
272 /**
273 * Triggered after a scheduled task is successfully executed.
274 *
275 * You can use the event to execute for example another task whenever a specific task is executed or to clean up
276 * certain resources.
277 *
278 * @param Task $task The task that was just executed
279 */
280 Piwik::postEvent('ScheduledTasks.execute.end', array(&$task));
281 $this->logger->info("Scheduler: finished. {timeElapsed}", array('timeElapsed' => $timer));
282 return $message;
283 }
284 }
285