]> git.mxchange.org Git - core.git/blob - framework/main/classes/handler/tasks/class_TaskHandler.php
Continued:
[core.git] / framework / main / classes / handler / tasks / class_TaskHandler.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Handler\Task;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
8 use Org\Mxchange\CoreFramework\Handler\BaseHandler;
9 use Org\Mxchange\CoreFramework\Registry\Registerable;
10 use Org\Mxchange\CoreFramework\Task\Taskable;
11 use Org\Mxchange\CoreFramework\Traits\Iterator\IteratorTrait;
12 use Org\Mxchange\CoreFramework\Traits\Lists\ListableTrait;
13 use Org\Mxchange\CoreFramework\Traits\Visitor\VisitorTrait;
14 use Org\Mxchange\CoreFramework\Visitor\Visitable;
15
16 // Import SPL stuff
17 use \InvalidArgumentException;
18 use \UnexpectedValueException;
19
20 /**
21  * A Task handler
22  *
23  * @author              Roland Haeder <webmaster@shipsimu.org>
24  * @version             0.0.0
25  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team
26  * @license             GNU GPL 3.0 or any newer version
27  * @link                http://www.shipsimu.org
28  *
29  * This program is free software: you can redistribute it and/or modify
30  * it under the terms of the GNU General Public License as published by
31  * the Free Software Foundation, either version 3 of the License, or
32  * (at your option) any later version.
33  *
34  * This program is distributed in the hope that it will be useful,
35  * but WITHOUT ANY WARRANTY; without even the implied warranty of
36  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37  * GNU General Public License for more details.
38  *
39  * You should have received a copy of the GNU General Public License
40  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
41  */
42 class TaskHandler extends BaseHandler implements Registerable, HandleableTask {
43         // Load traits
44         use IteratorTrait;
45         use ListableTrait;
46         use VisitorTrait;
47
48         // Exception constants
49         const EXCEPTION_TASK_IS_INVALID = 0xb00;
50
51         /**
52          * Protected constructor
53          *
54          * @return      void
55          */
56         private function __construct () {
57                 // Call parent constructor
58                 parent::__construct(__CLASS__);
59
60                 // Set handler name
61                 $this->setHandlerName('task');
62         }
63
64         /**
65          * Creates an instance of this class
66          *
67          * @return      $handlerInstance        An instance of a HandleableTask class
68          */
69         public static final function createTaskHandler () {
70                 // Get new instance
71                 $handlerInstance = new TaskHandler();
72
73                 // Output debug message
74                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER: Initializing task handler.');
75
76                 // Init the task list
77                 $handlerInstance->setListInstance(ObjectFactory::createObjectByConfiguredName('task_list_class'));
78
79                 // Get default instance
80                 $handlerInstance->setIteratorInstance($handlerInstance->getListInstance()->getIterator());
81
82                 // Init visitor instance for faster loop
83                 $handlerInstance->setVisitorInstance(ObjectFactory::createObjectByConfiguredName('active_task_visitor_class'));
84
85                 // Register the first (and generic) idle-loop task
86                 $taskInstance = ObjectFactory::createObjectByConfiguredName('idle_task_class');
87                 $handlerInstance->registerTask('idle_loop', $taskInstance);
88
89                 // Output debug message
90                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER: Task handler initialized.');
91
92                 // Return the prepared instance
93                 return $handlerInstance;
94         }
95
96         /**
97          * Tries to execute the given task. If as task should not be started (yet)
98          * or the interval time (see task_interval_delay) is not yet reached the
99          * task is quietly skipped.
100          *
101          * @return      void
102          * @throws      InvalidTaskException    If the current task is invalid
103          */
104         private function executeCurrentTask () {
105                 // Update no task by default
106                 $updateTask = false;
107
108                 // Is the current task valid?
109                 if (!$this->getListInstance()->getIterator()->valid()) {
110                         // Not valid!
111                         throw new InvalidTaskException($this, self::EXCEPTION_TASK_IS_INVALID);
112                 }
113
114                 // Get current task
115                 $currentTask = $this->getListInstance()->getIterator()->current();
116
117                 // Is the task not yet started?
118                 if ($currentTask['task_started'] === false) {
119                         // Determine difference between current time and registration
120                         $diff = ($this->getMilliTime() - $currentTask['task_registered']) * 1000;
121
122                         // Should we start now?
123                         if ($diff < $currentTask['task_startup_delay']) {
124                                 // Skip this silently
125                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER: Task ' . $currentTask['id'] . ' not started: diff=' . $diff . ',task_startup_delay=' . $currentTask['task_startup_delay']);
126                                 return;
127                         }
128
129                         // Launch the task and mark it as updated
130                         $currentTask['task_started'] = true;
131                         $updateTask = true;
132
133                         // Debug message
134                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER: Task ' . $currentTask['id'] . ' started with startup_delay=' . $currentTask['task_startup_delay'] . 'ms');
135                 }
136
137                 // Get time difference from interval delay
138                 $diff = ($this->getMilliTime() - $currentTask['task_last_activity']) * 1000;
139
140                 // Is the interval delay reached?
141                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('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']);
142                 if ((($diff < $currentTask['task_interval_delay']) && ($currentTask['task_max_runs'] == 0)) || (($currentTask['task_max_runs'] > 0) && ($currentTask['task_total_runs'] == $currentTask['task_max_runs']))) {
143                         // Should we update the task from startup?
144                         if ($updateTask === true) {
145                                 // Update the task before leaving
146                                 $this->updateTask($currentTask);
147                         }
148
149                         // Skip this silently
150                         return;
151                 }
152
153                 // Set last activity
154                 $currentTask['task_last_activity'] = $this->getMilliTime();
155
156                 // Count this run
157                 $currentTask['task_total_runs']++;
158
159                 // Update the task
160                 $this->updateTask($currentTask);
161
162                 // And visit/run it
163                 // @TODO Messurement can be added around this call
164                 $currentTask['task_instance']->accept($this->getVisitorInstance());
165         }
166
167         /**
168          * Updates given task by updating the underlaying list
169          *
170          * @param       $taskEntry      An array with a task
171          * @return      void
172          */
173         private function updateTask (array $taskEntry) {
174                 // Get the key from current iteration
175                 $key = $this->getListInstance()->getIterator()->key();
176
177                 // Get the hash from key
178                 $hash = $this->getListInstance()->getHashByIndex($key);
179
180                 // Update the entry
181                 $this->getListInstance()->updateCurrentEntryByHash($hash, $taskEntry);
182         }
183
184         /**
185          * Unregisters the given task
186          *
187          * @param       $taskData       Data of the task
188          * @return      void
189          */
190         private function unregisterTask (array $taskData) {
191                 // Debug output
192                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER: Removing task ' . $taskData['id'] . ' from queue - CALLED!');
193
194                 // Remove the entry
195                 $this->getListInstance()->removeEntry('tasks', $taskData);
196
197                 // Debug output
198                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER: Removing task ' . $taskData['id'] . ' from queue - EXIT!');
199          }
200
201         /**
202          * Searches a task by given instance
203          *
204          * @param       $taskInstanc    An instanceof a Taskable class
205          * @return      $taskName               Name of the task as used while registration
206          */
207         public function searchTask (Taskable $taskInstance) {
208                 // Default is an empty (not found) task name
209                 $taskName = '';
210
211                 // Get whole list
212                 $taskList = $this->getListInstance()->getArrayFromList('tasks');
213
214                 // Search all instances
215                 foreach ($taskList as $currentTask) {
216                         // Does it match given task instance?
217                         if ($currentTask['task_instance']->equals($taskInstance)) {
218                                 // Found it
219                                 $taskName = $currentTask['id'];
220
221                                 // Abort here
222                                 break;
223                         }
224                 }
225
226                 // Return found name
227                 return $taskName;
228         }
229
230         /**
231          * Registers a task with a task handler.
232          *
233          * @param       $taskName               A task name to register the task on
234          * @param       $taskInstance   An instance of a Taskable class
235          * @return      void
236          * @throws      InvalidArgumentException        If a parameter is not valid
237          * @throws      UnexpectedValueException        If an unexpected value has been configured
238          */
239         public function registerTask (string $taskName, Taskable $taskInstance) {
240                 // Is the parameter valid
241                 if (empty($taskName)) {
242                         // Task name cannot be empty
243                         throw new InvalidArgumentException('Parameter "taskName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
244                 }
245
246                 // Get interval delay, startup delay and max runs
247                 $intervalDelay = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('task_' . $taskName . '_interval_delay');
248                 $startupDelay = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('task_' . $taskName . '_startup_delay');
249                 $maxRuns = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry('task_' . $taskName . '_max_runs');
250
251                 // If the task is 'idle_loop', a deplay of zero seconds is fine
252                 if ($intervalDelay < 0) {
253                         // Invalid configuration value
254                         throw new UnexpectedValueException(sprintf('taskName=%s has intervalDelay=%d below zero', $taskName, $intervalDelay));
255                 } elseif ($startupDelay < 0) {
256                         // Invalid configuration value
257                         throw new UnexpectedValueException(sprintf('taskName=%s has startupDelay=%d below zero', $taskName, $startupDelay));
258                 } elseif ($maxRuns < 0) {
259                         // Invalid configuration value
260                         throw new UnexpectedValueException(sprintf('taskName=%s has maxRuns=%d below zero', $taskName, $maxRuns));
261                 } elseif ($taskName != 'idle_loop' && $intervalDelay == 0) {
262                         // Only idle_loop can have a zero interval delay
263                         throw new UnexpectedValueException(sprintf('taskName=%s has zero interval delay which is only valid for "idle_loop" task', $taskName));
264                 } elseif ($taskName != 'idle_loop' && $startupDelay == 0) {
265                         // Only idle_loop can have a zero interval delay
266                         throw new UnexpectedValueException(sprintf('taskName=%s has zero startup delay which is only valid for "idle_loop" task', $taskName));
267                 }
268
269                 // Create the entry
270                 $taskEntry = [
271                         // Identifier for the generateHash() method
272                         'id'                  => $taskName,
273                         // Whether the task is started
274                         'task_started'        => false,
275                         // Whether the task is paused (not yet implemented)
276                         'task_paused'         => false,
277                         // Whether the task can be paused (not yet implemented)
278                         'task_pauseable'      => true,
279                         // Timestamp of registration
280                         'task_registered'     => $this->getMilliTime(),
281                         // Last activity timestamp
282                         'task_last_activity'  => 0,
283                         // Total runs of this task
284                         'task_total_runs'     => 0,
285                         // Task instance itself
286                         'task_instance'       => $taskInstance,
287                         // Startup delay in milliseconds
288                         'task_startup_delay'  => $startupDelay,
289                         // Interval time (delay) in milliseconds before this task is executed again
290                         'task_interval_delay' => $intervalDelay,
291                         // How often should this task run?
292                         'task_max_runs'       => $maxRuns,
293                 ];
294
295                 // Add the entry
296                 $this->getListInstance()->addEntry('tasks', $taskEntry);
297
298                 // Debug message
299                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER: Task registered: taskName=' . $taskName .
300                         ' (taskInstance=' . $taskInstance->__toString() . ')' .
301                         ', startupDelay=' . $taskEntry['task_startup_delay'] . 'ms' .
302                         ', intervalDelay=' . $taskEntry['task_interval_delay'] . 'ms' .
303                         ', maxRuns=' . $taskEntry['task_max_runs'] . ' ...'
304                 );
305         }
306
307         /**
308          * Checks whether tasks are left including idle task
309          *
310          * @return      $tasksLeft      Whether there are tasks left to handle
311          */
312         public function hasTasksLeft () {
313                 // Do we have tasks there?
314                 $tasksLeft = ($this->getListInstance()->count() > 0);
315
316                 // Return result
317                 return $tasksLeft;
318         }
319
320         /**
321          * Handles all tasks by checking if they should startup or if it is their
322          * turn to run. You should use this method in a while() loop in conjuntion
323          * with hasTasksLeft() so you can e.g. shutdown by adding a ShutdownTask
324          * which will attempt to remove all tasks from the task handler.
325          *
326          * @return      void
327          */
328         public function handleTasks () {
329                 // Should we rewind?
330                 if (!$this->getListInstance()->getIterator()->valid()) {
331                         // Rewind to the beginning for next loop
332                         $this->getListInstance()->getIterator()->rewind();
333                 }
334
335                 // Try to execute the task
336                 $this->executeCurrentTask();
337
338                 // Go to next entry
339                 $this->getListInstance()->getIterator()->next();
340         }
341
342         /**
343          * Shuts down all tasks and the task handler itself. This method should be
344          * called from a corresponding filter class.
345          * 
346          * @return      void
347          */
348         public function doShutdown () {
349                 // Always rewind to the beginning for next loop
350                 $this->getListInstance()->getIterator()->rewind();
351
352                 // Debug message
353                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER: Shutting down all ' . $this->getListInstance()->count() . ' tasks...');
354
355                 // Remember all tasks that has been shutdown for removal
356                 $tasks = [];
357
358                 // Instance a visitor
359                 $this->setVisitorInstance(ObjectFactory::createObjectByConfiguredName('shutdown_task_visitor_class'));
360
361                 // Shutdown all tasks in once go
362                 while ($this->getListInstance()->getIterator()->valid()) {
363                         // Get current entry
364                         $currentTask = $this->getListInstance()->getIterator()->current();
365
366                         // Output debug message
367                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER: Shutting down task ' . $currentTask['id'] . ' (taskInstance=' . $currentTask['task_instance']->__toString() . ') ...');
368
369                         // Shutdown the task
370                         $currentTask['task_instance']->accept($this->getVisitorInstance());
371
372                         // Remember this task
373                         array_push($tasks, $currentTask);
374
375                         // Advance to next one
376                         $this->getListInstance()->getIterator()->next();
377                 }
378
379                 // Debug message
380                 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('TASK-HANDLER: Shutdown of all tasks completed.');
381
382                 // Remove all tasks
383                 foreach ($tasks as $entry) {
384                         $this->unregisterTask($entry);
385                 }
386         }
387
388 }