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