Completed
Push — master ( 64a44a...ce5e3e )
by Joao
04:39
created

ForkHandler::execute()   C

Complexity

Conditions 7
Paths 18

Size

Total Lines 38
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 1
Metric Value
c 5
b 0
f 1
dl 0
loc 38
rs 6.7272
cc 7
eloc 20
nc 18
nop 0
1
<?php
2
3
namespace ByJG\PHPThread\Handler;
4
5
use ByJG\Cache\CacheContext;
6
use InvalidArgumentException;
7
use RuntimeException;
8
9
/**
10
 * Native Implementation of Threads in PHP.
11
 *
12
 * A class to spawn a thread. Only works in *nix environments,
13
 * as Windows platform is missing libpcntl.
14
 *
15
 * Forks the process.
16
 */
17
class ForkHandler implements ThreadInterface
18
{
19
    protected $threadKey;
20
    private $callable;
21
    private $pid;
22
23
    /**
24
     * constructor method
25
     *
26
     * @throws RuntimeException
27
     * @throws InvalidArgumentException
28
     */
29
    public function __construct()
30
    {
31
        if (!function_exists('pcntl_fork')) {
32
            throw new RuntimeException('PHP was compiled without --enable-pcntl or you are running on Windows.');
33
        }
34
35
        /** Check if is configured */
36
        CacheContext::factory('phpthread');
37
    }
38
39
    /**
40
     * Private function for set the method will be forked;
41
     *
42
     * @param callable $callable string with the function name or a array with the instance and the method name
43
     * @return mixed|void
44
     */
45
    public function setCallable(callable $callable)
46
    {
47
        $this->callable = $callable;
48
    }
49
50
    /**
51
     * Start the thread
52
     *
53
     * @throws RuntimeException
54
     */
55
    public function execute()
56
    {
57
        $this->threadKey = 'thread_' . rand(1000, 9999) . rand(1000, 9999) . rand(1000, 9999) . rand(1000, 9999);
58
59
        if (($this->pid = pcntl_fork()) == -1) {
60
            throw new RuntimeException('Couldn\'t fork the process');
61
        }
62
63
        if ($this->pid) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
64
            // Parent
65
            //pcntl_wait($status); //Protect against Zombie children
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% 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...
66
        } else {
67
            // Child.
68
            pcntl_signal(SIGTERM, array($this, 'signalHandler'));
69
            $args = func_get_args();
70
71
            $callable = $this->callable;
72
            if (!is_string($callable)) {
73
                $callable = (array) $this->callable;
74
            }
75
76
            try {
77
                $return = call_user_func_array($callable, (array)$args);
78
79
                if (!is_null($return)) {
80
                    $this->saveResult($return);
81
                }
82
            // Executed only in PHP 7, will not match in PHP 5.x
83
            } catch (\Throwable $t) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
84
                $this->saveResult($t);
85
            // Executed only in PHP 5. Remove when PHP 5.x is no longer necessary.
86
            } catch (\Exception $ex) {
87
                $this->saveResult($ex);
88
            }
89
90
            exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method execute() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
91
        }
92
    }
93
94
    /**
95
     * Save the thread result in a shared memory block
96
     *
97
     * @param mixed $object Need to be serializable
98
     */
99
    protected function saveResult($object)
100
    {
101
        $cache = CacheContext::factory('phpthread');
102
        $cache->set($this->threadKey, $object);
103
    }
104
105
    /**
106
     * Get the thread result from the shared memory block and erase it
107
     *
108
     * @return mixed
109
     * @throws \Error
110
     * @throws object
111
     */
112
    public function getResult()
113
    {
114
        if (is_null($this->threadKey)) {
115
            return null;
116
        }
117
118
        $key = $this->threadKey;
119
        $this->threadKey = null;
120
121
        $cache = CacheContext::factory('phpthread');
122
        $result = $cache->get($key);
123
        $cache->release($key);
124
125 View Code Duplication
        if (is_object($result) && (is_subclass_of($result, '\\Error') || is_subclass_of($result, '\\Exception'))) {
0 ignored issues
show
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...
126
             throw $result;
127
        }
128
129
        return $result;
130
    }
131
132
    /**
133
     * Kill a thread
134
     *
135
     * @param int $signal
136
     * @param bool $wait
137
     */
138
    public function stop($signal = SIGKILL, $wait = false)
139
    {
140
        if ($this->isAlive()) {
141
            posix_kill($this->pid, $signal);
142
143
            $status = null;
144
            if ($wait) {
145
                pcntl_waitpid($this->pid, $status);
146
            }
147
        }
148
    }
149
150
    /**
151
     * Check if the forked process is alive
152
     * @return bool
153
     */
154
    public function isAlive()
155
    {
156
        $status = null;
157
        return (pcntl_waitpid($this->pid, $status, WNOHANG) === 0);
158
    }
159
160
    /**
161
     * Handle the signal to the thread
162
     *
163
     * @param int $signal
164
     */
165
    private function signalHandler($signal)
166
    {
167
        switch ($signal) {
168
            case SIGTERM:
169
                exit(0);
0 ignored issues
show
Coding Style Compatibility introduced by
The method signalHandler() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
170
        }
171
    }
172
173
    public function waitFinish()
174
    {
175
        pcntl_wait($status);
0 ignored issues
show
Bug introduced by
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...
176
    }
177
}
178