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