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