PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.14.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.14.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 4 years ago RetryableException.php 4 years ago Scheduler.php 4 years ago Task.php 5 years ago TaskLoader.php 5 years ago Timetable.php 4 years ago
Scheduler.php
341 lines
1 <?php
2 /**
3 * Matomo - free/libre analytics platform
4 *
5 * @link https://matomo.org
6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
7 */
8
9 namespace Piwik\Scheduler;
10
11 use Piwik\Piwik;
12 use Piwik\Timer;
13 use Psr\Log\LoggerInterface;
14
15 /**
16 * Schedules task execution.
17 *
18 * A scheduled task is a callback that should be executed every so often (such as daily,
19 * weekly, monthly, etc.). They are registered by extending {@link \Piwik\Plugin\Tasks}.
20 *
21 * Tasks are executed when the `core:archive` command is executed.
22 *
23 * ### Examples
24 *
25 * **Scheduling a task**
26 *
27 * class Tasks extends \Piwik\Plugin\Tasks
28 * {
29 * public function schedule()
30 * {
31 * $this->hourly('myTask'); // myTask() will be executed once every hour
32 * }
33 * public function myTask()
34 * {
35 * // do something
36 * }
37 * }
38 *
39 * **Executing all pending tasks**
40 *
41 * $results = $scheduler->run();
42 * $task1Result = $results[0];
43 * $task1Name = $task1Result['task'];
44 * $task1Output = $task1Result['output'];
45 *
46 * echo "Executed task '$task1Name'. Task output:\n$task1Output";
47 */
48 class Scheduler
49 {
50 /**
51 * Is the scheduler running any task.
52 * @var bool
53 */
54 private $isRunningTask = false;
55
56 /**
57 * Should the last run task be scheduled for a retry
58 * @var bool
59 */
60 private $scheduleRetry = false;
61
62 /**
63 * @var Timetable
64 */
65 private $timetable;
66
67 /**
68 * @var TaskLoader
69 */
70 private $loader;
71
72 /**
73 * @var LoggerInterface
74 */
75 private $logger;
76
77 public function __construct(TaskLoader $loader, LoggerInterface $logger)
78 {
79 $this->timetable = new Timetable();
80 $this->loader = $loader;
81 $this->logger = $logger;
82 }
83
84 /**
85 * Executes tasks that are scheduled to run, then reschedules them.
86 *
87 * @return array An array describing the results of scheduled task execution. Each element
88 * in the array will have the following format:
89 *
90 * ```
91 * array(
92 * 'task' => 'task name',
93 * 'output' => '... task output ...'
94 * )
95 * ```
96 */
97 public function run()
98 {
99 $tasks = $this->loader->loadTasks();
100
101 $this->logger->debug('{count} scheduled tasks loaded', array('count' => count($tasks)));
102
103 // remove from timetable tasks that are not active anymore
104 $this->timetable->removeInactiveTasks($tasks);
105
106 $this->logger->info("Starting Scheduled tasks... ");
107
108 // for every priority level, starting with the highest and concluding with the lowest
109 $executionResults = array();
110 $readFromOption = true;
111 for ($priority = Task::HIGHEST_PRIORITY; $priority <= Task::LOWEST_PRIORITY; ++$priority) {
112 $this->logger->debug("Executing tasks with priority {priority}:", array('priority' => $priority));
113
114 // loop through each task
115 foreach ($tasks as $task) {
116 // if the task does not have the current priority level, don't execute it yet
117 if ($task->getPriority() != $priority) {
118 continue;
119 }
120
121 if ($readFromOption) {
122 // 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
123 // ideally we would read from option every time but using $readFromOption as a minor performance tweak. There can be easily 100 tasks
124 // 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.
125 // this way we only read from option when we actually execute or reschedule a task as this can take a few seconds.
126 $this->timetable->readFromOption();
127 $readFromOption = false;
128 }
129
130 $taskName = $task->getName();
131 $shouldExecuteTask = $this->timetable->shouldExecuteTask($taskName);
132
133 if ($this->timetable->taskShouldBeRescheduled($taskName)) {
134 $readFromOption = true;
135 $rescheduledDate = $this->timetable->rescheduleTask($task);
136
137 $this->logger->debug("Task {task} is scheduled to run again for {date}.", array('task' => $taskName, 'date' => $rescheduledDate));
138 }
139
140 /**
141 * Triggered before a task is executed.
142 *
143 * A plugin can listen to it and modify whether a specific task should be executed or not. This way
144 * you can force certain tasks to be executed more often or for example to be never executed.
145 *
146 * @param bool &$shouldExecuteTask Decides whether the task will be executed.
147 * @param Task $task The task that is about to be executed.
148 */
149 Piwik::postEvent('ScheduledTasks.shouldExecuteTask', array(&$shouldExecuteTask, $task));
150
151 if ($shouldExecuteTask) {
152 $readFromOption = true;
153 $this->scheduleRetry = false;
154 $message = $this->executeTask($task);
155
156 // Task has thrown an exception and should be scheduled for a retry
157 if ($this->scheduleRetry) {
158
159 if($this->timetable->getRetryCount($task->getName()) == 3) {
160
161 // Task has already been retried three times, give up
162 $this->timetable->clearRetryCount($task->getName());
163
164 $this->logger->warning("Scheduler: '{task}' has already been retried three times, giving up",
165 ['task' => $task->getName()]);
166
167 } else {
168
169 $readFromOption = true;
170 $rescheduledDate = $this->timetable->rescheduleTaskAndRunInOneHour($task);
171 $this->timetable->incrementRetryCount($task->getName());
172
173 $this->logger->info("Scheduler: '{task}' retry scheduled for {date}",
174 ['task' => $task->getName(), 'date' => $rescheduledDate]);
175 }
176 $this->scheduleRetry = false;
177 } else {
178 if ($this->timetable->getRetryCount($task->getName()) > 0) {
179 $this->timetable->clearRetryCount($task->getName());
180 }
181 }
182
183 $executionResults[] = array('task' => $taskName, 'output' => $message);
184 }
185 }
186 }
187
188 $this->logger->info("done");
189
190 return $executionResults;
191 }
192
193 /**
194 * Run a specific task now. Will ignore the schedule completely.
195 *
196 * @param string $taskName
197 * @return string Task output.
198 */
199 public function runTaskNow($taskName)
200 {
201 $tasks = $this->loader->loadTasks();
202
203 foreach ($tasks as $task) {
204 if ($task->getName() === $taskName) {
205 return $this->executeTask($task);
206 }
207 }
208
209 throw new \InvalidArgumentException('Task ' . $taskName . ' not found');
210 }
211
212 /**
213 * Determines a task's scheduled time and persists it, overwriting the previous scheduled time.
214 *
215 * Call this method if your task's scheduled time has changed due to, for example, an option that
216 * was changed.
217 *
218 * @param Task $task Describes the scheduled task being rescheduled.
219 * @api
220 */
221 public function rescheduleTask(Task $task)
222 {
223 $this->logger->debug('Rescheduling task {task}', array('task' => $task->getName()));
224
225 $this->timetable->rescheduleTask($task);
226 }
227
228 /**
229 * Determines a task's scheduled time and persists it, overwriting the previous scheduled time.
230 *
231 * Call this method if your task's scheduled time has changed due to, for example, an option that
232 * was changed.
233 *
234 * The task will be run the first time tomorrow.
235 *
236 * @param Task $task Describes the scheduled task being rescheduled.
237 * @api
238 */
239 public function rescheduleTaskAndRunTomorrow(Task $task)
240 {
241 $this->logger->debug('Rescheduling task and setting first run for tomorrow {task}', array('task' => $task->getName()));
242
243 $this->timetable->rescheduleTaskAndRunTomorrow($task);
244 }
245
246 /**
247 * Returns true if the scheduler is currently running a task.
248 *
249 * @return bool
250 */
251 public function isRunningTask()
252 {
253 return $this->isRunningTask;
254 }
255
256 /**
257 * Return the next scheduled time given the class and method names of a scheduled task.
258 *
259 * @param string $className The name of the class that contains the scheduled task method.
260 * @param string $methodName The name of the scheduled task method.
261 * @param string|null $methodParameter Optional method parameter.
262 * @return mixed int|bool The time in milliseconds when the scheduled task will be executed
263 * next or false if it is not scheduled to run.
264 */
265 public function getScheduledTimeForMethod($className, $methodName, $methodParameter = null)
266 {
267 return $this->timetable->getScheduledTimeForMethod($className, $methodName, $methodParameter);
268 }
269
270 /**
271 * Returns the list of the task names.
272 *
273 * @return string[]
274 */
275 public function getTaskList()
276 {
277 $tasks = $this->loader->loadTasks();
278
279 return array_map(function (Task $task) {
280 return $task->getName();
281 }, $tasks);
282 }
283
284 /**
285 * Executes the given task
286 *
287 * @param Task $task
288 * @return string
289 */
290 private function executeTask($task)
291 {
292 $this->logger->info("Scheduler: executing task {taskName}...", array(
293 'taskName' => $task->getName(),
294 ));
295
296 $this->isRunningTask = true;
297
298 $timer = new Timer();
299
300 /**
301 * Triggered directly before a scheduled task is executed
302 *
303 * @param Task $task The task that is about to be executed
304 */
305 Piwik::postEvent('ScheduledTasks.execute', array(&$task));
306
307 try {
308 $callable = array($task->getObjectInstance(), $task->getMethodName());
309 call_user_func($callable, $task->getMethodParameter());
310 $message = $timer->__toString();
311 } catch (\Exception $e) {
312 $this->logger->error("Scheduler: Error {errorMessage} for task '{task}'",
313 ['errorMessage' => $e->getMessage(), 'task' => $task->getName()]);
314 $message = 'ERROR: ' . $e->getMessage();
315
316 // If the task has indicated that retrying on exception is safe then flag for rescheduling
317 if ($e instanceof RetryableException) {
318 $this->scheduleRetry = true;
319 }
320 }
321
322 $this->isRunningTask = false;
323
324 /**
325 * Triggered after a scheduled task is successfully executed.
326 *
327 * You can use the event to execute for example another task whenever a specific task is executed or to clean up
328 * certain resources.
329 *
330 * @param Task $task The task that was just executed
331 */
332 Piwik::postEvent('ScheduledTasks.execute.end', array(&$task));
333
334 $this->logger->info("Scheduler: finished. {timeElapsed}", array(
335 'timeElapsed' => $timer,
336 ));
337
338 return $message;
339 }
340 }
341