[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Scheduled and adhoc task management. 19 * 20 * @package core 21 * @category task 22 * @copyright 2013 Damyon Wiese 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 namespace core\task; 26 27 define('CORE_TASK_TASKS_FILENAME', 'db/tasks.php'); 28 /** 29 * Collection of task related methods. 30 * 31 * Some locking rules for this class: 32 * All changes to scheduled tasks must be protected with both - the global cron lock and the lock 33 * for the specific scheduled task (in that order). Locks must be released in the reverse order. 34 * @copyright 2013 Damyon Wiese 35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 36 */ 37 class manager { 38 39 /** 40 * Given a component name, will load the list of tasks in the db/tasks.php file for that component. 41 * 42 * @param string $componentname - The name of the component to fetch the tasks for. 43 * @return \core\task\scheduled_task[] - List of scheduled tasks for this component. 44 */ 45 public static function load_default_scheduled_tasks_for_component($componentname) { 46 $dir = \core_component::get_component_directory($componentname); 47 48 if (!$dir) { 49 return array(); 50 } 51 52 $file = $dir . '/' . CORE_TASK_TASKS_FILENAME; 53 if (!file_exists($file)) { 54 return array(); 55 } 56 57 $tasks = null; 58 include($file); 59 60 if (!isset($tasks)) { 61 return array(); 62 } 63 64 $scheduledtasks = array(); 65 66 foreach ($tasks as $task) { 67 $record = (object) $task; 68 $scheduledtask = self::scheduled_task_from_record($record); 69 // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled). 70 if ($scheduledtask) { 71 $scheduledtask->set_component($componentname); 72 $scheduledtasks[] = $scheduledtask; 73 } 74 } 75 76 return $scheduledtasks; 77 } 78 79 /** 80 * Update the database to contain a list of scheduled task for a component. 81 * The list of scheduled tasks is taken from @load_scheduled_tasks_for_component. 82 * Will throw exceptions for any errors. 83 * 84 * @param string $componentname - The frankenstyle component name. 85 */ 86 public static function reset_scheduled_tasks_for_component($componentname) { 87 global $DB; 88 $tasks = self::load_default_scheduled_tasks_for_component($componentname); 89 $validtasks = array(); 90 91 foreach ($tasks as $taskid => $task) { 92 $classname = get_class($task); 93 if (strpos($classname, '\\') !== 0) { 94 $classname = '\\' . $classname; 95 } 96 97 $validtasks[] = $classname; 98 99 if ($currenttask = self::get_scheduled_task($classname)) { 100 if ($currenttask->is_customised()) { 101 // If there is an existing task with a custom schedule, do not override it. 102 continue; 103 } 104 105 // Update the record from the default task data. 106 self::configure_scheduled_task($task); 107 } else { 108 // Ensure that the first run follows the schedule. 109 $task->set_next_run_time($task->get_next_scheduled_time()); 110 111 // Insert the new task in the database. 112 $record = self::record_from_scheduled_task($task); 113 $DB->insert_record('task_scheduled', $record); 114 } 115 } 116 117 // Delete any task that is not defined in the component any more. 118 $sql = "component = :component"; 119 $params = array('component' => $componentname); 120 if (!empty($validtasks)) { 121 list($insql, $inparams) = $DB->get_in_or_equal($validtasks, SQL_PARAMS_NAMED, 'param', false); 122 $sql .= ' AND classname ' . $insql; 123 $params = array_merge($params, $inparams); 124 } 125 $DB->delete_records_select('task_scheduled', $sql, $params); 126 } 127 128 /** 129 * Queue an adhoc task to run in the background. 130 * 131 * @param \core\task\adhoc_task $task - The new adhoc task information to store. 132 * @return boolean - True if the config was saved. 133 */ 134 public static function queue_adhoc_task(adhoc_task $task) { 135 global $DB; 136 137 $record = self::record_from_adhoc_task($task); 138 // Schedule it immediately if nextruntime not explicitly set. 139 if (!$task->get_next_run_time()) { 140 $record->nextruntime = time() - 1; 141 } 142 $result = $DB->insert_record('task_adhoc', $record); 143 144 return $result; 145 } 146 147 /** 148 * Change the default configuration for a scheduled task. 149 * The list of scheduled tasks is taken from {@link load_scheduled_tasks_for_component}. 150 * 151 * @param \core\task\scheduled_task $task - The new scheduled task information to store. 152 * @return boolean - True if the config was saved. 153 */ 154 public static function configure_scheduled_task(scheduled_task $task) { 155 global $DB; 156 157 $classname = get_class($task); 158 if (strpos($classname, '\\') !== 0) { 159 $classname = '\\' . $classname; 160 } 161 162 $original = $DB->get_record('task_scheduled', array('classname'=>$classname), 'id', MUST_EXIST); 163 164 $record = self::record_from_scheduled_task($task); 165 $record->id = $original->id; 166 $record->nextruntime = $task->get_next_scheduled_time(); 167 $result = $DB->update_record('task_scheduled', $record); 168 169 return $result; 170 } 171 172 /** 173 * Utility method to create a DB record from a scheduled task. 174 * 175 * @param \core\task\scheduled_task $task 176 * @return \stdClass 177 */ 178 public static function record_from_scheduled_task($task) { 179 $record = new \stdClass(); 180 $record->classname = get_class($task); 181 if (strpos($record->classname, '\\') !== 0) { 182 $record->classname = '\\' . $record->classname; 183 } 184 $record->component = $task->get_component(); 185 $record->blocking = $task->is_blocking(); 186 $record->customised = $task->is_customised(); 187 $record->lastruntime = $task->get_last_run_time(); 188 $record->nextruntime = $task->get_next_run_time(); 189 $record->faildelay = $task->get_fail_delay(); 190 $record->hour = $task->get_hour(); 191 $record->minute = $task->get_minute(); 192 $record->day = $task->get_day(); 193 $record->dayofweek = $task->get_day_of_week(); 194 $record->month = $task->get_month(); 195 $record->disabled = $task->get_disabled(); 196 197 return $record; 198 } 199 200 /** 201 * Utility method to create a DB record from an adhoc task. 202 * 203 * @param \core\task\adhoc_task $task 204 * @return \stdClass 205 */ 206 public static function record_from_adhoc_task($task) { 207 $record = new \stdClass(); 208 $record->classname = get_class($task); 209 if (strpos($record->classname, '\\') !== 0) { 210 $record->classname = '\\' . $record->classname; 211 } 212 $record->id = $task->get_id(); 213 $record->component = $task->get_component(); 214 $record->blocking = $task->is_blocking(); 215 $record->nextruntime = $task->get_next_run_time(); 216 $record->faildelay = $task->get_fail_delay(); 217 $record->customdata = $task->get_custom_data_as_string(); 218 219 return $record; 220 } 221 222 /** 223 * Utility method to create an adhoc task from a DB record. 224 * 225 * @param \stdClass $record 226 * @return \core\task\adhoc_task 227 */ 228 public static function adhoc_task_from_record($record) { 229 $classname = $record->classname; 230 if (strpos($classname, '\\') !== 0) { 231 $classname = '\\' . $classname; 232 } 233 if (!class_exists($classname)) { 234 debugging("Failed to load task: " . $classname, DEBUG_DEVELOPER); 235 return false; 236 } 237 $task = new $classname; 238 if (isset($record->nextruntime)) { 239 $task->set_next_run_time($record->nextruntime); 240 } 241 if (isset($record->id)) { 242 $task->set_id($record->id); 243 } 244 if (isset($record->component)) { 245 $task->set_component($record->component); 246 } 247 $task->set_blocking(!empty($record->blocking)); 248 if (isset($record->faildelay)) { 249 $task->set_fail_delay($record->faildelay); 250 } 251 if (isset($record->customdata)) { 252 $task->set_custom_data_as_string($record->customdata); 253 } 254 255 return $task; 256 } 257 258 /** 259 * Utility method to create a task from a DB record. 260 * 261 * @param \stdClass $record 262 * @return \core\task\scheduled_task 263 */ 264 public static function scheduled_task_from_record($record) { 265 $classname = $record->classname; 266 if (strpos($classname, '\\') !== 0) { 267 $classname = '\\' . $classname; 268 } 269 if (!class_exists($classname)) { 270 debugging("Failed to load task: " . $classname, DEBUG_DEVELOPER); 271 return false; 272 } 273 /** @var \core\task\scheduled_task $task */ 274 $task = new $classname; 275 if (isset($record->lastruntime)) { 276 $task->set_last_run_time($record->lastruntime); 277 } 278 if (isset($record->nextruntime)) { 279 $task->set_next_run_time($record->nextruntime); 280 } 281 if (isset($record->customised)) { 282 $task->set_customised($record->customised); 283 } 284 if (isset($record->component)) { 285 $task->set_component($record->component); 286 } 287 $task->set_blocking(!empty($record->blocking)); 288 if (isset($record->minute)) { 289 $task->set_minute($record->minute); 290 } 291 if (isset($record->hour)) { 292 $task->set_hour($record->hour); 293 } 294 if (isset($record->day)) { 295 $task->set_day($record->day); 296 } 297 if (isset($record->month)) { 298 $task->set_month($record->month); 299 } 300 if (isset($record->dayofweek)) { 301 $task->set_day_of_week($record->dayofweek); 302 } 303 if (isset($record->faildelay)) { 304 $task->set_fail_delay($record->faildelay); 305 } 306 if (isset($record->disabled)) { 307 $task->set_disabled($record->disabled); 308 } 309 310 return $task; 311 } 312 313 /** 314 * Given a component name, will load the list of tasks from the scheduled_tasks table for that component. 315 * Do not execute tasks loaded from this function - they have not been locked. 316 * @param string $componentname - The name of the component to load the tasks for. 317 * @return \core\task\scheduled_task[] 318 */ 319 public static function load_scheduled_tasks_for_component($componentname) { 320 global $DB; 321 322 $tasks = array(); 323 // We are just reading - so no locks required. 324 $records = $DB->get_records('task_scheduled', array('component' => $componentname), 'classname', '*', IGNORE_MISSING); 325 foreach ($records as $record) { 326 $task = self::scheduled_task_from_record($record); 327 // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled). 328 if ($task) { 329 $tasks[] = $task; 330 } 331 } 332 333 return $tasks; 334 } 335 336 /** 337 * This function load the scheduled task details for a given classname. 338 * 339 * @param string $classname 340 * @return \core\task\scheduled_task or false 341 */ 342 public static function get_scheduled_task($classname) { 343 global $DB; 344 345 if (strpos($classname, '\\') !== 0) { 346 $classname = '\\' . $classname; 347 } 348 // We are just reading - so no locks required. 349 $record = $DB->get_record('task_scheduled', array('classname'=>$classname), '*', IGNORE_MISSING); 350 if (!$record) { 351 return false; 352 } 353 return self::scheduled_task_from_record($record); 354 } 355 356 /** 357 * This function load the default scheduled task details for a given classname. 358 * 359 * @param string $classname 360 * @return \core\task\scheduled_task or false 361 */ 362 public static function get_default_scheduled_task($classname) { 363 $task = self::get_scheduled_task($classname); 364 $componenttasks = array(); 365 366 // Safety check in case no task was found for the given classname. 367 if ($task) { 368 $componenttasks = self::load_default_scheduled_tasks_for_component($task->get_component()); 369 } 370 371 foreach ($componenttasks as $componenttask) { 372 if (get_class($componenttask) == get_class($task)) { 373 return $componenttask; 374 } 375 } 376 377 return false; 378 } 379 380 /** 381 * This function will return a list of all the scheduled tasks that exist in the database. 382 * 383 * @return \core\task\scheduled_task[] 384 */ 385 public static function get_all_scheduled_tasks() { 386 global $DB; 387 388 $records = $DB->get_records('task_scheduled', null, 'component, classname', '*', IGNORE_MISSING); 389 $tasks = array(); 390 391 foreach ($records as $record) { 392 $task = self::scheduled_task_from_record($record); 393 // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled). 394 if ($task) { 395 $tasks[] = $task; 396 } 397 } 398 399 return $tasks; 400 } 401 402 /** 403 * This function will dispatch the next adhoc task in the queue. The task will be handed out 404 * with an open lock - possibly on the entire cron process. Make sure you call either 405 * {@link adhoc_task_failed} or {@link adhoc_task_complete} to release the lock and reschedule the task. 406 * 407 * @param int $timestart 408 * @return \core\task\adhoc_task or null if not found 409 */ 410 public static function get_next_adhoc_task($timestart) { 411 global $DB; 412 $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); 413 414 if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10)) { 415 throw new \moodle_exception('locktimeout'); 416 } 417 418 $where = '(nextruntime IS NULL OR nextruntime < :timestart1)'; 419 $params = array('timestart1' => $timestart); 420 $records = $DB->get_records_select('task_adhoc', $where, $params); 421 422 foreach ($records as $record) { 423 424 if ($lock = $cronlockfactory->get_lock('adhoc_' . $record->id, 10)) { 425 $classname = '\\' . $record->classname; 426 $task = self::adhoc_task_from_record($record); 427 // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled). 428 if (!$task) { 429 $lock->release(); 430 continue; 431 } 432 433 $task->set_lock($lock); 434 if (!$task->is_blocking()) { 435 $cronlock->release(); 436 } else { 437 $task->set_cron_lock($cronlock); 438 } 439 return $task; 440 } 441 } 442 443 // No tasks. 444 $cronlock->release(); 445 return null; 446 } 447 448 /** 449 * This function will dispatch the next scheduled task in the queue. The task will be handed out 450 * with an open lock - possibly on the entire cron process. Make sure you call either 451 * {@link scheduled_task_failed} or {@link scheduled_task_complete} to release the lock and reschedule the task. 452 * 453 * @param int $timestart - The start of the cron process - do not repeat any tasks that have been run more recently than this. 454 * @return \core\task\scheduled_task or null 455 */ 456 public static function get_next_scheduled_task($timestart) { 457 global $DB; 458 $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); 459 460 if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10)) { 461 throw new \moodle_exception('locktimeout'); 462 } 463 464 $where = "(lastruntime IS NULL OR lastruntime < :timestart1) 465 AND (nextruntime IS NULL OR nextruntime < :timestart2) 466 AND disabled = 0 467 ORDER BY lastruntime, id ASC"; 468 $params = array('timestart1' => $timestart, 'timestart2' => $timestart); 469 $records = $DB->get_records_select('task_scheduled', $where, $params); 470 471 $pluginmanager = \core_plugin_manager::instance(); 472 473 foreach ($records as $record) { 474 475 if ($lock = $cronlockfactory->get_lock(($record->classname), 10)) { 476 $classname = '\\' . $record->classname; 477 $task = self::scheduled_task_from_record($record); 478 // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled). 479 if (!$task) { 480 $lock->release(); 481 continue; 482 } 483 484 $task->set_lock($lock); 485 486 // See if the component is disabled. 487 $plugininfo = $pluginmanager->get_plugin_info($task->get_component()); 488 489 if ($plugininfo) { 490 if (($plugininfo->is_enabled() === false) && !$task->get_run_if_component_disabled()) { 491 $lock->release(); 492 continue; 493 } 494 } 495 496 // Make sure the task data is unchanged. 497 if (!$DB->record_exists('task_scheduled', (array) $record)) { 498 $lock->release(); 499 continue; 500 } 501 502 if (!$task->is_blocking()) { 503 $cronlock->release(); 504 } else { 505 $task->set_cron_lock($cronlock); 506 } 507 return $task; 508 } 509 } 510 511 // No tasks. 512 $cronlock->release(); 513 return null; 514 } 515 516 /** 517 * This function indicates that an adhoc task was not completed successfully and should be retried. 518 * 519 * @param \core\task\adhoc_task $task 520 */ 521 public static function adhoc_task_failed(adhoc_task $task) { 522 global $DB; 523 $delay = $task->get_fail_delay(); 524 525 // Reschedule task with exponential fall off for failing tasks. 526 if (empty($delay)) { 527 $delay = 60; 528 } else { 529 $delay *= 2; 530 } 531 532 // Max of 24 hour delay. 533 if ($delay > 86400) { 534 $delay = 86400; 535 } 536 537 $classname = get_class($task); 538 if (strpos($classname, '\\') !== 0) { 539 $classname = '\\' . $classname; 540 } 541 542 $task->set_next_run_time(time() + $delay); 543 $task->set_fail_delay($delay); 544 $record = self::record_from_adhoc_task($task); 545 $DB->update_record('task_adhoc', $record); 546 547 if ($task->is_blocking()) { 548 $task->get_cron_lock()->release(); 549 } 550 $task->get_lock()->release(); 551 } 552 553 /** 554 * This function indicates that an adhoc task was completed successfully. 555 * 556 * @param \core\task\adhoc_task $task 557 */ 558 public static function adhoc_task_complete(adhoc_task $task) { 559 global $DB; 560 561 // Delete the adhoc task record - it is finished. 562 $DB->delete_records('task_adhoc', array('id' => $task->get_id())); 563 564 // Reschedule and then release the locks. 565 if ($task->is_blocking()) { 566 $task->get_cron_lock()->release(); 567 } 568 $task->get_lock()->release(); 569 } 570 571 /** 572 * This function indicates that a scheduled task was not completed successfully and should be retried. 573 * 574 * @param \core\task\scheduled_task $task 575 */ 576 public static function scheduled_task_failed(scheduled_task $task) { 577 global $DB; 578 579 $delay = $task->get_fail_delay(); 580 581 // Reschedule task with exponential fall off for failing tasks. 582 if (empty($delay)) { 583 $delay = 60; 584 } else { 585 $delay *= 2; 586 } 587 588 // Max of 24 hour delay. 589 if ($delay > 86400) { 590 $delay = 86400; 591 } 592 593 $classname = get_class($task); 594 if (strpos($classname, '\\') !== 0) { 595 $classname = '\\' . $classname; 596 } 597 598 $record = $DB->get_record('task_scheduled', array('classname' => $classname)); 599 $record->nextruntime = time() + $delay; 600 $record->faildelay = $delay; 601 $DB->update_record('task_scheduled', $record); 602 603 if ($task->is_blocking()) { 604 $task->get_cron_lock()->release(); 605 } 606 $task->get_lock()->release(); 607 } 608 609 /** 610 * This function indicates that a scheduled task was completed successfully and should be rescheduled. 611 * 612 * @param \core\task\scheduled_task $task 613 */ 614 public static function scheduled_task_complete(scheduled_task $task) { 615 global $DB; 616 617 $classname = get_class($task); 618 if (strpos($classname, '\\') !== 0) { 619 $classname = '\\' . $classname; 620 } 621 $record = $DB->get_record('task_scheduled', array('classname' => $classname)); 622 if ($record) { 623 $record->lastruntime = time(); 624 $record->faildelay = 0; 625 $record->nextruntime = $task->get_next_scheduled_time(); 626 627 $DB->update_record('task_scheduled', $record); 628 } 629 630 // Reschedule and then release the locks. 631 if ($task->is_blocking()) { 632 $task->get_cron_lock()->release(); 633 } 634 $task->get_lock()->release(); 635 } 636 637 /** 638 * This function is used to indicate that any long running cron processes should exit at the 639 * next opportunity and restart. This is because something (e.g. DB changes) has changed and 640 * the static caches may be stale. 641 */ 642 public static function clear_static_caches() { 643 global $DB; 644 // Do not use get/set config here because the caches cannot be relied on. 645 $record = $DB->get_record('config', array('name'=>'scheduledtaskreset')); 646 if ($record) { 647 $record->value = time(); 648 $DB->update_record('config', $record); 649 } else { 650 $record = new \stdClass(); 651 $record->name = 'scheduledtaskreset'; 652 $record->value = time(); 653 $DB->insert_record('config', $record); 654 } 655 } 656 657 /** 658 * Return true if the static caches have been cleared since $starttime. 659 * @param int $starttime The time this process started. 660 * @return boolean True if static caches need resetting. 661 */ 662 public static function static_caches_cleared_since($starttime) { 663 global $DB; 664 $record = $DB->get_record('config', array('name'=>'scheduledtaskreset')); 665 return $record && (intval($record->value) > $starttime); 666 } 667 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |