Completed
Push — master ( 8a4ca9...acd23c )
by Marco
02:46
created

src/Runner/JobsRunner.php (16 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php namespace Comodojo\Extender\Runner;
2
3
use \Exception;
4
use \Comodojo\Extender\Checks;
5
use \Comodojo\Extender\Queue;
6
use \Monolog\Logger;
7
use \Comodojo\Exception\TaskException;
8
9
/**
10
 * Jobs runner
11
 *
12
 * @package     Comodojo extender
13
 * @author      Marco Giovinazzi <[email protected]>
14
 * @license     GPL-3.0+
15
 *
16
 * LICENSE:
17
 * 
18
 * This program is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License as
20
 * published by the Free Software Foundation, either version 3 of the
21
 * License, or (at your option) any later version.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26
 * GNU Affero General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU Affero General Public License
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30
 */
31
32
class JobsRunner {
33
34
    /**
35
     * Jobs database (a simple array!).
36
     *
37
     * @var     array
38
     */
39
    private $jobs = array();
40
41
    /**
42
     * Logger instance
43
     *
44
     * @var \Monolog\Logger
45
     */
46
    private $logger = null;
47
48
    /**
49
     * Multithread switch
50
     *
51
     * @var     bool
52
     */
53
    private $multithread = false;
54
55
    /**
56
     * Array of completed processes
57
     *
58
     * @var     array
59
     */
60
    private $completed_processes = array();
61
62
    /**
63
     * Array of running processes
64
     *
65
     * @var     array
66
     */
67
    private $running_processes = array();
68
69
    /**
70
     * Array of forked processes
71
     *
72
     * @var     array
73
     */
74
    private $forked_processes = array();
75
76
    /**
77
     * Amount of data (bits) to read from interprocess sockets
78
     *
79
     * @var     int
80
     */
81
    private $max_result_bytes_in_multithread = null;
82
83
    /**
84
     * Maximum child runtime (after this interval parent process will start to kill)
85
     *
86
     * @var     array
87
     */
88
    private $max_childs_runtime = null;
89
90
    /**
91
     * Array of ipc inter-processes sockets
92
     *
93
     * @var     array
94
     */
95
    private $ipc_array = array();
96
    
97
    /**
98
     * Number of processes waiting in the queue
99
     *
100
     * @var     int
101
     */
102
    private $queued_processes = 0;
103
104
    /**
105
     * Array of chunks from current jobs
106
     * 
107
     * It is used to divide jobs in groups and generate the queue
108
     *
109
     * @var     array
110
     */
111
    private $queued_chunks = array();
112
113
    /**
114
     * Time between SIGTERM and SIGKILL when child is killed
115
     * 
116
     * Just to give it a chance
117
     *
118
     * @var     int
119
     */
120
    static private $lagger_timeout = 5;
121
122
    /**
123
     * Runner constructor
124
     *
125
     * @param   \Comodojo\Extender\Debug   $logger                             Logger instance
126
     * @param   bool                      $multithread                        Enable/disable multithread mode
127
     * @param   int                       $max_result_bytes_in_multithread    Max result bytes
128
     * @param   int                       $max_childs_runtime                 Max child runtime
129
     */
130 12
    final public function __construct(Logger $logger, $multithread, $max_result_bytes_in_multithread, $max_childs_runtime) {
131
132 12
        $this->logger = $logger;
133
134 12
        $this->multithread = $multithread;
135
136 12
        $this->max_result_bytes_in_multithread = $max_result_bytes_in_multithread;
137
138 12
        $this->max_childs_runtime = $max_childs_runtime;
0 ignored issues
show
Documentation Bug introduced by
It seems like $max_childs_runtime of type integer is incompatible with the declared type array of property $max_childs_runtime.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
139
140 12
    }
141
142
    /**
143
     * Add job to current queue
144
     *
145
     * @param   \Comodojo\Extender\Job\Job  $job    An instance of \Comodojo\Extender\Job\Job
146
     *
147
     * @return  string  A job unique identifier
148
     */
149
    final public function addJob(\Comodojo\Extender\Job\Job $job) {
150
151
        $uid = self::getJobUid();
152
153
        try {
154
155
            $class = $job->getClass();
156
157
            if ( class_exists($class) === false ) throw new Exception("Task cannot be loaded");
158
159
            $this->jobs[$uid] = array(
160
                "name"      =>  $job->getName(),
161
                "id"        =>  $job->getId(),
162
                "parameters"=>  $job->getParameters(),
163
                "task"      =>  $job->getTask(),
164
                "class"     =>  $class
165
            );
166
167
        } catch (Exception $e) {
168
169
            $this->logger->error('Error including job', array(
170
                "JOBUID"=> $uid,
171
                "ERROR" => $e->getMessage(),
172
                "ERRID" => $e->getCode()
173
            ));
174
175
            return false;
176
177
        }
178
179
        return $uid;
180
181
    }
182
183
    /**
184
     * Free (reset) runner instance
185
     *
186
     */
187
    final public function free() {
188
189
        $this->jobs = array();
190
        $this->completed_processes = array();
191
        $this->running_processes = array();
192
        $this->forked_processes = array();
193
        $this->ipc_array = array();
194
195
    }
196
197
    /**
198
     * Execute job(s) in current queue
199
     *
200
     * @return  array   An array of completed processes
201
     */
202
    final public function run() {
203
204
        // if jobs > concurrent jobs, create the queue
205
206
        if ( $this->multithread AND defined("EXTENDER_MAX_CHILDS") AND sizeof($this->jobs) > EXTENDER_MAX_CHILDS AND EXTENDER_MAX_CHILDS != 0 ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
207
208
            $this->queued_processes = sizeof($this->jobs);
209
210
            // split jobs in chunks
211
212
            $this->queued_chunks = array_chunk($this->jobs, EXTENDER_MAX_CHILDS, true);
213
214
            // exec chunks, one at time
215
216
            foreach ( $this->queued_chunks as $chunk ) {
217
                
218
                $this->queued_processes = $this->queued_processes - sizeof($chunk);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->queued_processes - sizeof($chunk) can also be of type double. However, the property $queued_processes is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
219
220
                Queue::dump(sizeof($chunk), $this->queued_processes);
221
222
                $this->forker($chunk);
223
224 View Code Duplication
                if ( $this->multithread ) $this->logger->info("Extender forked ".sizeof($this->forked_processes)." process(es) in the running queue", $this->forked_processes);
0 ignored issues
show
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...
225
226
                $this->catcher();
227
228
                $this->forked_processes = array();
229
230
            }
231
232
        } else {
233
234
            Queue::dump(sizeof($this->jobs), 0);
235
236
            $this->forker($this->jobs);
237
238 View Code Duplication
            if ( $this->multithread ) $this->logger->info("Extender forked ".sizeof($this->forked_processes)." process(es) in the running queue", $this->forked_processes);
0 ignored issues
show
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...
239
240
            $this->catcher();
241
242
        }
243
244
        // Dump the end queue status
245
246
        Queue::dump(sizeof($this->running_processes), $this->queued_processes);
247
248
        return $this->completed_processes;
249
250
    }
251
252
    /**
253
     * Terminate all running processes
254
     *
255
     * @param   int     Parent process pid
256
     * @param integer $parent_pid
257
     */
258
    final public function killAll($parent_pid) {
259
260
        foreach ( $this->running_processes as $pid => $process ) {
261
262
            // if ( $pid !== $parent_pid) posix_kill($pid, SIGTERM);
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% 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...
263
            if ( $pid !== $parent_pid ) self::kill($pid);
264
265
        }
266
267
    }
268
269
    /**
270
     * Fork or exec some jobs
271
     *
272
     * @param   array   $jobs   A subset of $this->jobs to process in a round
273
     */
274
    private function forker($jobs) {
275
276
        foreach ( $jobs as $jobUid => $job ) {
277
            
278
            if ( $this->multithread AND sizeof($jobs) > 1 ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
279
280
                $status = $this->runMultithread($jobUid);
281
282
                if ( !is_null($status["pid"]) ) {
283
284
                    $this->running_processes[$status["pid"]] = array($status["name"], $status["uid"], $status["timestamp"], $status["id"]);
285
286
                    array_push($this->forked_processes, $status["pid"]);
287
288
                }
289
290
            } else {
291
292
                $status = $this->runSinglethread($jobUid);
293
294
                array_push($this->completed_processes, $status);
295
296
            }
297
298
        }
299
300
    }
301
302
    /**
303
     * Catch results from completed jobs
304
     * 
305
     */
306
    private function catcher() {
307
308
        $exec_time = microtime(true);
309
310
        while ( !empty($this->running_processes) ) {
311
312
            foreach ( $this->running_processes as $pid => $job ) {
313
314
                //$job[0] is name
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...
315
                //$job[1] is uid
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...
316
                //$job[2] is start timestamp
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% 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...
317
                //$job[3] is job id
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% 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...
318
319
                if ( !self::isRunning($pid) ) {
320
321
                    list($reader, $writer) = $this->ipc_array[$job[1]];
322
323
                    socket_close($writer);
324
                    
325
                    $parent_result = socket_read($reader, $this->max_result_bytes_in_multithread, PHP_BINARY_READ);
326
327
                    if ( $parent_result === false ) {
328
329
                        $this->logger->error("socket_read() failed. Reason: ".socket_strerror(socket_last_error($reader)));
330
331
                        array_push($this->completed_processes, Array(
332
                            null,
333
                            $job[0], //$job_name,
334
                            false,
335
                            $job[2], //$start_timestamp,
336
                            null,
337
                            "socket_read() failed. Reason: ".socket_strerror(socket_last_error($reader)),
338
                            $job[3],
339
                            null
340
                        ));
341
342
                        $status = 'ERROR';
343
344
                    } else {
345
346
                        $result = unserialize(rtrim($parent_result));
347
348
                        socket_close($reader);
349
                        
350
                        array_push($this->completed_processes, Array(
351
                            $pid,
352
                            $job[0], //$job_name,
353
                            $result["success"],
354
                            $job[2], //$start_timestamp,
355
                            $result["timestamp"],
356
                            $result["result"],
357
                            $job[3],
358
                            $result["worklogid"]
359
                        ));
360
361
                        $status = $result["success"] ? 'success' : 'failure';
362
363
                    }
364
                    
365
                    $this->logger->notice("Job ".$job[0]."(".$job[3].") ends with ".$status);
366
367
                    unset($this->running_processes[$pid]);
368
369
                    $this->logger->info("Removed pid ".$pid." from the running queue, job terminated with ".$status);
370
371
                } else {
372
373
                    $current_time = microtime(true);
374
375
                    if ( $current_time > $exec_time + $this->max_childs_runtime ) {
376
377
                        $this->logger->warning("Killing pid ".$pid." due to maximum exec time reached (>".$this->max_childs_runtime.")", array(
378
                            "START_TIME"    => $exec_time,
379
                            "CURRENT_TIME"  => $current_time,
380
                            "MAX_RUNTIME"   => $this->max_childs_runtime
381
                        ));
382
383
                        $kill = self::kill($pid);
384
385
                        if ( $kill ) $this->logger->warning("Pid ".$pid." killed");
386
387
                        else $this->logger->warning("Pid ".$pid." could not be killed");
388
389
                        list($reader, $writer) = $this->ipc_array[$job[1]];
390
391
                        socket_close($writer);
392
                        socket_close($reader);
393
                        
394
                        array_push($this->completed_processes, Array(
395
                            $pid,
396
                            $job[0], //$job_name,
397
                            false,
398
                            $job[2], //$start_timestamp,
399
                            $current_time,
400
                            "Job ".$job[0]." killed due to maximum exec time reached (>".$this->max_childs_runtime.")",
401
                            $job[3],
402
                            null
403
                        ));
404
405
                        $this->logger->notice("Job ".$job[0]."(".$job[3].") ends with error");
406
407
                        unset($this->running_processes[$pid]);
408
409
                        $this->logger->info("Removed pid ".$pid." from the running queue, job terminated with error");
410
411
                    }
412
413
                }
414
415
            }
416
417
        }
418
419
    }
420
421
    /**
422
     * Run job in singlethread mode
423
     *
424
     * @param   string  Job unique identifier
425
     *
426
     * @return  array   {[pid],[name],[success],[start],[end],[result],[id]}
427
     */
428
    private function runSinglethread($jobUid) {
429
430
        $job = $this->jobs[$jobUid];
431
432
        // get job start timestamp
433
        $start_timestamp = microtime(true);
434
435
        $this->logger->notice("Starting job ".$job['name']."(".$job['id'].")");
436
437
        $name = $job['name'];
438
439
        $id = $job['id'];
440
441
        $parameters = $job['parameters'];
442
443
        $task = $job['task'];
0 ignored issues
show
$task is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
444
445
        $task_class = $job['class'];
446
447
        try {
448
449
            // create a task instance
450
451
            $thetask = new $task_class($parameters, $this->logger, null, $name, $start_timestamp, false, $id);
452
453
            // get the task pid (we are in singlethread mode)
454
455
            $pid = $thetask->getPid();
456
457
            // run task
458
459
            $result = $thetask->start();
460
        
461
        } catch (TaskException $te) {
462
463
            $this->logger->notice("Job ".$job['name']."(".$job['id'].") ends with error");
464
        
465
            return array($pid, $name, false, $start_timestamp, $te->getEndTimestamp(), $te->getMessage(), $id, $te->getWorklogId());
0 ignored issues
show
The variable $pid does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
466
        
467
        } catch (Exception $e) {
468
469
            $this->logger->notice("Job ".$job['name']."(".$job['id'].") ends with error");
470
        
471
            return array($pid, $name, false, $start_timestamp, null, $e->getMessage(), $id, null);
472
        
473
        }
474
475
        $this->logger->notice("Job ".$job['name']."(".$job['id'].") ends with ".($result["success"] ? "success" : "failure"));
476
477
        return array($pid, $name, $result["success"], $start_timestamp, $result["timestamp"], $result["result"], $id, $result["worklogid"]);
478
479
    }
480
481
    /**
482
     * Run job in singlethread mode
483
     *
484
     * @param   string  Job unique identifier
485
     *
486
     * @return  array   {[pid],[name],[success],[start],[end],[result],[id]}
487
     */
488
    private function runMultithread($jobUid) {
489
490
        $job = $this->jobs[$jobUid];
491
492
        // get job start timestamp
493
        $start_timestamp = microtime(true);
494
495
        $this->logger->notice("Starting job ".$job['name']."(".$job['id'].")");
496
497
        $name = $job['name'];
498
499
        $id = $job['id'];
500
501
        $parameters = $job['parameters'];
502
503
        $task = $job['task'];
0 ignored issues
show
$task is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
504
505
        $task_class = $job['class'];
506
507
        $this->ipc_array[$jobUid] = array();
508
509
        // create a comm socket
510
        $socket = socket_create_pair(AF_UNIX, SOCK_STREAM, 0, $this->ipc_array[$jobUid]);
511
512
        if ( $socket === false ) {
513
514
            $this->logger->error("No IPC communication, aborting", array(
515
                "JOBUID"=> $jobUid,
516
                "ERROR" => socket_strerror(socket_last_error()),
517
                "ERRID" => null
518
            ));
519
520
            array_push($this->completed_processes, array(
521
                null,
522
                $name,
523
                false,
524
                $start_timestamp,
525
                microtime(true),
526
                'No IPC communication, exiting - '.socket_strerror(socket_last_error()),
527
                $id,
528
                null
529
            ));
530
531
            return array(
532
                "pid"       =>  null,
533
                "name"      =>  $name,
534
                "uid"       =>  $jobUid,
535
                "timestamp" =>  $start_timestamp,
536
                "id"        =>  $id
537
            );
538
539
        }
540
541
        list($reader, $writer) = $this->ipc_array[$jobUid];
542
543
        $pid = pcntl_fork();
544
545
        if ( $pid == -1 ) {
546
547
            $this->logger->error("Could not fok job, aborting");
548
549
            array_push($this->completed_processes, Array(
550
                null,
551
                $name,
552
                false,
553
                $start_timestamp,
554
                microtime(true),
555
                'Could not fok job',
556
                $id,
557
                null
558
            ));
559
560
        } elseif ( $pid ) {
561
562
            //PARENT will take actions on processes later
563
564
            self::adjustNiceness($pid, $this->logger);
565
566
        } else {
567
            
568
            socket_close($reader);
569
570
            $thetask = new $task_class($parameters, $this->logger, null, $name, $start_timestamp, true, $id);
571
572
            try {
573
574
                $result = $thetask->start();
575
576
                $return = serialize(array(
577
                    "success"   => $result["success"],
578
                    "result"    => $result["result"],
579
                    "timestamp" => $result["timestamp"],
580
                    "worklogid" => $result["worklogid"]
581
                ));
582
583
                $exit = 0;
584
585
            } catch (TaskException $te) {
586
587
                $return = serialize(Array(
588
                    "success"   => false,
589
                    "result"    => $te->getMessage(),
590
                    "timestamp" => $te->getEndTimestamp(),
591
                    "worklogid" => $te->getWorklogId()
592
                ));
593
594
                $exit = 1;
595
596
            } catch (Exception $e) {
597
598
                $return = serialize(Array(
599
                    "success"   => false,
600
                    "result"    => $e->getMessage(),
601
                    "timestamp" => microtime(true),
602
                    "worklogid" => null
603
                ));
604
                
605
                $exit = 1;
606
607
            }
608
609
            if ( socket_write($writer, $return, strlen($return)) === false ) {
610
611
                $this->logger->error("socket_write() failed ", array(
612
                    "ERROR" => socket_strerror(socket_last_error($writer))
613
                ));
614
615
            }
616
617
            socket_close($writer);
618
619
            exit($exit);
620
621
        }
622
623
        return array(
624
            "pid"       =>  $pid == -1 ? null : $pid,
625
            "name"      =>  $name,
626
            "uid"       =>  $jobUid,
627
            "id"        =>  $id,
628
            "timestamp" =>  $start_timestamp
629
        );
630
631
    }
632
633
    /**
634
     * Return true if process is still running, false otherwise
635
     * 
636
     * @return  bool
637
     */
638
    private static function isRunning($pid) {
639
640
        return (pcntl_waitpid($pid, $status, WNOHANG) === 0);
0 ignored issues
show
The variable $status does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
641
642
    }
643
644
    /**
645
     * Kill a child process
646
     * 
647
     * @return  bool
648
     */
649
    private static function kill($pid) {
650
651
        $kill_time = time() + self::$lagger_timeout;
652
653
        $term = posix_kill($pid, SIGTERM);
654
655
        while ( time() < $kill_time ) {
656
            
657
            if ( !self::isRunning($pid) ) return $term;
658
659
        }
660
661
        return posix_kill($pid, SIGKILL);
662
663
    }
664
665
    /**
666
     * Get a job unique identifier
667
     * 
668
     * @return  string
669
     */
670
    private static function getJobUid() {
671
672
        return md5(uniqid(rand(), true), 0);
673
674
    }
675
676
    /**
677
     * Change child process priority according to EXTENDER_NICENESS
678
     *
679
     */
680
    private static function adjustNiceness($pid, $logger) {
681
682
        if ( Checks::multithread() AND defined("EXTENDER_CHILD_NICENESS") ) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
683
684
            $niceness = pcntl_setpriority($pid, EXTENDER_CHILD_NICENESS);
685
686
            if ( $niceness == false ) $logger->warning("Unable to set child process ".$pid." niceness to ".EXTENDER_CHILD_NICENESS);
687
688
        }
689
690
    }
691
692
}
693