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