3 namespace Org\Mxchange\CoreFramework\Handler\Task;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
8 use Org\Mxchange\CoreFramework\Generic\FrameworkInterface;
9 use Org\Mxchange\CoreFramework\Handler\BaseHandler;
10 use Org\Mxchange\CoreFramework\Registry\Registerable;
11 use Org\Mxchange\CoreFramework\Task\Taskable;
12 use Org\Mxchange\CoreFramework\Traits\Iterator\IteratorTrait;
13 use Org\Mxchange\CoreFramework\Traits\Lists\ListableTrait;
14 use Org\Mxchange\CoreFramework\Traits\Visitor\VisitorTrait;
15 use Org\Mxchange\CoreFramework\Visitor\Visitable;
18 use \InvalidArgumentException;
19 use \UnexpectedValueException;
24 * @author Roland Haeder <webmaster@shipsimu.org>
26 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team
27 * @license GNU GPL 3.0 or any newer version
28 * @link http://www.shipsimu.org
30 * This program is free software: you can redistribute it and/or modify
31 * it under the terms of the GNU General Public License as published by
32 * the Free Software Foundation, either version 3 of the License, or
33 * (at your option) any later version.
35 * This program is distributed in the hope that it will be useful,
36 * but WITHOUT ANY WARRANTY; without even the implied warranty of
37 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 * GNU General Public License for more details.
40 * You should have received a copy of the GNU General Public License
41 * along with this program. If not, see <http://www.gnu.org/licenses/>.
43 class TaskHandler extends BaseHandler implements Registerable, HandleableTask {
49 // Exception constants
50 const EXCEPTION_TASK_IS_INVALID = 0xb00;
53 * Protected constructor
57 private function __construct () {
58 // Call parent constructor
59 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: CONSTRUCTWD!');
60 parent::__construct(__CLASS__);
63 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage('TASK-HANDLER: Setting handlerName=task ...');
64 $this->setHandlerName('task');
67 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: EXIT!');
71 * Creates an instance of this class
73 * @return $handlerInstance An instance of a HandleableTask class
75 public static final function createTaskHandler () {
77 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: CALLED!');
78 $handlerInstance = new TaskHandler();
80 // Output debug message
81 self::createDebugInstance(__CLASS__, __LINE__)->debugMessage('TASK-HANDLER: Initializing task handler.');
84 $handlerInstance->setListInstance(ObjectFactory::createObjectByConfiguredName('task_list_class'));
86 // Get default instance
87 $handlerInstance->setIteratorInstance($handlerInstance->getListInstance()->getIterator());
89 // Init visitor instance for faster loop
90 $handlerInstance->setVisitorInstance(ObjectFactory::createObjectByConfiguredName('active_task_visitor_class'));
92 // Register the first (and generic) idle-loop task
93 $taskInstance = ObjectFactory::createObjectByConfiguredName('idle_task_class');
94 $handlerInstance->registerTask('idle_loop', $taskInstance);
96 // Output debug message
97 self::createDebugInstance(__CLASS__, __LINE__)->debugMessage('TASK-HANDLER: Task handler initialized.');
99 // Return the prepared instance
100 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER:handlerInstance=%s - EXIT!', $handlerInstance->__toString()));
101 return $handlerInstance;
105 * Tries to execute the given task. If as task should not be started (yet)
106 * or the interval time (see task_interval_delay) is not yet reached the
107 * task is quietly skipped.
110 * @throws InvalidTaskException If the current task is invalid
112 private function executeCurrentTask () {
113 // Update no task by default
114 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: CALLED!');
117 // Is the current task valid?
118 if (!$this->getListInstance()->getIterator()->valid()) {
120 throw new InvalidTaskException($this, self::EXCEPTION_TASK_IS_INVALID);
124 $currentTask = $this->getListInstance()->getIterator()->current();
126 // Is the task not yet started?
127 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: currentTask()=%d', count($currentTask)));
128 if ($currentTask['task_started'] === false) {
129 // Determine difference between current time and registration
130 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: currentTask[id]=%s has not started yet, checking ...', $currentTask['id']));
131 $diff = ($this->getMilliTime() - $currentTask['task_registered']) * 1000;
133 // Should we start now?
134 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: diff=%d,currentTask[task_startup_delay]=%d', $diff, $currentTask['task_startup_delay']));
135 if ($diff < $currentTask['task_startup_delay']) {
136 // Skip this silently
137 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: Task %s not started: diff=%d,task_startup_delay=%d', $currentTask['id'], $diff, $currentTask['task_startup_delay']));
141 // Launch the task and mark it as updated
142 $currentTask['task_started'] = true;
146 self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: Task %s started with startup_delay=%dms', $currentTask['id'], $currentTask['task_startup_delay']));
149 // Get time difference from interval delay
150 $diff = ($this->getMilliTime() - $currentTask['task_last_activity']) * 1000;
152 // Is the interval delay reached?
153 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage('TASK-HANDLER: Task ' . $currentTask['id'] . ' diff=' . $diff . ',task_interval_delay=' . $currentTask['task_interval_delay'] . ',task_max_runs=' . $currentTask['task_max_runs'] . ',task_total_runs=' . $currentTask['task_total_runs']);
154 if ((($diff < $currentTask['task_interval_delay']) && ($currentTask['task_max_runs'] == 0)) || (($currentTask['task_max_runs'] > 0) && ($currentTask['task_total_runs'] == $currentTask['task_max_runs']))) {
155 // Should we update the task from startup?
156 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: currentTask[id]=%s has reached interval ... updateTask=%d', $currentTask['id'], intval($updateTask)));
157 if ($updateTask === true) {
158 // Update the task before leaving
159 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: Updating task %s ...', $currentTask['id']));
160 $this->updateTask($currentTask);
163 // Skip this silently
164 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: Task %s has been updated - EXIT!', $currentTask['id']));
169 $currentTask['task_last_activity'] = $this->getMilliTime();
172 $currentTask['task_total_runs']++;
175 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: Updating task %s ...', $currentTask['id']));
176 $this->updateTask($currentTask);
179 // @TODO Messurement can be added around this call
180 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: Invoking currentTask[task_instance]->accept(%s), task_instance=%s ...', $this->getVisitorInstance()->__toString(), $currentTask['task_instance']->__toString()));
181 $currentTask['task_instance']->accept($this->getVisitorInstance());
184 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: EXIT!');
188 * Updates given task by updating the underlaying list
190 * @param $taskEntry An array with a task
193 private function updateTask (array $taskEntry) {
194 // Get the key from current iteration
195 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: taskEntry()=%d - CALLED!', count($taskEntry)));
196 $key = $this->getListInstance()->getIterator()->key();
198 // Get the hash from key
199 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: key=%s', $key));
200 $hash = $this->getListInstance()->getHashByIndex($key);
203 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: Invoking this->listInstance->updateCurrentEntryByHash(%s,taskEntry()=%d) ...', $hash, count($taskEntry)));
204 $this->getListInstance()->updateCurrentEntryByHash($hash, $taskEntry);
207 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: EXIT!');
211 * Unregisters the given task
213 * @param $taskData Data of the task
216 private function unregisterTask (array $taskData) {
218 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: Removing task %s from queue - CALLED!', $taskData['id']));
219 $this->getListInstance()->removeEntry('tasks', $taskData);
222 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: Removing task %s from queue - EXIT!', $taskData['id']));
226 * Searches a task by given instance
228 * @param $taskInstanc An instanceof a Taskable class
229 * @return $taskName Name of the task as used while registration
231 public function searchTask (Taskable $taskInstance) {
232 // Default is an empty (not found) task name
233 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: taskInstance=%s - CALLED!', $taskInstance->__toString()));
237 $taskList = $this->getListInstance()->getArrayFromList('tasks');
239 // Search all instances
240 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: taskList()=%d', count($taskList)));
241 foreach ($taskList as $currentTask) {
242 // Does it match given task instance?
243 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: currentTask[id=%s', $currentTask['id']));
244 if ($currentTask['task_instance']->equals($taskInstance)) {
246 $taskName = $currentTask['id'];
249 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: taskName=%s - BREAK!', $taskName));
255 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: taskName=%s - EXIT!', $taskName));
260 * Registers a task with a task handler.
262 * @param $taskName A task name to register the task on
263 * @param $taskInstance An instance of a Taskable class
265 * @throws InvalidArgumentException If a parameter is not valid
266 * @throws UnexpectedValueException If an unexpected value has been configured
268 public function registerTask (string $taskName, Taskable $taskInstance) {
269 // Is the parameter valid
270 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: taskName=%s,taskInstance - CALLED!', $taskName, $taskInstance->__toString()));
271 if (empty($taskName)) {
272 // Task name cannot be empty
273 throw new InvalidArgumentException('Parameter "taskName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
276 // Get interval delay, startup delay and max runs
277 $intervalDelay = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('task_' . $taskName . '_interval_delay');
278 $startupDelay = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('task_' . $taskName . '_startup_delay');
279 $maxRuns = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('task_' . $taskName . '_max_runs');
281 // If the task is 'idle_loop', a deplay of zero seconds is fine
282 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: intervalDelay=%d,startupDelay=%d,maxRuns=%d', $intervalDelay, $startupDelay, $maxRuns));
283 if ($intervalDelay < 0) {
284 // Invalid configuration value
285 throw new UnexpectedValueException(sprintf('taskName=%s has intervalDelay=%d below zero', $taskName, $intervalDelay), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
286 } elseif ($startupDelay < 0) {
287 // Invalid configuration value
288 throw new UnexpectedValueException(sprintf('taskName=%s has startupDelay=%d below zero', $taskName, $startupDelay), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
289 } elseif ($maxRuns < 0) {
290 // Invalid configuration value
291 throw new UnexpectedValueException(sprintf('taskName=%s has maxRuns=%d below zero', $taskName, $maxRuns), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
292 } elseif ($taskName != 'idle_loop' && $intervalDelay == 0) {
293 // Only idle_loop can have a zero interval delay
294 throw new UnexpectedValueException(sprintf('taskName=%s has zero interval delay which is only valid for "idle_loop" task', $taskName), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
295 } elseif ($taskName != 'idle_loop' && $startupDelay == 0) {
296 // Only idle_loop can have a zero interval delay
297 throw new UnexpectedValueException(sprintf('taskName=%s has zero startup delay which is only valid for "idle_loop" task', $taskName), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
302 // Identifier for the generateHash() method
304 // Whether the task is started
305 'task_started' => false,
306 // Whether the task is paused (not yet implemented)
307 'task_paused' => false,
308 // Whether the task can be paused (not yet implemented)
309 'task_pauseable' => true,
310 // Timestamp of registration
311 'task_registered' => $this->getMilliTime(),
312 // Last activity timestamp
313 'task_last_activity' => 0,
314 // Total runs of this task
315 'task_total_runs' => 0,
316 // Task instance itself
317 'task_instance' => $taskInstance,
318 // Startup delay in milliseconds
319 'task_startup_delay' => $startupDelay,
320 // Interval time (delay) in milliseconds before this task is executed again
321 'task_interval_delay' => $intervalDelay,
322 // How often should this task run?
323 'task_max_runs' => $maxRuns,
327 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: Invoking this->listInstance->addEntry(tasks,taskEntry()=%d) ...', count($taskEntry)));
328 $this->getListInstance()->addEntry('tasks', $taskEntry);
331 self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: Task registered: taskName=%s (taskInstance=%s), startupDelay=%dms, intervalDelay=%dms, maxRuns=%d ...',
333 $taskInstance->__toString(),
334 $taskEntry['task_startup_delay'],
335 $taskEntry['task_interval_delay'],
336 $taskEntry['task_max_runs'],
340 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: EXIT!');
344 * Checks whether tasks are left including idle task
346 * @return $tasksLeft Whether there are tasks left to handle
348 public function hasTasksLeft () {
349 // Do we have tasks there?
350 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: CALLED!');
351 $tasksLeft = ($this->getListInstance()->count() > 0);
354 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: tasksLeft=%d - EXIT!', intval($tasksLeft)));
359 * Handles all tasks by checking if they should startup or if it is their
360 * turn to run. You should use this method in a while() loop in conjuntion
361 * with hasTasksLeft() so you can e.g. shutdown by adding a ShutdownTask
362 * which will attempt to remove all tasks from the task handler.
366 public function handleTasks () {
368 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: CALLED!');
369 if (!$this->getListInstance()->getIterator()->valid()) {
370 // Rewind to the beginning for next loop
371 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: Invoking this->listInstance->iterator->rewind() ...');
372 $this->getListInstance()->getIterator()->rewind();
375 // Try to execute the task
376 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: Invoking this->executeCurrentTask() ...');
377 $this->executeCurrentTask();
380 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: Invoking this->listInstance->iterator->next() ...');
381 $this->getListInstance()->getIterator()->next();
384 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: EXIT!');
388 * Shuts down all tasks and the task handler itself. This method should be
389 * called from a corresponding filter class.
393 public function doShutdown () {
394 // Always rewind to the beginning for next loop
395 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: Invoking this->listInstance->iterator->rewind() - CALLED!');
396 $this->getListInstance()->getIterator()->rewind();
398 // Remember all tasks that has been shutdown for removal
399 self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: Shutting down all %d tasks...', $this->getListInstance()->count()));
402 // Instance a visitor
403 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage('TASK-HANDLER: Setting visitorInstance=shutdown_task_visitor_class from configuration ...');
404 $this->setVisitorInstance(ObjectFactory::createObjectByConfiguredName('shutdown_task_visitor_class'));
406 // Shutdown all tasks in once go
407 while ($this->getListInstance()->getIterator()->valid()) {
409 $currentTask = $this->getListInstance()->getIterator()->current();
412 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage('TASK-HANDLER: Shutting down task ' . $currentTask['id'] . ' (taskInstance=' . $currentTask['task_instance']->__toString() . ') ...');
413 $currentTask['task_instance']->accept($this->getVisitorInstance());
415 // Remember this task
416 array_push($tasks, $currentTask);
418 // Advance to next one
419 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: Invoking this->listInstance->iterator->next() ...');
420 $this->getListInstance()->getIterator()->next();
424 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('TASK-HANDLER: Shutdown of all tasks completed. tasks()=%d', count($tasks)));
425 foreach ($tasks as $taskEntry) {
426 // Unregister this task
427 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('TASK-HANDLER: Invokint this->unregisterTask(taskEntry()=%d) ...', count($taskEntry)));
428 $this->unregisterTask($taskEntry);
432 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('TASK-HANDLER: EXIT!');