Completed
Push — master ( f7aa2f...852d76 )
by Sébastien
04:02
created

StandaloneServer::clearPidFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0175

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 7
cts 8
cp 0.875
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 0
crap 3.0175
1
<?php
2
3
namespace PjbServer\Tools;
4
5
use PjbServer\Tools\Network\PortTester;
6
use PjbServer\Tools\System\Process;
7
use Psr\Log\LoggerInterface;
8
use Psr\Log\NullLogger;
9
10
11
class StandaloneServer
12
{
13
14
    /**
15
     * @var int
16
     */
17
    protected $port;
18
19
    /**
20
     *
21
     * @var StandaloneServer\Config
22
     */
23
    protected $config;
24
25
    /**
26
     * Tells whether the standalone server is started
27
     * @var boolean
28
     */
29
    protected $started = false;
30
31
32
    /**
33
     *
34
     * @var PortTester
35
     */
36
    protected $portTester;
37
38
39
    /**
40
     * @var LoggerInterface
41
     */
42
    protected $logger;
43
44
45
    /**
46
     * @var Process
47
     */
48
    protected $process;
49
50
    /**
51
     * Constructor
52
     *
53
     * <code>
54
     *
55
     * $config = array(
56
     *      'port' => 8089,
57
     *
58
     *      // optionally
59
     *      'server_jar' => 'path/to/JavaBridge.jar'
60
     *      'java_bin' => 'path/to/java'
61
     * );
62
     * $server = new StandaloneServer($config);
63
     *
64
     * </code>
65
     *
66
     * @throws Exception\InvalidArgumentException
67
     * @param StandaloneServer\Config $config
68
     * @param LoggerInterface $logger
69
     *
70
     */
71 22
    public function __construct(StandaloneServer\Config $config, LoggerInterface $logger=null)
72
    {
73 22
        $this->config = $config;
74
75 22
        $curl_available = function_exists('curl_version');
76
77 22
        $this->portTester = new PortTester([
78 22
            'backend' => $curl_available ? PortTester::BACKEND_CURL : PortTester::BACKEND_STREAM_SOCKET,
79
            // Close timout ms could be adjusted for your system
80
            // It prevent that port availability testing does
81
            // not close quickly enough to allow standalone server binding
82 22
            'close_timeout_ms' => $curl_available ? null : 300
83 22
        ]);
84 22
        if ($logger === null) {
85 15
            $logger = new NullLogger();
86 15
        }
87 22
        $this->logger = $logger;
88
89 22
        $this->process = new Process();
90 22
    }
91
92
    /**
93
     * Start the standalone server
94
     *
95
     * @throws Exception\RuntimeException
96
     *
97
     * @param int $timeout_ms maximum number of milliseconds to wait for server start
98
     * @return void
99
     */
100 14
    public function start($timeout_ms = 3000)
101
    {
102 14
        $port = $this->config->getPort();
103
104 14
        $this->logger->notice("Starting standalone server on port $port.");
105
106 14
        if ($this->isStarted()) {
107 2
            $this->logger->notice("Standalone server already running, skipping start.");
108 2
            return;
109
        }
110
111 14
        if (!$this->portTester->isAvailable('localhost', $port, 'http')) {
112 1
            $msg = "Cannot start server on port '$port', it's already in use.";
113 1
            $this->logger->error("Start failed: $msg");
114 1
            throw new Exception\PortUnavailableException($msg);
115
        }
116
117 14
        $command = $this->getCommand();
118
119 14
        $log_file = $this->config->getLogFile();
120 14
        $pid_file = $this->config->getPidFile();
121 14
        $cmd = sprintf("%s > %s 2>&1 & echo $! > %s", $command, $log_file, $pid_file);
122
123 14
        $this->logger->debug("Start server with: $cmd");
124 14
        exec($cmd);
125
126 14
        if (!file_exists($pid_file)) {
127
            $msg = "Server not started, pid file was not created in '$pid_file'";
128
            $this->logger->error("Start failed: $msg");
129
            throw new Exception\RuntimeException($msg);
130
        }
131 14
        if (!file_exists($log_file)) {
132
            $msg = "Server not started, log file was not created in '$log_file'";
133
            $this->logger->error("Start failed: $msg");
134
            throw new Exception\RuntimeException($msg);
135
        }
136
137
        // Loop for waiting correct start of phpjavabridge
138 14
        $started = false;
139 14
        $iterations = true;
140 14
        $refresh_us = 100 * 1000; // 100ms
141 14
        $timeout_us = $timeout_ms * 1000;
142 14
        $max_iterations = ceil($timeout_us / min([$refresh_us, $timeout_us]));
143
144 14
        while (!$started || $iterations > $max_iterations) {
145 14
            usleep($refresh_us);
146 14
            $log_file_content = file_get_contents($log_file);
147 14
            if (preg_match('/Exception/', $log_file_content)) {
148
                $msg = "Cannot start standalone server on port '$port', reason:\n";
149
                $msg .= $log_file_content;
150
                $this->logger->error("Start failed: $msg");
151
                throw new Exception\RuntimeException($msg);
152
            }
153
154 14
            $log_file_content = file_get_contents($log_file);
155 14
            if (preg_match('/JavaBridgeRunner started on/', $log_file_content)) {
156 14
                $started = true;
157 14
            }
158 14
            $iterations++;
159 14
        }
160 14
        if (!$started) {
161
            $msg = "Standalone server probably not started, timeout '$timeout_ms' reached before getting output";
162
            $this->logger->error("Start failed: $msg");
163
            throw new Exception\RuntimeException($msg);
164
        }
165 14
        $this->started = true;
166 14
    }
167
168
169
170
    /**
171
     * Stop the standalone server
172
     *
173
     * @throws Exception\StopFailedException
174
     * @param boolean $throwException whether to throw exception if pid exists in config but process cannot be found
175
     * @param boolean $clearPidFileOnException clear th pid file if the server was not running
176
     * @return void
177
     */
178 17
    public function stop($throwException=false, $clearPidFileOnException=false)
179
    {
180 17
        $this->logger->notice("Stopping server");
181
182
        try {
183 17
            $pid = $this->getPid();
184 14
            $running = $this->isProcessRunning(true);
185 14
            if (!$running) {
186
                if ($throwException) {
187
                    $msg = "Cannot stop: pid exists (${pid}) but server process is not running (throws_exception=true)";
188
                    $this->logger->notice("Stop failed: ${msg}");
189
                    throw new Exception\StopFailedException($msg);
190
                }
191
                return;
192
            }
193
        } catch (Exception\PidNotFoundException $e) {
194 11
            if ($throwException) {
195 1
                $msg = "Cannot stop server: pid file not found (was the server started ?)";
196 1
                $this->logger->notice("Stop failed: $msg");
197 1
                if ($clearPidFileOnException) {
198
                    $this->clearPidFile();
199
                }
200 1
                throw new Exception\StopFailedException($msg, null, $e);
201
            }
202 11
            return;
203
        }
204
205 14
        $killed = $this->process->kill($pid, $wait=true);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $killed is correct as $this->process->kill($pid, $wait = true) (which targets PjbServer\Tools\System\Process::kill()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
206
207
        try {
208 14
            if (!$killed) {
209
                $msg = "Cannot kill standalone server process '$pid', seems to not exists.";
210
                $this->logger->notice("Stop failed: $msg");
211
                throw new Exception\RuntimeException($msg);
212
            }
213 14
        } catch (Exception\RuntimeException $e) {
214
            if ($throwException) {
215
                $this->clearPidFile();
216
                throw $e;
217
            }
218
        }
219
220
        // Server successfully stopped let's clear the pid
221 14
        $this->clearPidFile();
222 14
        $this->started = false;
223 14
    }
224
225
226
    /**
227
     * @throws Exception\FilePermissionException
228
     */
229
    protected function clearPidFile()
230
    {
231 14
        $pid_file = $this->config->getPidFile();
232 14
        if (file_exists($pid_file)) {
233 14
            if (is_writable($pid_file)) {
234 14
                unlink($pid_file);
235 14
            } else {
236
                throw new Exception\FilePermissionException("Cannot remove pid file '${pid_file}', no write access");
237
            }
238 14
        }
239 14
    }
240
241
    /**
242
     * Tells whether the standalone server is started
243
     *
244
     * @param boolean $test_is_running
245
     * @return boolean
246
     */
247
    public function isStarted($test_is_running=true)
248
    {
249
        // In case of previous run, let's us
250 14
        if (!$this->started && $test_is_running) {
251 14
            $this->started = $this->isProcessRunning();
252 14
        }
253 14
        return $this->started;
254
    }
255
256
    /**
257
     * Return command used to start the standalone server
258
     * @return string
259
     */
260
    public function getCommand()
261
    {
262 15
        $port = $this->config->getPort();
263
264 15
        $java_bin = $this->config->getJavaBin();
265
266 15
        $jars = [];
267 15
        $classpaths = $this->config->getClasspaths();
268 15
        foreach ($classpaths as $classpath) {
269 12
            if (preg_match('/\*\.jar$/', $classpath)) {
270 12
                $directory = preg_replace('/\*\.jar$/', '', $classpath);
271 12
                $files = glob("$directory/*.jar");
272 12
                foreach ($files as $file) {
273
                    foreach ($files as $file) {
274
                        $jars[] = $file;
275
                    }
276 12
                }
277 12
            } else {
278
                $jars[] = $classpath;
279
            }
280 15
        }
281
282 15
        $jars[] = $this->config->getServerJar();
283 15
        $classpath = implode(':', $jars);
284 15
        $threads = $this->config->getThreads();
285
286 15
        $directives = ' -D' . implode(' -D', [
287 15
                    'php.java.bridge.daemon="false"',
288 15
                    "php.java.bridge.threads=$threads"
289 15
        ]);
290
291 15
        $command = sprintf('%s -cp "%s" %s php.java.bridge.Standalone SERVLET:%d',
292 15
                            $java_bin, $classpath, $directives, $port);
293
294 15
        return $command;
295
    }
296
297
298
    /**
299
     * Get standalone server pid number as it was stored during last start
300
     *
301
     * @throws Exception\PidNotFoundException|ExceptionPidCorruptedException
302
     * @return int
303
     */
304
    public function getPid()
305
    {
306 20
        $pid_file = $this->config->getPidFile();
307 20
        if (!file_exists($pid_file)) {
308 18
            $msg = "Pid file cannot be found '$pid_file'";
309 18
            $this->logger->info("Get PID failed: $msg");
310 18
            throw new Exception\PidNotFoundException($msg);
311
        }
312 15
        $pid = trim(file_get_contents($pid_file));
313 15
        if (!preg_match('/^[0-9]+$/', $pid)) {
314 1
            $msg = "Pid found '$pid_file' but no valid pid stored in it or corrupted file '$pid_file'.";
315 1
            $this->logger->error("Get PID failed: $msg");
316 1
            throw new Exception\PidCorruptedException($msg);
317
        }
318 15
        return (int) $pid;
319
    }
320
321
322
    /**
323
     * Return the content of the output_file
324
     * @throws Exception\RuntimeException
325
     * @return string
326
     */
327
    public function getOutput()
328
    {
329 5
        $log_file = $this->config->getLogFile();
330 5
        if (!file_exists($log_file)) {
331 1
            $msg = "Server output log file does not exists '$log_file'";
332 1
            $this->logger->error("Get server output failed: $msg");
333 1
            throw new Exception\RuntimeException($msg);
334 4
        } elseif (!is_readable($log_file)) {
335 1
            $msg = "Cannot read log file do to missing read permission '$log_file'";
336 1
            $this->logger->error("Get server output failed: $msg");
337 1
            throw new Exception\RuntimeException($msg);
338
        }
339 4
        $output = file_get_contents($log_file);
340 4
        return $output;
341
    }
342
343
344
    /**
345
     * Test whether the standalone server process
346
     * is effectively running
347
     *
348
     * @throws Exception\PidNotFoundException
349
     * @param boolean $throwsException if false discard exception if pidfile not exists
350
     * @return boolean
351
     */
352
    public function isProcessRunning($throwsException=false)
353
    {
354 17
        $running = false;
355
        try {
356 17
            $pid = $this->getPid();
357 15
            $isRunning = $this->process->isRunning($pid);
358 15
            if ($isRunning) {
359 15
                $this->logger->debug("Pid '${pid}' running.");
360 15
                $running = true;
361 15
            } else {
362
                $this->logger->debug("Pid '${pid}' not running.");
363
            }
364 17
        } catch (Exception\PidNotFoundException $e) {
365 15
            if ($throwsException) {
366 1
                throw $e;
367
            }
368
        }
369 16
        return $running;
370
    }
371
372
373
    /**
374
     * Restart the standalone server
375
     */
376
    public function restart()
377
    {
378 3
        $this->stop();
379 3
        $this->start();
380 3
    }
381
382
    /**
383
     * Return underlying configuration object
384
     * @return StandaloneServer\Config
385
     */
386
    public function getConfig()
387
    {
388 14
        return $this->config;
389
    }
390
}
391