[ Index ] |
PHP Cross Reference of moodle-2.8 |
[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 require_once($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 $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); 89 90 if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10, 60)) { 91 throw new \moodle_exception('locktimeout'); 92 } 93 $tasks = self::load_default_scheduled_tasks_for_component($componentname); 94 95 $tasklocks = array(); 96 foreach ($tasks as $taskid => $task) { 97 $classname = get_class($task); 98 if (strpos($classname, '\\') !== 0) { 99 $classname = '\\' . $classname; 100 } 101 102 // If there is an existing task with a custom schedule, do not override it. 103 $currenttask = self::get_scheduled_task($classname); 104 if ($currenttask && $currenttask->is_customised()) { 105 $tasks[$taskid] = $currenttask; 106 } 107 108 if (!$lock = $cronlockfactory->get_lock($classname, 10, 60)) { 109 // Could not get all the locks required - release all locks and fail. 110 foreach ($tasklocks as $tasklock) { 111 $tasklock->release(); 112 } 113 $cronlock->release(); 114 throw new \moodle_exception('locktimeout'); 115 } 116 $tasklocks[] = $lock; 117 } 118 119 // Got a lock on cron and all the tasks for this component, time to reset the config. 120 $DB->delete_records('task_scheduled', array('component' => $componentname)); 121 foreach ($tasks as $task) { 122 $record = self::record_from_scheduled_task($task); 123 $DB->insert_record('task_scheduled', $record); 124 } 125 126 // Release the locks. 127 foreach ($tasklocks as $tasklock) { 128 $tasklock->release(); 129 } 130 131 $cronlock->release(); 132 } 133 134 /** 135 * Queue an adhoc task to run in the background. 136 * 137 * @param \core\task\adhoc_task $task - The new adhoc task information to store. 138 * @return boolean - True if the config was saved. 139 */ 140 public static function queue_adhoc_task(adhoc_task $task) { 141 global $DB; 142 143 $record = self::record_from_adhoc_task($task); 144 // Schedule it immediately. 145 $record->nextruntime = time() - 1; 146 $result = $DB->insert_record('task_adhoc', $record); 147 148 return $result; 149 } 150 151 /** 152 * Change the default configuration for a scheduled task. 153 * The list of scheduled tasks is taken from {@link load_scheduled_tasks_for_component}. 154 * 155 * @param \core\task\scheduled_task $task - The new scheduled task information to store. 156 * @return boolean - True if the config was saved. 157 */ 158 public static function configure_scheduled_task(scheduled_task $task) { 159 global $DB; 160 $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); 161 162 if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10, 60)) { 163 throw new \moodle_exception('locktimeout'); 164 } 165 166 $classname = get_class($task); 167 if (strpos($classname, '\\') !== 0) { 168 $classname = '\\' . $classname; 169 } 170 if (!$lock = $cronlockfactory->get_lock($classname, 10, 60)) { 171 $cronlock->release(); 172 throw new \moodle_exception('locktimeout'); 173 } 174 175 $original = $DB->get_record('task_scheduled', array('classname'=>$classname), 'id', MUST_EXIST); 176 177 $record = self::record_from_scheduled_task($task); 178 $record->id = $original->id; 179 $record->nextruntime = $task->get_next_scheduled_time(); 180 $result = $DB->update_record('task_scheduled', $record); 181 182 $lock->release(); 183 $cronlock->release(); 184 return $result; 185 } 186 187 /** 188 * Utility method to create a DB record from a scheduled task. 189 * 190 * @param \core\task\scheduled_task $task 191 * @return \stdClass 192 */ 193 public static function record_from_scheduled_task($task) { 194 $record = new \stdClass(); 195 $record->classname = get_class($task); 196 if (strpos($record->classname, '\\') !== 0) { 197 $record->classname = '\\' . $record->classname; 198 } 199 $record->component = $task->get_component(); 200 $record->blocking = $task->is_blocking(); 201 $record->customised = $task->is_customised(); 202 $record->lastruntime = $task->get_last_run_time(); 203 $record->nextruntime = $task->get_next_run_time(); 204 $record->faildelay = $task->get_fail_delay(); 205 $record->hour = $task->get_hour(); 206 $record->minute = $task->get_minute(); 207 $record->day = $task->get_day(); 208 $record->dayofweek = $task->get_day_of_week(); 209 $record->month = $task->get_month(); 210 $record->disabled = $task->get_disabled(); 211 212 return $record; 213 } 214 215 /** 216 * Utility method to create a DB record from an adhoc task. 217 * 218 * @param \core\task\adhoc_task $task 219 * @return \stdClass 220 */ 221 public static function record_from_adhoc_task($task) { 222 $record = new \stdClass(); 223 $record->classname = get_class($task); 224 if (strpos($record->classname, '\\') !== 0) { 225 $record->classname = '\\' . $record->classname; 226 } 227 $record->id = $task->get_id(); 228 $record->component = $task->get_component(); 229 $record->blocking = $task->is_blocking(); 230 $record->nextruntime = $task->get_next_run_time(); 231 $record->faildelay = $task->get_fail_delay(); 232 $record->customdata = $task->get_custom_data_as_string(); 233 234 return $record; 235 } 236 237 /** 238 * Utility method to create an adhoc task from a DB record. 239 * 240 * @param \stdClass $record 241 * @return \core\task\adhoc_task 242 */ 243 public static function adhoc_task_from_record($record) { 244 $classname = $record->classname; 245 if (strpos($classname, '\\') !== 0) { 246 $classname = '\\' . $classname; 247 } 248 if (!class_exists($classname)) { 249 debugging("Failed to load task: " . $classname, DEBUG_DEVELOPER); 250 return false; 251 } 252 $task = new $classname; 253 if (isset($record->nextruntime)) { 254 $task->set_next_run_time($record->nextruntime); 255 } 256 if (isset($record->id)) { 257 $task->set_id($record->id); 258 } 259 if (isset($record->component)) { 260 $task->set_component($record->component); 261 } 262 $task->set_blocking(!empty($record->blocking)); 263 if (isset($record->faildelay)) { 264 $task->set_fail_delay($record->faildelay); 265 } 266 if (isset($record->customdata)) { 267 $task->set_custom_data_as_string($record->customdata); 268 } 269 270 return $task; 271 } 272 273 /** 274 * Utility method to create a task from a DB record. 275 * 276 * @param \stdClass $record 277 * @return \core\task\scheduled_task 278 */ 279 public static function scheduled_task_from_record($record) { 280 $classname = $record->classname; 281 if (strpos($classname, '\\') !== 0) { 282 $classname = '\\' . $classname; 283 } 284 if (!class_exists($classname)) { 285 debugging("Failed to load task: " . $classname, DEBUG_DEVELOPER); 286 return false; 287 } 288 /** @var \core\task\scheduled_task $task */ 289 $task = new $classname; 290 if (isset($record->lastruntime)) { 291 $task->set_last_run_time($record->lastruntime); 292 } 293 if (isset($record->nextruntime)) { 294 $task->set_next_run_time($record->nextruntime); 295 } 296 if (isset($record->customised)) { 297 $task->set_customised($record->customised); 298 } 299 if (isset($record->component)) { 300 $task->set_component($record->component); 301 } 302 $task->set_blocking(!empty($record->blocking)); 303 if (isset($record->minute)) { 304 $task->set_minute($record->minute); 305 } 306 if (isset($record->hour)) { 307 $task->set_hour($record->hour); 308 } 309 if (isset($record->day)) { 310 $task->set_day($record->day); 311 } 312 if (isset($record->month)) { 313 $task->set_month($record->month); 314 } 315 if (isset($record->dayofweek)) { 316 $task->set_day_of_week($record->dayofweek); 317 } 318 if (isset($record->faildelay)) { 319 $task->set_fail_delay($record->faildelay); 320 } 321 if (isset($record->disabled)) { 322 $task->set_disabled($record->disabled); 323 } 324 325 return $task; 326 } 327 328 /** 329 * Given a component name, will load the list of tasks from the scheduled_tasks table for that component. 330 * Do not execute tasks loaded from this function - they have not been locked. 331 * @param string $componentname - The name of the component to load the tasks for. 332 * @return \core\task\scheduled_task[] 333 */ 334 public static function load_scheduled_tasks_for_component($componentname) { 335 global $DB; 336 337 $tasks = array(); 338 // We are just reading - so no locks required. 339 $records = $DB->get_records('task_scheduled', array('component' => $componentname), 'classname', '*', IGNORE_MISSING); 340 foreach ($records as $record) { 341 $task = self::scheduled_task_from_record($record); 342 // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled). 343 if ($task) { 344 $tasks[] = $task; 345 } 346 } 347 348 return $tasks; 349 } 350 351 /** 352 * This function load the scheduled task details for a given classname. 353 * 354 * @param string $classname 355 * @return \core\task\scheduled_task or false 356 */ 357 public static function get_scheduled_task($classname) { 358 global $DB; 359 360 if (strpos($classname, '\\') !== 0) { 361 $classname = '\\' . $classname; 362 } 363 // We are just reading - so no locks required. 364 $record = $DB->get_record('task_scheduled', array('classname'=>$classname), '*', IGNORE_MISSING); 365 if (!$record) { 366 return false; 367 } 368 return self::scheduled_task_from_record($record); 369 } 370 371 /** 372 * This function load the default scheduled task details for a given classname. 373 * 374 * @param string $classname 375 * @return \core\task\scheduled_task or false 376 */ 377 public static function get_default_scheduled_task($classname) { 378 $task = self::get_scheduled_task($classname); 379 $componenttasks = array(); 380 381 // Safety check in case no task was found for the given classname. 382 if ($task) { 383 $componenttasks = self::load_default_scheduled_tasks_for_component($task->get_component()); 384 } 385 386 foreach ($componenttasks as $componenttask) { 387 if (get_class($componenttask) == get_class($task)) { 388 return $componenttask; 389 } 390 } 391 392 return false; 393 } 394 395 /** 396 * This function will return a list of all the scheduled tasks that exist in the database. 397 * 398 * @return \core\task\scheduled_task[] 399 */ 400 public static function get_all_scheduled_tasks() { 401 global $DB; 402 403 $records = $DB->get_records('task_scheduled', null, 'component, classname', '*', IGNORE_MISSING); 404 $tasks = array(); 405 406 foreach ($records as $record) { 407 $task = self::scheduled_task_from_record($record); 408 // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled). 409 if ($task) { 410 $tasks[] = $task; 411 } 412 } 413 414 return $tasks; 415 } 416 417 /** 418 * This function will dispatch the next adhoc task in the queue. The task will be handed out 419 * with an open lock - possibly on the entire cron process. Make sure you call either 420 * {@link adhoc_task_failed} or {@link adhoc_task_complete} to release the lock and reschedule the task. 421 * 422 * @param int $timestart 423 * @return \core\task\adhoc_task or null if not found 424 */ 425 public static function get_next_adhoc_task($timestart) { 426 global $DB; 427 $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); 428 429 if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10)) { 430 throw new \moodle_exception('locktimeout'); 431 } 432 433 $where = '(nextruntime IS NULL OR nextruntime < :timestart1)'; 434 $params = array('timestart1' => $timestart); 435 $records = $DB->get_records_select('task_adhoc', $where, $params); 436 437 foreach ($records as $record) { 438 439 if ($lock = $cronlockfactory->get_lock('adhoc_' . $record->id, 10)) { 440 $classname = '\\' . $record->classname; 441 $task = self::adhoc_task_from_record($record); 442 // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled). 443 if (!$task) { 444 $lock->release(); 445 continue; 446 } 447 448 $task->set_lock($lock); 449 if (!$task->is_blocking()) { 450 $cronlock->release(); 451 } else { 452 $task->set_cron_lock($cronlock); 453 } 454 return $task; 455 } 456 } 457 458 // No tasks. 459 $cronlock->release(); 460 return null; 461 } 462 463 /** 464 * This function will dispatch the next scheduled task in the queue. The task will be handed out 465 * with an open lock - possibly on the entire cron process. Make sure you call either 466 * {@link scheduled_task_failed} or {@link scheduled_task_complete} to release the lock and reschedule the task. 467 * 468 * @param int $timestart - The start of the cron process - do not repeat any tasks that have been run more recently than this. 469 * @return \core\task\scheduled_task or null 470 */ 471 public static function get_next_scheduled_task($timestart) { 472 global $DB; 473 $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); 474 475 if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10)) { 476 throw new \moodle_exception('locktimeout'); 477 } 478 479 $where = "(lastruntime IS NULL OR lastruntime < :timestart1) 480 AND (nextruntime IS NULL OR nextruntime < :timestart2) 481 AND disabled = 0"; 482 $params = array('timestart1' => $timestart, 'timestart2' => $timestart); 483 $records = $DB->get_records_select('task_scheduled', $where, $params); 484 485 $pluginmanager = \core_plugin_manager::instance(); 486 487 foreach ($records as $record) { 488 489 if ($lock = $cronlockfactory->get_lock(($record->classname), 10)) { 490 $classname = '\\' . $record->classname; 491 $task = self::scheduled_task_from_record($record); 492 // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled). 493 if (!$task) { 494 $lock->release(); 495 continue; 496 } 497 498 $task->set_lock($lock); 499 500 // See if the component is disabled. 501 $plugininfo = $pluginmanager->get_plugin_info($task->get_component()); 502 503 if ($plugininfo) { 504 if (($plugininfo->is_enabled() === false) && !$task->get_run_if_component_disabled()) { 505 mtrace($task->get_name().' skipped - the component '.$task->get_component().' is disabled'); 506 $lock->release(); 507 continue; 508 } 509 } 510 511 if (!$task->is_blocking()) { 512 $cronlock->release(); 513 } else { 514 $task->set_cron_lock($cronlock); 515 } 516 return $task; 517 } 518 } 519 520 // No tasks. 521 $cronlock->release(); 522 return null; 523 } 524 525 /** 526 * This function indicates that an adhoc task was not completed successfully and should be retried. 527 * 528 * @param \core\task\adhoc_task $task 529 */ 530 public static function adhoc_task_failed(adhoc_task $task) { 531 global $DB; 532 $delay = $task->get_fail_delay(); 533 534 // Reschedule task with exponential fall off for failing tasks. 535 if (empty($delay)) { 536 $delay = 60; 537 } else { 538 $delay *= 2; 539 } 540 541 // Max of 24 hour delay. 542 if ($delay > 86400) { 543 $delay = 86400; 544 } 545 546 $classname = get_class($task); 547 if (strpos($classname, '\\') !== 0) { 548 $classname = '\\' . $classname; 549 } 550 551 $task->set_next_run_time(time() + $delay); 552 $task->set_fail_delay($delay); 553 $record = self::record_from_adhoc_task($task); 554 $DB->update_record('task_adhoc', $record); 555 556 if ($task->is_blocking()) { 557 $task->get_cron_lock()->release(); 558 } 559 $task->get_lock()->release(); 560 } 561 562 /** 563 * This function indicates that an adhoc task was completed successfully. 564 * 565 * @param \core\task\adhoc_task $task 566 */ 567 public static function adhoc_task_complete(adhoc_task $task) { 568 global $DB; 569 570 // Delete the adhoc task record - it is finished. 571 $DB->delete_records('task_adhoc', array('id' => $task->get_id())); 572 573 // Reschedule and then release the locks. 574 if ($task->is_blocking()) { 575 $task->get_cron_lock()->release(); 576 } 577 $task->get_lock()->release(); 578 } 579 580 /** 581 * This function indicates that a scheduled task was not completed successfully and should be retried. 582 * 583 * @param \core\task\scheduled_task $task 584 */ 585 public static function scheduled_task_failed(scheduled_task $task) { 586 global $DB; 587 588 $delay = $task->get_fail_delay(); 589 590 // Reschedule task with exponential fall off for failing tasks. 591 if (empty($delay)) { 592 $delay = 60; 593 } else { 594 $delay *= 2; 595 } 596 597 // Max of 24 hour delay. 598 if ($delay > 86400) { 599 $delay = 86400; 600 } 601 602 $classname = get_class($task); 603 if (strpos($classname, '\\') !== 0) { 604 $classname = '\\' . $classname; 605 } 606 607 $record = $DB->get_record('task_scheduled', array('classname' => $classname)); 608 $record->nextruntime = time() + $delay; 609 $record->faildelay = $delay; 610 $DB->update_record('task_scheduled', $record); 611 612 if ($task->is_blocking()) { 613 $task->get_cron_lock()->release(); 614 } 615 $task->get_lock()->release(); 616 } 617 618 /** 619 * This function indicates that a scheduled task was completed successfully and should be rescheduled. 620 * 621 * @param \core\task\scheduled_task $task 622 */ 623 public static function scheduled_task_complete(scheduled_task $task) { 624 global $DB; 625 626 $classname = get_class($task); 627 if (strpos($classname, '\\') !== 0) { 628 $classname = '\\' . $classname; 629 } 630 $record = $DB->get_record('task_scheduled', array('classname' => $classname)); 631 if ($record) { 632 $record->lastruntime = time(); 633 $record->faildelay = 0; 634 $record->nextruntime = $task->get_next_scheduled_time(); 635 636 $DB->update_record('task_scheduled', $record); 637 } 638 639 // Reschedule and then release the locks. 640 if ($task->is_blocking()) { 641 $task->get_cron_lock()->release(); 642 } 643 $task->get_lock()->release(); 644 } 645 646 /** 647 * This function is used to indicate that any long running cron processes should exit at the 648 * next opportunity and restart. This is because something (e.g. DB changes) has changed and 649 * the static caches may be stale. 650 */ 651 public static function clear_static_caches() { 652 global $DB; 653 // Do not use get/set config here because the caches cannot be relied on. 654 $record = $DB->get_record('config', array('name'=>'scheduledtaskreset')); 655 if ($record) { 656 $record->value = time(); 657 $DB->update_record('config', $record); 658 } else { 659 $record = new \stdClass(); 660 $record->name = 'scheduledtaskreset'; 661 $record->value = time(); 662 $DB->insert_record('config', $record); 663 } 664 } 665 666 /** 667 * Return true if the static caches have been cleared since $starttime. 668 * @param int $starttime The time this process started. 669 * @return boolean True if static caches need resetting. 670 */ 671 public static function static_caches_cleared_since($starttime) { 672 global $DB; 673 $record = $DB->get_record('config', array('name'=>'scheduledtaskreset')); 674 return $record && (intval($record->value) > $starttime); 675 } 676 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |