3 # FILE: AFTaskManager.php 5 # Part of the ScoutLib application support library 6 # Copyright 2009-2018 Edward Almasy and Internet Scout Research Group 7 # http://scout.wisc.edu 16 # ---- PUBLIC INTERFACE -------------------------------------------------- 22 public function __construct()
24 # set up our internal environment 27 # load our settings from database 28 $this->LoadSettings();
54 public function QueueTask($Callback, $Parameters = NULL,
55 $Priority = self::PRIORITY_LOW, $Description =
"")
57 # make sure priority is within bounds 58 $Priority = min(self::PRIORITY_BACKGROUND,
59 max(self::PRIORITY_HIGH, $Priority));
61 # pack task info and write to database 62 if ($Parameters === NULL) { $Parameters = array(); }
63 $this->DB->Query(
"INSERT INTO TaskQueue" 64 .
" (Callback, Parameters, Priority, Description)" 65 .
" VALUES ('".addslashes(serialize($Callback)).
"', '" 66 .addslashes(serialize($Parameters)).
"', ".intval($Priority).
", '" 67 .addslashes($Description).
"')");
89 $Priority = self::PRIORITY_LOW, $Description =
"")
93 $QueryResult = $this->DB->Query(
"SELECT TaskId,Priority FROM TaskQueue" 94 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'" 95 .($Parameters ?
" AND Parameters = '" 96 .addslashes(serialize($Parameters)).
"'" :
""));
97 if ($QueryResult !== FALSE)
99 # make sure priority is within bounds 100 $Priority = min(self::PRIORITY_BACKGROUND,
101 max(self::PRIORITY_HIGH, $Priority));
103 $Record = $this->DB->FetchRow();
104 if ($Record[
"Priority"] > $Priority)
106 $this->DB->Query(
"UPDATE TaskQueue" 107 .
" SET Priority = ".intval($Priority)
108 .
" WHERE TaskId = ".intval($Record[
"TaskId"]));
115 $this->
QueueTask($Callback, $Parameters, $Priority, $Description);
131 $QueuedCount = $this->DB->Query(
132 "SELECT COUNT(*) AS FoundCount FROM TaskQueue" 133 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'" 134 .($Parameters ?
" AND Parameters = '" 135 .addslashes(serialize($Parameters)).
"'" :
""),
137 $RunningCount = $this->DB->Query(
138 "SELECT COUNT(*) AS FoundCount FROM RunningTasks" 139 .
" WHERE Callback = '".addslashes(serialize($Callback)).
"'" 140 .($Parameters ?
" AND Parameters = '" 141 .addslashes(serialize($Parameters)).
"'" :
""),
143 $FoundCount = $QueuedCount + $RunningCount;
144 return ($FoundCount ? TRUE : FALSE);
166 return $this->GetTaskList(
"SELECT * FROM TaskQueue" 167 .
" ORDER BY Priority, TaskId ", $Count, $Offset);
184 $Priority = NULL, $Description = NULL)
186 $Query =
"SELECT COUNT(*) AS TaskCount FROM TaskQueue";
188 if ($Callback !== NULL)
190 $Query .= $Sep.
" Callback = '".addslashes(serialize($Callback)).
"'";
193 if ($Parameters !== NULL)
195 $Query .= $Sep.
" Parameters = '".addslashes(serialize($Parameters)).
"'";
198 if ($Priority !== NULL)
200 $Query .= $Sep.
" Priority = ".intval($Priority);
203 if ($Description !== NULL)
205 $Query .= $Sep.
" Description = '".addslashes($Description).
"'";
207 return $this->DB->Query($Query,
"TaskCount");
219 return $this->GetTaskList(
"SELECT * FROM RunningTasks" 220 .
" WHERE StartedAt >= '".date(
"Y-m-d H:i:s",
221 (time() - $GLOBALS[
"AF"]->MaxExecutionTime())).
"'" 222 .
" ORDER BY StartedAt", $Count, $Offset);
231 return $this->DB->Query(
"SELECT COUNT(*) AS Count FROM RunningTasks" 232 .
" WHERE StartedAt >= '".date(
"Y-m-d H:i:s",
233 (time() - $GLOBALS[
"AF"]->MaxExecutionTime())).
"'",
246 return $this->GetTaskList(
"SELECT * FROM RunningTasks" 247 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
248 (time() - $GLOBALS[
"AF"]->MaxExecutionTime())).
"'" 249 .
" ORDER BY StartedAt", $Count, $Offset);
258 return $this->DB->Query(
"SELECT COUNT(*) AS Count FROM RunningTasks" 259 .
" WHERE StartedAt < '".date(
"Y-m-d H:i:s",
260 (time() - $GLOBALS[
"AF"]->MaxExecutionTime())).
"'",
271 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
272 $this->DB->Query(
"INSERT INTO TaskQueue" 273 .
" (Callback,Parameters,Priority,Description) " 274 .
"SELECT Callback, Parameters, Priority, Description" 275 .
" FROM RunningTasks WHERE TaskId = ".intval($TaskId));
276 if ($NewPriority !== NULL)
278 $NewTaskId = $this->DB->LastInsertId();
279 $this->DB->Query(
"UPDATE TaskQueue SET Priority = " 280 .intval($NewPriority)
281 .
" WHERE TaskId = ".intval($NewTaskId));
283 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
284 $this->DB->Query(
"UNLOCK TABLES");
305 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = ".intval($TaskId));
306 $TasksRemoved = $this->DB->NumRowsAffected();
307 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
308 $TasksRemoved += $this->DB->NumRowsAffected();
309 return $TasksRemoved;
321 # assume task will not be found 324 # look for task in task queue 325 $this->DB->Query(
"SELECT * FROM TaskQueue WHERE TaskId = ".intval($TaskId));
327 # if task was not found in queue 328 if (!$this->DB->NumRowsSelected())
330 # look for task in running task list 331 $this->DB->Query(
"SELECT * FROM RunningTasks WHERE TaskId = " 336 if ($this->DB->NumRowsSelected())
338 # if task was periodic 339 $Row = $this->DB->FetchRow();
340 if ($Row[
"Callback"] ==
341 serialize(array(
"ApplicationFramework",
"RunPeriodicEvent")))
343 # unpack periodic task callback 344 $WrappedCallback = unserialize($Row[
"Parameters"]);
345 $Task[
"Callback"] = $WrappedCallback[1];
346 $Task[
"Parameters"] = $WrappedCallback[2];
350 # unpack task callback and parameters 351 $Task[
"Callback"] = unserialize($Row[
"Callback"]);
352 $Task[
"Parameters"] = unserialize($Row[
"Parameters"]);
356 # return task to caller 373 return $this->UpdateSetting(__FUNCTION__, $NewValue, $Persistent);
386 return $this->UpdateSetting(
"MaxTasksRunning", $NewValue, $Persistent);
398 # if task callback is function use function name 399 $Callback = $TaskInfo[
"Callback"];
401 if (!is_array($Callback))
407 # if task callback is object 408 if (is_object($Callback[0]))
410 # if task callback is encapsulated ask encapsulation for name 411 if (method_exists($Callback[0],
"GetCallbackAsText"))
413 $Name = $Callback[0]->GetCallbackAsText();
415 # else assemble name from object 418 $Name = get_class($Callback[0]) .
"::" . $Callback[1];
421 # else assemble name from supplied info 424 $Name= $Callback[0] .
"::" . $Callback[1];
428 # if parameter array was supplied 429 $Parameters = $TaskInfo[
"Parameters"];
430 $ParameterString =
"";
431 if (is_array($Parameters))
433 # assemble parameter string 435 foreach ($Parameters as $Parameter)
437 $ParameterString .= $Separator;
438 if (is_int($Parameter) || is_float($Parameter))
440 $ParameterString .= $Parameter;
442 else if (is_string($Parameter))
444 $ParameterString .=
"\"".htmlspecialchars($Parameter).
"\"";
446 else if (is_array($Parameter))
448 $ParameterString .=
"ARRAY";
450 else if (is_object($Parameter))
452 $ParameterString .=
"OBJECT";
454 else if (is_null($Parameter))
456 $ParameterString .=
"NULL";
458 else if (is_bool($Parameter))
460 $ParameterString .= $Parameter ?
"TRUE" :
"FALSE";
462 else if (is_resource($Parameter))
464 $ParameterString .= get_resource_type($Parameter);
468 $ParameterString .=
"????";
474 # assemble name and parameters and return result to caller 475 return $Name.
"(".$ParameterString.
")";
485 return isset($this->RunningTask)
486 ? $this->RunningTask[
"Priority"] : NULL;
500 if ($Priority === NULL)
503 if ($Priority === NULL)
508 return ($Priority > self::PRIORITY_HIGH)
509 ? ($Priority - 1) : self::PRIORITY_HIGH;
523 if ($Priority === NULL)
526 if ($Priority === NULL)
531 return ($Priority < self::PRIORITY_BACKGROUND)
532 ? ($Priority + 1) : self::PRIORITY_BACKGROUND;
541 # if there are tasks in the queue 544 # tell PHP to garbage collect to give as much memory as possible for tasks 547 # turn on output buffering to (hopefully) record any crash output 550 # lock tables to prevent anyone else from running a task 551 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
553 # while there is time and memory left 555 # and an open slot to run it in 556 $MinimumTimeToRunAnotherTask = 65;
557 while (($GLOBALS[
"AF"]->GetSecondsBeforeTimeout()
558 > $MinimumTimeToRunAnotherTask)
560 > $this->BackgroundTaskMinFreeMemPercent)
564 # look for task at head of queue 565 $this->DB->Query(
"SELECT * FROM TaskQueue" 566 .
" ORDER BY Priority, TaskId LIMIT 1");
567 $Task = $this->DB->FetchRow();
569 # move task from queued list to running tasks list 570 $this->DB->Query(
"INSERT INTO RunningTasks " 571 .
"(TaskId,Callback,Parameters,Priority,Description) " 572 .
"SELECT * FROM TaskQueue WHERE TaskId = " 573 .intval($Task[
"TaskId"]));
574 $this->DB->Query(
"DELETE FROM TaskQueue WHERE TaskId = " 575 .intval($Task[
"TaskId"]));
577 # release table locks to again allow other sessions to run tasks 578 $this->DB->Query(
"UNLOCK TABLES");
580 # update the "last run" time 581 $this->DB->Query(
"UPDATE ApplicationFrameworkSettings" 582 .
" SET LastTaskRunAt = '".date(
"Y-m-d H:i:s").
"'");
585 $this->RunTask($Task);
587 # lock tables to prevent anyone else from running a task 588 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
591 $this->ResetTaskIdGeneratorIfNecessary();
593 # make sure tables are released 594 $this->DB->Query(
"UNLOCK TABLES");
599 # ---- PRIVATE INTERFACE ------------------------------------------------- 601 private $BackgroundTaskMemLeakLogThreshold = 10; # percentage of max mem
602 private $BackgroundTaskMinFreeMemPercent = 25;
604 private $MaxRunningTasksToTrack = 250;
605 private $RequeueCurrentTask;
606 private $RunningTask;
613 private $NoTSR = FALSE;
619 private function LoadSettings()
621 # read settings in from database 622 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
623 $this->Settings = $this->DB->FetchRow();
625 # if settings were not previously initialized 626 if ($this->Settings === FALSE)
628 # initialize settings in database 629 $this->DB->Query(
"INSERT INTO ApplicationFrameworkSettings" 630 .
" (LastTaskRunAt) VALUES ('2000-01-02 03:04:05')");
632 # read new settings in from database 633 $this->DB->Query(
"SELECT * FROM ApplicationFrameworkSettings");
634 $this->Settings = $this->DB->FetchRow();
636 # bail out if reloading new settings failed 637 if ($this->Settings === FALSE)
640 "Unable to load application framework settings.");
654 private function GetTaskList($DBQuery, $Count, $Offset)
656 $this->DB->Query($DBQuery.
" LIMIT ".intval($Offset).
",".intval($Count));
658 while ($Row = $this->DB->FetchRow())
660 $Tasks[$Row[
"TaskId"]] = $Row;
661 if ($Row[
"Callback"] ==
662 serialize(array(
"ApplicationFramework",
"RunPeriodicEvent")))
664 $WrappedCallback = unserialize($Row[
"Parameters"]);
665 $Tasks[$Row[
"TaskId"]][
"Callback"] = $WrappedCallback[1];
666 $Tasks[$Row[
"TaskId"]][
"Parameters"] = NULL;
670 $Tasks[$Row[
"TaskId"]][
"Callback"] = unserialize($Row[
"Callback"]);
671 $Tasks[$Row[
"TaskId"]][
"Parameters"] = unserialize($Row[
"Parameters"]);
681 private function RunTask($Task)
683 # unpack stored task info 684 $TaskId = $Task[
"TaskId"];
685 $Callback = unserialize($Task[
"Callback"]);
686 $Parameters = unserialize($Task[
"Parameters"]);
688 # attempt to load task callback if not already available 689 $GLOBALS[
"AF"]->LoadFunction($Callback);
691 # clear task requeue flag 694 # save amount of free memory for later comparison 698 $this->RunningTask = $Task;
701 call_user_func_array($Callback, $Parameters);
705 call_user_func($Callback);
707 unset($this->RunningTask);
709 # log if task leaked significant memory 710 $this->LogTaskMemoryLeakIfAny($TaskId, $BeforeFreeMem);
712 # if task requeue requested 715 # move task from running tasks list to queue 716 $this->RequeueRunningTask($TaskId);
720 # remove task from running tasks list 721 $this->DB->Query(
"DELETE FROM RunningTasks" 722 .
" WHERE TaskId = ".intval($TaskId));
725 # prune running tasks list if necessary 726 $RunningTasksCount = $this->DB->Query(
727 "SELECT COUNT(*) AS TaskCount FROM RunningTasks",
"TaskCount");
728 if ($RunningTasksCount > $this->MaxRunningTasksToTrack)
730 $this->DB->Query(
"DELETE FROM RunningTasks ORDER BY StartedAt" 731 .
" LIMIT ".($RunningTasksCount - $this->MaxRunningTasksToTrack));
740 private function RequeueRunningTask($TaskId)
742 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE, RunningTasks WRITE");
743 $this->DB->Query(
"INSERT INTO TaskQueue" 744 .
" (Callback,Parameters,Priority,Description)" 745 .
" SELECT Callback,Parameters,Priority,Description" 746 .
" FROM RunningTasks WHERE TaskId = ".intval($TaskId));
747 $this->DB->Query(
"DELETE FROM RunningTasks WHERE TaskId = ".intval($TaskId));
748 $this->DB->Query(
"UNLOCK TABLES");
756 private function ResetTaskIdGeneratorIfNecessary()
758 $this->DB->Query(
"LOCK TABLES TaskQueue WRITE");
759 if (($GLOBALS[
"AF"]->GetSecondsBeforeTimeout() > 30)
761 && ($this->DB->GetNextInsertId(
"TaskQueue")
764 $this->DB->Query(
"TRUNCATE TABLE TaskQueue");
766 $this->DB->Query(
"UNLOCK TABLES");
775 private function LogTaskMemoryLeakIfAny($TaskId, $StartingFreeMemory)
777 # tell PHP to garbage collect to free up any memory no longer used 780 # calculate the logging threshold 782 * ($this->BackgroundTaskMemLeakLogThreshold / 100);
784 # calculate the amount of memory used by task 786 $MemoryUsed = $StartingFreeMemory - $EndingFreeMemory;
788 # if amount of memory used is over threshold 789 if ($MemoryUsed > $LeakThreshold)
792 $TaskSynopsis = self::GetTaskCallbackSynopsis($this->
GetTask($TaskId));
794 "Task ".$TaskSynopsis.
" leaked " 795 .number_format($MemoryUsed).
" bytes.");
808 private function UpdateSetting(
809 $FieldName, $NewValue =
DB_NOVALUE, $Persistent = TRUE)
811 static $LocalSettings;
816 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
817 "ApplicationFrameworkSettings",
818 $FieldName, $NewValue, NULL, $this->Settings);
822 $LocalSettings[$FieldName] = $NewValue;
825 elseif (!isset($LocalSettings[$FieldName]))
827 $LocalSettings[$FieldName] = $this->DB->UpdateValue(
828 "ApplicationFrameworkSettings",
829 $FieldName, $NewValue, NULL, $this->Settings);
831 return $LocalSettings[$FieldName];
GetCurrentBackgroundPriority()
Determine current priority if running in background.
const PRIORITY_BACKGROUND
Lowest priority.
QueueTask($Callback, $Parameters=NULL, $Priority=self::PRIORITY_LOW, $Description="")
Add task to queue.
GetOrphanedTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently orphaned.
TaskExecutionEnabled($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set whether automatic task execution is enabled.
SQL database abstraction object with smart query caching.
const LOGLVL_DEBUG
DEBUG error logging level.
const PRIORITY_LOW
Lower priority.
GetNextLowerBackgroundPriority($Priority=NULL)
Get next lower possible background task priority.
RunQueuedTasks()
Run any queued background tasks until either remaining PHP execution time or available memory run too...
const PRIORITY_MEDIUM
Medium (default) priority.
GetNextHigherBackgroundPriority($Priority=NULL)
Get next higher possible background task priority.
const PRIORITY_HIGH
Highest priority.
GetTask($TaskId)
Retrieve task info from queue (either running or queued tasks).
static GetPercentFreeMemory()
Get current percentage of free memory.
GetOrphanedTaskCount()
Retrieve current number of orphaned tasks.
GetQueuedTaskCount($Callback=NULL, $Parameters=NULL, $Priority=NULL, $Description=NULL)
Get number of queued tasks that match supplied values.
ReQueueOrphanedTask($TaskId, $NewPriority=NULL)
Move orphaned task back into queue.
RequeueCurrentTask($NewValue=TRUE)
Set whether to requeue the currently-running background task when it completes.
TaskIsInQueue($Callback, $Parameters=NULL)
Check if task is already in queue or currently running.
static GetFreeMemory()
Get current amount of free memory.
Task manager component of top-level framework for web applications.
DeleteTask($TaskId)
Remove task from task queues.
MaxTasks($NewValue=DB_NOVALUE, $Persistent=FALSE)
Get/set maximum number of tasks to have running simultaneously.
GetQueuedTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently in queue.
static GetTaskCallbackSynopsis(array $TaskInfo)
Get printable synopsis for task callback.
static GetPhpMemoryLimit()
Get PHP memory limit in bytes.
GetRunningTaskList($Count=100, $Offset=0)
Retrieve list of tasks currently running.
GetTaskQueueSize($Priority=NULL)
Retrieve current number of tasks in queue.
QueueUniqueTask($Callback, $Parameters=NULL, $Priority=self::PRIORITY_LOW, $Description="")
Add task to queue if not already in queue or currently running.
GetRunningTaskCount()
Retrieve count of tasks currently running.