Completed
Push — 2.0 ( 76e968...4129d9 )
by Marco
13:01
created

Manager::__construct()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 35
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 24
nc 2
nop 6
1
<?php namespace Comodojo\Extender\Task;
2
3
use \Comodojo\Foundation\Base\Configuration;
4
use \Comodojo\Foundation\Events\Manager as EventsManager;
5
use \Comodojo\Daemon\Traits\LoggerTrait;
6
use \Comodojo\Daemon\Traits\EventsTrait;
7
use \Comodojo\Daemon\Utils\ProcessTools;
8
use \Comodojo\Extender\Traits\ConfigurationTrait;
9
use \Comodojo\Extender\Traits\TasksTableTrait;
10
use \Comodojo\Extender\Utils\Validator as ExtenderCommonValidations;
11
use \Comodojo\Extender\Traits\EntityManagerTrait;
12
use \Comodojo\Extender\Components\Ipc;
13
use \Comodojo\Extender\Task\Table as TasksTable;
14
use \Comodojo\Extender\Components\Database;
15
use \Doctrine\ORM\EntityManager;
16
use \Psr\Log\LoggerInterface;
17
use \Exception;
18
19
/**
20
* @package     Comodojo Extender
21
* @author      Marco Giovinazzi <[email protected]>
22
* @license     MIT
23
*
24
* LICENSE:
25
*
26
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
* THE SOFTWARE.
33
 */
