Completed
Push — 2.0 ( bcdb61...f02592 )
by Marco
02:29
created

Manager::generateSyntheticResult()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 1
eloc 11
nc 1
nop 4
1
<?php namespace Comodojo\Extender\Task;
2
3
use \Comodojo\Foundation\Base\Configuration;
4
use \Comodojo\Foundation\Events\Manager as EventsManager;
5
use \Comodojo\Foundation\Logging\LoggerTrait;
6
use \Comodojo\Foundation\Events\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
            'tasks_count' => count($this->table)
116
        ));
117
118
    }
119
120
    /**
121
     * Class destructor
122
     *
123
     * Remove the locker file on shutdown
124
     */
125
    public function __destruct() {
126
127
        $this->locker->release();
128
129
    }
130
131
    public function add(Request $request) {
132
133
        $this->locker->setQueued($request);
134
135
        return $this;
136
137
    }
138
139
    public function addBulk(array $requests) {
140
141
        $responses = [];
142
143
        foreach ($requests as $id => $request) {
144
145
            if ($request instanceof \Comodojo\Extender\Task\Request) {
146
                $this->add($request);
147
                $responses[$id] = true;
148
            } else {
149
                $this->logger->error("Skipping invalid request with local id $id: class mismatch");
150
                $responses[$id] = false;
151
            }
152
153
        }
154
155
        return $responses;
156
157
    }
158
159
    public function run() {
160
161
        while ( $this->locker->countQueued() > 0 ) {
162
163
            // Start to cycle queued tasks
164
            $this->cycle();
165
166
        }
167
168
        $result = $this->locker->getCompleted();
169
170
        $this->locker->freeCompleted();
171
172
        return $result;
173
174
    }
175
176
    protected function cycle() {
177
178
        foreach ($this->locker->getQueued() as $uid => $request) {
179
180
            if ( $this->multithread === false ) {
181
182
                $this->runSingleThread($uid, $request);
183
184
            } else {
185
186
                try {
187
188
                    $pid = $this->forker($request);
189
190
                } catch (Exception $e) {
191
192
                    $result = self::generateSyntheticResult($uid, $e->getMessage(), $request->getJid(), false);
193
194
                    $this->locker->setAborted($uid, $result);
195
196
                    if ( $request->isChain() ) $this->evalChain($request, $result);
197
198
                    continue;
199
200
                }
201
202
                $this->locker->setRunning($uid, $pid);
203
204
                if ( $this->max_childs > 0 && $this->locker->countRunning() >= $this->max_childs ) {
205
206
                    while( $this->locker->countRunning() >= $this->max_childs ) {
207
208
                        $this->catcher();
209
210
                    }
211
212
                }
213
214
            }
215
216
        }
217
218
        // spawn the loop if multithread
219
        if ( $this->multithread === true ) $this->catcher_loop();
220
221
    }
222
223
    protected function runSingleThread($uid, Request $request) {
224
225
        $pid = ProcessTools::getPid();
226
227
        $this->locker->setRunning($uid, $pid);
228
229
        $result = Runner::fastStart(
230
            $request,
231
            $this->getConfiguration(),
232
            $this->getLogger(),
233
            $this->getTasksTable(),
234
            $this->getEvents(),
235
            $this->getEntityManager()
236
        );
237
238
        if ( $request->isChain() ) $this->evalChain($request, $result);
239
240
        $this->locker->setCompleted($uid, $result);
241
242
        $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...
243
        $this->logger->notice("Task ".$request->getName()."(uid: ".$request->getUid().") ends in $success");
244
245
    }
246
247
    private function forker(Request $request) {
248
249
        $uid = $request->getUid();
250
251
        try {
252
253
            $this->ipc->init($uid);
254
255
        } catch (Exception $e) {
256
257
            $this->logger->error("Aborting task ".$request->getName().": ".$e->getMessage());
258
259
            $this->ipc->hang($uid);
260
261
            throw $e;
262
263
        }
264
265
        $pid = pcntl_fork();
266
267
        if ( $pid == -1 ) {
268
269
            // $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...
270
271
            throw new Exception("Unable to fork job, aborting");
272
273
        } elseif ( $pid ) {
274
275
            //PARENT will take actions on processes later
276
277
            $niceness = $request->getNiceness();
278
279
            if ( $niceness !== null ) ProcessTools::setNiceness($niceness, $pid);
280
281
        } else {
282
283
            $this->ipc->close($uid, Ipc::READER);
284
285
            $result = Runner::fastStart(
286
                $request,
287
                $this->getConfiguration(),
288
                $this->getLogger(),
289
                $this->getTasksTable(),
290
                $this->getEvents(),
291
                $this->getEntityManager()
292
            );
293
294
            // $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...
295
            //     '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...
296
            //     '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...
297
            //     '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...
298
            // );
299
300
            $this->ipc->write($uid, serialize($result));
301
302
            $this->ipc->close($uid, Ipc::WRITER);
303
304
            exit(!$result->success);
305
306
        }
307
308
        return $pid;
309
310
    }
311
312
    private function catcher_loop() {
313
314
        while ( !empty($this->locker->getRunning()) ) {
315
316
            $this->catcher();
317
318
        }
319
320
    }
321
322
    /**
323
     * Catch results from completed jobs
324
     *
325
     */
326
    private function catcher() {
327
328
        foreach ( $this->locker->getRunning() as $uid => $request ) {
329
330
            if ( ProcessTools::isRunning($request->getPid()) === false ) {
331
332
                $this->ipc->close($uid, Ipc::WRITER);
333
334
                try {
335
336
                    $raw_output = $this->ipc->read($uid);
337
338
                    $result = unserialize(rtrim($raw_output));
339
340
                    $this->ipc->close($uid, Ipc::READER);
341
342
                } catch (Exception $e) {
343
344
                    $result = self::generateSyntheticResult($uid, $e->getMessage(), $request->getJid(), false);
345
346
                    // $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...
347
348
                }
349
350
                if ( $request->isChain() ) $this->evalChain($request, $result);
351
352
                $this->locker->setCompleted($uid, $result);
353
354
                $success = $result->success === false ? "error" : "success";
355
                $this->logger->notice("Task ".$request->getName()."(uid: ".$request->getUid().") ends in $success");
356
357
            } else {
358
359
                $current_time = microtime(true);
360
361
                $request_max_time = $request->getMaxtime();
362
                $maxtime = $request_max_time === null ? $this->max_runtime : $request_max_time;
363
364
                if ( $current_time > $request->getStartTimestamp() + $maxtime ) {
365
366
                    $pid = $request->getPid();
367
368
                    $this->logger->warning("Killing pid $pid due to maximum exec time reached", [
369
                        "START_TIME"    => $request->getStartTimestamp(),
370
                        "CURRENT_TIME"  => $current_time,
371
                        "MAX_RUNTIME"   => $maxtime
372
                    ]);
373
374
                    $kill = ProcessTools::term($pid, $this->lagger_timeout);
375
376
                    if ( $kill ) {
377
                        $this->logger->warning("Pid $pid killed");
378
                    } else {
379
                        $this->logger->warning("Pid $pid could not be killed");
380
                    }
381
382
                    $this->ipc->hang($uid);
383
384
                    $result = self::generateSyntheticResult($uid, "Job killed due to max runtime reached", $request->getJid(), false);
385
386
                    if ( $request->isChain() ) $this->evalChain($request, $result);
387
388
                    $this->locker->setCompleted($uid, $result);
389
390
                    $this->logger->notice("Task ".$request->getName()."(uid: $uid) ends in error");
391
392
                }
393
394
            }
395
396
        }
397
398
    }
399
400
    public function free() {
401
402
        $this->ipc->free();
403
        $this->locker->free();
404
405
    }
406
407
    private function evalChain(Request $request, Result $result) {
408
409 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...
410
            $chain_done = $request->getOnDone();
411
            $chain_done->getParameters()->set('parent', $result);
412
            $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...
413
            $this->add($chain_done);
414
        }
415
416 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...
417
            $chain_fail = $request->getOnFail();
418
            $chain_fail->getParameters()->set('parent', $result);
419
            $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...
420
            $this->add($chain_fail);
421
        }
422
423
        if ( $request->hasPipe() ) {
424
            $chain_pipe = $request->getPipe();
425
            $chain_pipe->getParameters()->set('parent', $result);
426
            $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...
427
            $this->add($chain_pipe);
428
        }
429
430
    }
431
432
    private function generateSyntheticResult($uid, $message, $jid = null, $success = true) {
433
434
        return new Result([
435
            $uid,
436
            null,
437
            $jid,
438
            null,
439
            $success,
440
            null,
441
            null,
442
            $message,
443
            null
444
        ]);
445
446
    }
447
448
}
449