Issues (4)

src/Handler/ForkHandler.php (3 issues)

1
<?php
2
3
namespace ByJG\PHPThread\Handler;
4
5
use ByJG\Cache\CacheContext;
0 ignored issues
show
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 $closure;
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 \Closure $closure
55
     * @return mixed|void
56
     */
57 3
    public function setClosure(\Closure $closure)
58
    {
59 3
        $this->closure = $closure;
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
            try {
84
                $return = call_user_func_array($this->closure, (array)$args);
85
86
                if (!is_null($return)) {
87
                    $this->saveResult($return);
88
                }
89
            // Executed only in PHP 7, will not match in PHP 5.x
90
            } catch (\Throwable $t) {
91
                $this->saveResult($t);
92
            // Executed only in PHP 5. Remove when PHP 5.x is no longer necessary.
93
            } catch (\Exception $ex) {
94
                $this->saveResult($ex);
95
            }
96
97
            exit(0);
0 ignored issues
show
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...
98
        }
99
    }
100
101
    /**
102
     * @return \ByJG\Cache\Engine\ShmopCacheEngine
103
     */
104 2
    protected function getSharedMemoryEngine()
105
    {
106 2
        return new ShmopCacheEngine(
107
            [
108 2
                'max-size' => $this->maxSharedMemorySize,
109 2
                'default-permission' => $this->defaultPermission
110
            ]
111
        );
112
    }
113
114
    /**
115
     * Save the thread result in a shared memory block
116
     *
117
     * @param mixed $object Need to be serializable
118
     */
119
    protected function saveResult($object)
120
    {
121
        $this->getSharedMemoryEngine()->set($this->threadKey, $object);
122
    }
123
124
    /**
125
     * Get the thread result from the shared memory block and erase it
126
     *
127
     * @return mixed
128
     * @throws \Error
129
     * @throws object
130
     */
131 2
    public function getResult()
132
    {
133 2
        if (is_null($this->threadKey)) {
134
            return null;
135
        }
136
137 2
        $key = $this->threadKey;
138 2
        $this->threadKey = null;
139
140 2
        $cache = $this->getSharedMemoryEngine();
141 2
        $result = $cache->get($key);
142 2
        $cache->release($key);
143
144 2
        if (is_object($result) &&
145
            ($result instanceof \Exception
146
                || $result instanceof \Throwable
147
                || $result instanceof \Error
148
            )
149
        ) {
150
            throw $result;
151
        }
152
153 2
        return $result;
154
    }
155
156
    /**
157
     * Kill a thread
158
     *
159
     * @param int $signal
160
     * @param bool $wait
161
     */
162 1
    public function stop($signal = SIGKILL, $wait = false)
163
    {
164 1
        if ($this->isAlive()) {
165 1
            posix_kill($this->pid, $signal);
166
167 1
            $status = null;
168 1
            if ($wait) {
169
                pcntl_waitpid($this->pid, $status);
170
            }
171
        }
172
    }
173
174
    /**
175
     * Check if the forked process is alive
176
     * @return bool
177
     */
178 3
    public function isAlive()
179
    {
180 3
        $status = null;
181 3
        return (pcntl_waitpid($this->pid, $status, WNOHANG) === 0);
182
    }
183
184
    /**
185
     * Handle the signal to the thread
186
     *
187
     * @param int $signal
188
     */
189
    private function signalHandler($signal)
190
    {
191
        switch ($signal) {
192
            case SIGTERM:
193
                exit(0);
0 ignored issues
show
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...
194
        }
195
    }
196
197 2
    public function waitFinish()
198
    {
199 2
        pcntl_wait($status);
200 2
        if ($this->isAlive()) {
201 2
            $this->waitFinish();
202
        }
203
    }
204
205
    public function getClassName()
206
    {
207
        return ForkHandler::class;
208
    }
209
}
210