Completed
Push — master ( ce5e3e...79ead5 )
by Joao
02:54
created

ForkHandler::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 2
1
<?php
2
3
namespace ByJG\PHPThread\Handler;
4
5
use ByJG\Cache\CacheContext;
6
use ByJG\Cache\Engine\ShmopCacheEngine;
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
    private $maxSharedMemorySize = null;
25
    private $defaultPermission = null;
26
27
    /**
28
     * constructor method
29
     *
30
     * @param int $maxSharedMemorySize
31
     * @param string $defaultPermission
32
     */
33
    public function __construct($maxSharedMemorySize = 0x100000, $defaultPermission = '0700')
34
    {
35
        if (!function_exists('pcntl_fork')) {
36
            throw new RuntimeException('PHP was compiled without --enable-pcntl or you are running on Windows.');
37
        }
38
39
        $this->maxSharedMemorySize = $maxSharedMemorySize;
40
        $this->defaultPermission = $defaultPermission;
41
    }
42
43
    /**
44
     * Private function for set the method will be forked;
45
     *
46
     * @param callable $callable string with the function name or a array with the instance and the method name
47
     * @return mixed|void
48
     */
49
    public function setCallable(callable $callable)
50
    {
51
        $this->callable = $callable;
52
    }
53
54
    /**
55
     * Start the thread
56
     *
57
     * @throws RuntimeException
58
     */
59
    public function execute()
60
    {
61
        $this->threadKey = 'thread_' . rand(1000, 9999) . rand(1000, 9999) . rand(1000, 9999) . rand(1000, 9999);
62
63
        if (($this->pid = pcntl_fork()) == -1) {
64
            throw new RuntimeException('Couldn\'t fork the process');
65
        }
66
67
        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...
68
            // Parent
69
            //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...
70
        } else {
71
            // Child.
72
            pcntl_signal(SIGTERM, array($this, 'signalHandler'));
73
            $args = func_get_args();
74
75
            $callable = $this->callable;
76
            if (!is_string($callable)) {
77
                $callable = (array) $this->callable;
78
            }
79
80
            try {
81
                $return = call_user_func_array($callable, (array)$args);
82
83
                if (!is_null($return)) {
84
                    $this->saveResult($return);
85
                }
86
            // Executed only in PHP 7, will not match in PHP 5.x
87
            } 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...
88
                $this->saveResult($t);
89
            // Executed only in PHP 5. Remove when PHP 5.x is no longer necessary.
90
            } catch (\Exception $ex) {
91
                $this->saveResult($ex);
92
            }
93
94
            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...
95
        }
96
    }
97
98
    /**
99
     * @return \ByJG\Cache\Engine\ShmopCacheEngine
100
     */
101
    protected function getSharedMemoryEngine()
102
    {
103
        return new ShmopCacheEngine(
104
            [
105
                'max-size' => $this->maxSharedMemorySize,
106
                'default-permission' => $this->defaultPermission
107
            ]
108
        );
109
    }
110
111
    /**
112
     * Save the thread result in a shared memory block
113
     *
114
     * @param mixed $object Need to be serializable
115
     */
116
    protected function saveResult($object)
117
    {
118
        $this->getSharedMemoryEngine()->set($this->threadKey, $object);
119
    }
120
121
    /**
122
     * Get the thread result from the shared memory block and erase it
123
     *
124
     * @return mixed
125
     * @throws \Error
126
     * @throws object
127
     */
128
    public function getResult()
129
    {
130
        if (is_null($this->threadKey)) {
131
            return null;
132
        }
133
134
        $key = $this->threadKey;
135
        $this->threadKey = null;
136
137
        $cache = $this->getSharedMemoryEngine();
138
        $result = $cache->get($key);
139
        $cache->release($key);
140
141 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...
142
             throw $result;
143
        }
144
145
        return $result;
146
    }
147
148
    /**
149
     * Kill a thread
150
     *
151
     * @param int $signal
152
     * @param bool $wait
153
     */
154
    public function stop($signal = SIGKILL, $wait = false)
155
    {
156
        if ($this->isAlive()) {
157
            posix_kill($this->pid, $signal);
158
159
            $status = null;
160
            if ($wait) {
161
                pcntl_waitpid($this->pid, $status);
162
            }
163
        }
164
    }
165
166
    /**
167
     * Check if the forked process is alive
168
     * @return bool
169
     */
170
    public function isAlive()
171
    {
172
        $status = null;
173
        return (pcntl_waitpid($this->pid, $status, WNOHANG) === 0);
174
    }
175
176
    /**
177
     * Handle the signal to the thread
178
     *
179
     * @param int $signal
180
     */
181
    private function signalHandler($signal)
182
    {
183
        switch ($signal) {
184
            case SIGTERM:
185
                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...
186
        }
187
    }
188
189
    public function waitFinish()
190
    {
191
        pcntl_wait($status);
192
    }
193
}
194