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