34
35
class Manager {
36
37
    use ConfigurationTrait;
38
    use LoggerTrait;
39
    use EventsTrait;
40
    use TasksTableTrait;
41
    use EntityManagerTrait;
42
43
    /**
44
     * @var int
45
     */
46
    protected $lagger_timeout;
47
48
    /**
49
     * @var bool
50
     */
51
    protected $multithread;
52
53
    /**
54
     * @var int
55
     */
56
    protected $max_runtime;
57
58
    /**
59
     * @var int
60
     */
61
    protected $max_childs;
62
63
    /**
64
     * @var Ipc
65
     */
66
    protected $ipc;
67
68
    /**
69
     * @var Locker
70
     */
71
    protected $locker;
72
73
    /**
74
     * Class constructor
75
     *
76
     * @param string $manager_name
77
     * @param Configuration $configuration
78
     * @param LoggerInterface $logger
79
     * @param TasksTable $tasks
80
     * @param EventsManager $events
81
     * @param EntityManager $em
82
     */
83
    public function __construct(
84
        $manager_name,
85
        Configuration $configuration,
86
        LoggerInterface $logger,
87
        TasksTable $tasks,
88
        EventsManager $events,
89
        EntityManager $em = null
90
    ) {
91
92
        $this->setConfiguration($configuration);
93
        $this->setLogger($logger);
94
        $this->setTasksTable($tasks);
95
        $this->setEvents($events);
96
97
        $em = is_null($em) ? Database::init($configuration)->getEntityManager() : $em;
98
        $this->setEntityManager($em);
99
100
        $this->ipc = new Ipc($configuration);
101
102
        $this->locker = Locker::create($manager_name, $configuration, $logger);
103
104
        // retrieve parameters
105
        $this->lagger_timeout = ExtenderCommonValidations::laggerTimeout($this->configuration->get('child-lagger-timeout'));
106
        $this->multithread = ExtenderCommonValidations::multithread($this->configuration->get('multithread'));
107
        $this->max_runtime = ExtenderCommonValidations::maxChildRuntime($this->configuration->get('child-max-runtime'));
108
        $this->max_childs = ExtenderCommonValidations::forkLimit($this->configuration->get('fork-limit'));
109
110
        $logger->debug("Tasks Manager online", array(
111
            'lagger_timeout' => $this->lagger_timeout,
112
            'multithread' => $this->multithread,
113
            'max_runtime' => $this->max_runtime,
114
            'max_childs' => $this->max_childs
115
        ));
116
117
    }
118
119
    /**
120
     * Class destructor
121
     *
122
     * Remove the locker file on shutdown
123
     */
124
    public function __destruct() {
125
126
        $this->locker->release();
127
128
    }
129
130
    public function add(Request $request) {
131
132
        $this->locker->setQueued($request);
133
134
        return $this;
135
136
    }
137
138
    public function addBulk(array $requests) {
139
140
        $responses = [];
141
142
        foreach ($requests as $id => $request) {
143
144
            if ($request instanceof \Comodojo\Extender\Task\Request) {
145
                $this->add($request);
146
                $responses[$id] = true;
147
            } else {
148
                $this->logger->error("Skipping invalid request with local id $id: class mismatch");
149
                $responses[$id] = false;
150
            }
151
152
        }
153
154
        return $responses;
155
156
    }
157
158
    public function run() {
159
160
        while ( $this->locker->countQueued() > 0 ) {
161
162
            // Start to cycle queued tasks
163
            $this->cycle();
164
165
        }
166
167
        $result = $this->locker->getCompleted();
168
169
        $this->locker->freeCompleted();
170
171
        return $result;
172
173
    }
174
175
    protected function cycle() {
176
177
        foreach ($this->locker->getQueued() as $uid => $request) {
178
179
            if ( $this->multithread === false ) {
180
181
                $this->runSingleThread($uid, $request);
182
183
            } else {
184
185
                try {
186
187
                    $pid = $this->forker($request);
188
189
                } catch (Exception $e) {
190
191
                    $result = self::generateSyntheticResult($uid, $e->getMessage(), false);
192
193
                    $this->locker->setAborted($uid, $result);
194
195
                    if ( $request->isChain() ) $this->evalChain($request, $result);
196
197
                    continue;
198
199
                }
200
201
                $this->locker->setRunning($uid, $pid);
202
203
                if ( $this->max_childs > 0 && $this->locker->countRunning() >= $this->max_childs ) {
204
205
                    while( $this->locker->countRunning() >= $this->max_childs ) {
206
207
                        $this->catcher();
208
209
                    }
210
211
                }
212
213
            }
214
215
        }
216
217
        // spawn the loop if multithread
218
        if ( $this->multithread === true ) $this->catcher_loop();
219
220
    }
221
222
    protected function runSingleThread($uid, Request $request) {
223
224
        $pid = ProcessTools::getPid();
225
226
        $this->locker->setRunning($uid, $pid);
227
228
        $result = Runner::fastStart(
229
            $request,
230
            $this->getConfiguration(),
231
            $this->getLogger(),
232
            $this->getTasksTable(),
233
            $this->getEvents(),
234
            $this->getEntityManager()
235
        );
236
237
        if ( $request->isChain() ) $this->evalChain($request, $result);
238
239
        $this->locker->setCompleted($uid, $result);
240
241
        $success = $result->success === false ? "error" : "success";
0 ignored issues
show
Documentation introduced by
The property success does not exist on object<Comodojo\Extender\Task\Result>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
242
        $this->logger->notice("Task ".$request->getName()."(uid: ".$request->getUid().") ends in $success");
243
244
    }
245
246
    private function forker(Request $request) {
247
248
        $uid = $request->getUid();
249
250
        try {
251
252
            $this->ipc->init($uid);
253
254
        } catch (Exception $e) {
255
256
            $this->logger->error("Aborting task ".$request->getName().": ".$e->getMessage());
257
258
            $this->ipc->hang($uid);
259
260
            throw $e;
261
262
        }
263
264
        $pid = pcntl_fork();
265
266
        if ( $pid == -1 ) {
267
268
            // $this->logger->error("Could not fok job, aborting");
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
269
270
            throw new Exception("Unable to fork job, aborting");
271
272
        } elseif ( $pid ) {
273
274
            //PARENT will take actions on processes later
275
276
            $niceness = $request->getNiceness();
277
278
            if ( $niceness !== null ) ProcessTools::setNiceness($niceness, $pid);
279
280
        } else {
281
282
            $this->ipc->close($uid, Ipc::READER);
283
284
            $result = Runner::fastStart(
285
                $request,
286
                $this->getConfiguration(),
287
                $this->getLogger(),
288
                $this->getTasksTable(),
289
                $this->getEvents(),
290
                $this->getEntityManager()
291
            );
292
293
            // $output = array(
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
294
            //     'success' => $result->success,
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
295
            //     'result' => $result->result,
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
296
            //     'wid' => $result->wid
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
297
            // );
298
299
            $this->ipc->write($uid, serialize($result));
300
301
            $this->ipc->close($uid, Ipc::WRITER);
302
303
            exit(!$result->success);
304
305
        }
306
307
        return $pid;
308
309
    }
310
311
    private function catcher_loop() {
312
313
        while ( !empty($this->locker->getRunning()) ) {
314
315
            $this->catcher();
316
317
        }
318
319
    }
320
321
    /**
322
     * Catch results from completed jobs
323
     *
324
     */
325
    private function catcher() {
326
327
        foreach ( $this->locker->getRunning() as $uid => $request ) {
328
329
            if ( ProcessTools::isRunning($request->getPid()) === false ) {
330
331
                $this->ipc->close($uid, Ipc::WRITER);
332
333
                try {
334
335
                    $raw_output = $this->ipc->read($uid);
336
337
                    $result = unserialize(rtrim($raw_output));
338
339
                    $this->ipc->close($uid, Ipc::READER);
340
341
                } catch (Exception $e) {
342
343
                    $result = self::generateSyntheticResult($uid, $e->getMessage(), false);
344
345
                    // $this->logger->error($result);
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
346
347
                }
348
349
                if ( $request->isChain() ) $this->evalChain($request, $result);
350
351
                $this->locker->setCompleted($uid, $result);
352
353
                $success = $result->success === false ? "error" : "success";
354
                $this->logger->notice("Task ".$request->getName()."(uid: ".$request->getUid().") ends in $success");
355
356
            } else {
357
358
                $current_time = microtime(true);
359
360
                $request_max_time = $request->getMaxtime();
361
                $maxtime = $request_max_time === null ? $this->max_runtime : $request_max_time;
362
363
                if ( $current_time > $request->getStartTimestamp() + $maxtime ) {
364
365
                    $pid = $request->getPid();
366
367
                    $this->logger->warning("Killing pid $pid due to maximum exec time reached", [
368
                        "START_TIME"    => $request->getStartTimestamp(),
369
                        "CURRENT_TIME"  => $current_time,
370
                        "MAX_RUNTIME"   => $maxtime
371
                    ]);
372
373
                    $kill = ProcessTools::term($pid, $this->lagger_timeout);
374
375
                    if ( $kill ) {
376
                        $this->logger->warning("Pid $pid killed");
377
                    } else {
378
                        $this->logger->warning("Pid $pid could not be killed");
379
                    }
380
381
                    $this->ipc->hang($uid);
382
383
                    $result = self::generateSyntheticResult($uid, "Job killed due to max runtime reached", false);
384
385
                    if ( $request->isChain() ) $this->evalChain($request, $result);
386
387
                    $this->locker->setCompleted($uid, $result);
388
389
                    $this->logger->notice("Task ".$request->getName()."(uid: $uid) ends in error");
390
391
                }
392
393
            }
394
395
        }
396
397
    }
398
399
    public function free() {
400
401
        $this->ipc->free();
402
        $this->locker->free();
403
404
    }
405
406
    private function evalChain(Request $request, Result $result) {
407
408 View Code Duplication
        if ( $result->success && $request->hasOnDone() ) {
0 ignored issues
show
Documentation introduced by
The property success does not exist on object<Comodojo\Extender\Task\Result>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
409
            $chain_done = $request->getOnDone();
410
            $chain_done->getParameters()->set('parent', $result);
411
            $chain_done->setParentUid($result->uid);
0 ignored issues
show
Documentation introduced by
The property uid does not exist on object<Comodojo\Extender\Task\Result>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
412
            $this->add($chain_done);
413
        }
414
415 View Code Duplication
        if ( $result->success === false && $request->hasOnFail() ) {
0 ignored issues
show
Documentation introduced by
The property success does not exist on object<Comodojo\Extender\Task\Result>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
416
            $chain_fail = $request->getOnFail();
417
            $chain_fail->getParameters()->set('parent', $result);
418
            $chain_fail->setParentUid($result->uid);
0 ignored issues
show
Documentation introduced by
The property uid does not exist on object<Comodojo\Extender\Task\Result>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
419
            $this->add($chain_fail);
420
        }
421
422
        if ( $request->hasPipe() ) {
423
            $chain_pipe = $request->getPipe();
424
            $chain_pipe->getParameters()->set('parent', $result);
425
            $chain_pipe->setParentUid($result->uid);
0 ignored issues
show
Documentation introduced by
The property uid does not exist on object<Comodojo\Extender\Task\Result>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
426
            $this->add($chain_pipe);
427
        }
428
429
    }
430
431
    private function generateSyntheticResult($uid, $message, $success = true) {
432
433
        return new Result([
434
            $uid,
435
            null,
436
            null,
437
            $success,
438
            null,
439
            null,
440
            $message,
441
            null
442
        ]);
443
444
    }
445
446
}
447