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