Passed
Push — master ( f982b7...ed48f7 )
by Joao
02:03
created

ForkHandler::getSharedMemoryEngine()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
namespace ByJG\PHPThread\Handler;
4
5
use ByJG\Cache\CacheContext;
0 ignored issues
show
Bug introduced by
The type ByJG\Cache\CacheContext was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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 3
    public function __construct($maxSharedMemorySize, $defaultPermission)
34
    {
35 3
        if (!function_exists('pcntl_fork')) {
36
            throw new RuntimeException('PHP was compiled without --enable-pcntl or you are running on Windows.');
37
        }
38
39 3
        if (empty($maxSharedMemorySize)) {
40 3
            $maxSharedMemorySize = 0x100000;
41
        }
42
43 3
        if (empty($defaultPermission)) {
44 3
            $defaultPermission = '0700';
45
        }
46
47 3
        $this->maxSharedMemorySize = $maxSharedMemorySize;
48 3
        $this->defaultPermission = $defaultPermission;
49
    }
50
51
    /**
52
     * Private function for set the method will be forked;
53
     *
54
     * @param callable $callable string with the function name or a array with the instance and the method name
55
     * @return mixed|void
56
     */
57 3
    public function setCallable(callable $callable)
58
    {
59 3
        $this->callable = $callable;
60
    }
61
62
    /**
63
     * Start the thread
64
     *
65
     * @throws RuntimeException
66
     */
67 3
    public function execute()
68
    {
69 3
        $this->threadKey = 'thread_' . rand(1000, 9999) . rand(1000, 9999) . rand(1000, 9999) . rand(1000, 9999);
70
71 3
        if (($this->pid = pcntl_fork()) == -1) {
72
            throw new RuntimeException('Couldn\'t fork the process');
73
        }
74
75 3
        if ($this->pid) {
76
            // Parent
77
            //pcntl_wait($status); //Protect against Zombie children
78
        } else {
79
            // Child.
80
            pcntl_signal(SIGTERM, array($this, 'signalHandler'));
81
            $args = func_get_args();
82
83
            $callable = $this->callable;
84
            if (!is_string($callable)) {
85
                $callable = (array) $this->callable;
86
            }
87
88
            try {
89
                $return = call_user_func_array($callable, (array)$args);
90
91
                if (!is_null($return)) {
92
                    $this->saveResult($return);
93
                }
94
            // Executed only in PHP 7, will not match in PHP 5.x
95
            } catch (\Throwable $t) {
96
                $this->saveResult($t);
97
            // Executed only in PHP 5. Remove when PHP 5.x is no longer necessary.
98
            } catch (\Exception $ex) {
99
                $this->saveResult($ex);
100
            }
101
102
            exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
103
        }
104
    }
105
106
    /**
107
     * @return \ByJG\Cache\Engine\ShmopCacheEngine
108
     */
109 2
    protected function getSharedMemoryEngine()
110
    {
111 2
        return new ShmopCacheEngine(
112
            [
113 2
                'max-size' => $this->maxSharedMemorySize,
114 2
                'default-permission' => $this->defaultPermission
115
            ]
116
        );
117
    }
118
119
    /**
120
     * Save the thread result in a shared memory block
121
     *
122
     * @param mixed $object Need to be serializable
123
     */
124
    protected function saveResult($object)
125
    {
126
        $this->getSharedMemoryEngine()->set($this->threadKey, $object);
127
    }
128
129
    /**
130
     * Get the thread result from the shared memory block and erase it
131
     *
132
     * @return mixed
133
     * @throws \Error
134
     * @throws object
135
     */
136 2
    public function getResult()
137
    {
138 2
        if (is_null($this->threadKey)) {
139
            return null;
140
        }
141
142 2
        $key = $this->threadKey;
143 2
        $this->threadKey = null;
144
145 2
        $cache = $this->getSharedMemoryEngine();
146 2
        $result = $cache->get($key);
147 2
        $cache->release($key);
148
149 2
        if (is_object($result) &&
150
            ($result instanceof \Exception
151
                || $result instanceof \Throwable
152
                || $result instanceof \Error
153
            )
154
        ) {
155
            throw $result;
156
        }
157
158 2
        return $result;
159
    }
160
161
    /**
162
     * Kill a thread
163
     *
164
     * @param int $signal
165
     * @param bool $wait
166
     */
167 1
    public function stop($signal = SIGKILL, $wait = false)
168
    {
169 1
        if ($this->isAlive()) {
170 1
            posix_kill($this->pid, $signal);
171
172 1
            $status = null;
173 1
            if ($wait) {
174
                pcntl_waitpid($this->pid, $status);
175
            }
176
        }
177
    }
178
179
    /**
180
     * Check if the forked process is alive
181
     * @return bool
182
     */
183 3
    public function isAlive()
184
    {
185 3
        $status = null;
186 3
        return (pcntl_waitpid($this->pid, $status, WNOHANG) === 0);
187
    }
188
189
    /**
190
     * Handle the signal to the thread
191
     *
192
     * @param int $signal
193
     */
194
    private function signalHandler($signal)
195
    {
196
        switch ($signal) {
197
            case SIGTERM:
198
                exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
199
        }
200
    }
201
202 2
    public function waitFinish()
203
    {
204 2
        pcntl_wait($status);
205 2
        if ($this->isAlive()) {
206 2
            $this->waitFinish();
207
        }
208
    }
209
210
    public function getClassName()
211
    {
212
        return ForkHandler::class;
213
    }
214
}
215