Completed
Push — master ( 8c6604...d079a5 )
by Sébastien
05:41
created

StandaloneServer::getPid()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 16
ccs 12
cts 12
cp 1
rs 9.4285
cc 3
eloc 12
nc 3
nop 0
crap 3
1
<?php
2
3
namespace PjbServer\Tools;
4
5
use PjbServer\Tools\Network\PortTester;
6
use Psr\Log\LoggerInterface;
7
use Psr\Log\NullLogger;
8
9
10
class StandaloneServer
11
{
12
13
    /**
14
     * @var int
15
     */
16
    protected $port;
17
18
    /**
19
     *
20
     * @var StandaloneServer\Config
21
     */
22
    protected $config;
23
24
    /**
25
     * Tells whether the standalone server is started
26
     * @var boolean
27
     */
28
    protected $started = false;
29
30
31
    /**
32
     *
33
     * @var PortTester
34
     */
35
    protected $portTester;
36
37
38
    /**
39
     * @var
40
     */
41
    protected $logger;
42
43
    /**
44
     * Constructor
45
     *
46
     * <code>
47
     *
48
     * $config = array(
49
     *      'port' => 8089,
50
     *
51
     *      // optionally
52
     *      'server_jar' => 'path/to/JavaBridge.jar'
53
     *      'java_bin' => 'path/to/java'
54
     * );
55
     * $server = new StandaloneServer($config);
56
     *
57
     * </code>
58
     *
59
     * @throws Exception\InvalidArgumentException
60
     * @param StandaloneServer\Config $config
61
     * @param LoggerInterface $logger
62
     *
63
     */
64 13
    public function __construct(StandaloneServer\Config $config, LoggerInterface $logger=null)
65
    {
66 13
        $this->config = $config;
67
68 13
        $curl_available = function_exists('curl_version');
69
70 13
        $this->portTester = new PortTester([
71 13
            'backend' => $curl_available ? PortTester::BACKEND_CURL : PortTester::BACKEND_STREAM_SOCKET,
72
            // Close timout ms could be adjusted for your system
73
            // It prevent that port availability testing does
74
            // not close quickly enough to allow standalone server binding
75 13
            'close_timeout_ms' => $curl_available ? null : 300
76 13
        ]);
77 13
        if ($logger === null) {
78 13
            $logger = new NullLogger();
79 13
        }
80 13
        $this->logger = $logger;
81 13
    }
82
83
    /**
84
     * Start the standalone server
85
     *
86
     * @throws Exception\RuntimeException
87
     *
88
     * @param int $timeout_ms maximum number of milliseconds to wait for server start
89
     * @return void
90
     */
91 11
    public function start($timeout_ms = 3000)
92
    {
93 11
        $port = $this->config->getPort();
94
95 11
        $this->logger->info("Starting standalone server on port $port.");
96
97 11
        if ($this->isStarted()) {
98 1
            $this->logger->info("Standalone server already running, skipping start.");
99 1
            return;
100
        }
101
102
103
104 11
        if (!$this->portTester->isAvailable('localhost', $port, 'http')) {
105
            $msg = "Cannot start server on port '$port', it's already in use.";
106 1
            $this->logger->error("Start failed: $msg");
107
            throw new Exception\RuntimeException($msg);
108
        }
109
110 11
        $command = $this->getCommand();
111
112 11
        $log_file = $this->config->getLogFile();
113 11
        $pid_file = $this->config->getPidFile();
114 11
        $cmd = sprintf("%s > %s 2>&1 & echo $! > %s", $command, $log_file, $pid_file);
115
116 11
        $this->logger->debug("Start server with: $cmd");
117 11
        exec($cmd);
118
119 11
        if (!file_exists($pid_file)) {
120
            $msg = "Server not started, pid file was not created in '$pid_file'";
121
            $this->logger->error("Start failed: $msg");
122
            throw new Exception\RuntimeException($msg);
123
        }
124 11
        if (!file_exists($log_file)) {
125
            $msg = "Server not started, log file was not created in '$log_file'";
126
            $this->logger->error("Start failed: $msg");
127
            throw new Exception\RuntimeException($msg);
128
        }
129
130
        // Loop for waiting correct start of phpjavabridge
131 11
        $started = false;
132 11
        $iterations = true;
133 11
        $refresh_us = 100 * 1000; // 100ms
134 11
        $timeout_us = $timeout_ms * 1000;
135 11
        $max_iterations = ceil($timeout_us / min([$refresh_us, $timeout_us]));
136
137 11
        while (!$started || $iterations > $max_iterations) {
138 11
            usleep($refresh_us);
139 11
            $log_file_content = file_get_contents($log_file);
140 11
            if (preg_match('/Exception/', $log_file_content)) {
141
                $msg = "Cannot start standalone server on port '$port', reason:\n";
142
                $msg .= $log_file_content;
143
                $this->logger->error("Start failed: $msg");
144
                throw new Exception\RuntimeException($msg);
145
            }
146
147 11
            $log_file_content = file_get_contents($log_file);
148 11
            if (preg_match('/JavaBridgeRunner started on/', $log_file_content)) {
149 11
                $started = true;
150 11
            }
151 11
            $iterations++;
152 11
        }
153 11
        if (!$started) {
154
            $msg = "Standalone server probably not started, timeout '$timeout_ms' reached before getting output";
155
            $this->logger->error("Start failed: $msg");
156
            throw new Exception\RuntimeException($msg);
157
        }
158 11
        $this->started = true;
159 11
    }
160
161
162
163
    /**
164
     * Stop the standalone server
165
     *
166
     * @throws Exception\RuntimeException
167
     * @param boolean $throws_exception wether to throw exception if pid or process cannot be found or killed.
168
     * @return void
169
     */
170 13
    public function stop($throws_exception = false)
171
    {
172 13
        $this->logger->info("Stopping server");
173 13
        $pid_file = $this->config->getPidFile();
174
        try {
175 13
            $pid = $this->getPid();
176 11
            $running = $this->isProcessRunning($throws_exception=true);
177 11
            if (!$running) {
178
                if ($throws_exception) {
179
                    $msg = "Cannot stop: pid exists ($pid) but server process is not running";
180
                    $this->logger->info("Stop failed: $msg");
181
                    throw new Exception\RuntimeException($msg);
182
                }
183
                return;
184
            }
185 13
        } catch (Exception\PidNotFoundException $e) {
186 10
            if ($throws_exception) {
187 1
                $msg = "Cannot stop server, pid file not found (was the server started ?)";
188 1
                $this->logger->info("Stop failed: $msg");
189 1
                throw new Exception\RuntimeException($msg);
190
            }
191 10
            return;
192
        }
193
194
195
        //$cmd = "kill $pid";
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% 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...
196
        // Let sleep the process,
197
        // @todo: test sleep mith microseconds on different unix flavours
198 11
        $sleep_time = '0.2';
199 11
        $cmd = sprintf("kill %d; while ps -p %d; do sleep %s;done;", $pid, $pid, $sleep_time);
200
201 11
        exec($cmd, $output, $return_var);
202
        try {
203 11
            if ($return_var !== 0) {
204
                $msg = "Cannot kill standalone server process '$pid', seems to not exists.";
205
                $this->logger->info("Stop failed: $msg");
206
                throw new Exception\RuntimeException($msg);
207
            }
208 11
        } catch (Exception\RuntimeException $e) {
209
            if ($throws_exception) {
210
                if (file_exists($pid_file)) {
211
                    unlink($pid_file);
212
                }
213
                throw $e;
214
            }
215
        }
216
217 11
        if (file_exists($pid_file)) {
218 11
            unlink($pid_file);
219 11
        }
220
221 11
        $this->started = false;
222 11
    }
223
224
    /**
225
     * Tells whether the standalone server is started
226
     * @return boolean
227
     */
228 11
    public function isStarted()
229
    {
230 11
        return $this->started;
231
    }
232
233
    /**
234
     * Return command used to start the standalone server
235
     * @return string
236
     */
237 11
    public function getCommand()
238
    {
239 11
        $port = $this->config->getPort();
240
241 11
        $java_bin = $this->config->getJavaBin();
242
/*
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% 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...
243
        $jars = [];
244
        $autoload_path = $this->config['autoload_path'];
245
        $files = glob("$autoload_path/*.jar");
246
        foreach ($files as $file) {
247
            $jars[] = $file;
248
        }
249
*/
250 11
        $jars =[];
251 11
        $jars[] = "/web/www/pjbserver-tools/resources/autoload/*.jar";
252 11
        $jars[] = $this->config->getServerJar();
253 11
        $classpath = implode(':', $jars);
254
255 11
        $directives = ' -D' . implode(' -D', [
256 11
                    'php.java.bridge.daemon="false"',
257
                    'php.java.bridge.threads=30'
258 11
        ]);
259
260 11
        $command = sprintf('%s -cp "%s" %s php.java.bridge.Standalone SERVLET:%d',
261 11
                            $java_bin, $classpath, $directives, $port);
262
263 11
        return $command;
264
    }
265
266
267
    /**
268
     * Get standalone server pid number as it was stored during last start
269
     *
270
     * @throws Exception\PidNotFoundException
271
     * @return int
272
     */
273 13
    public function getPid()
274
    {
275 13
        $pid_file = $this->config->getPidFile();
276 13
        if (!file_exists($pid_file)) {
277 10
            $msg = "Pid file cannot be found '$pid_file'";
278 10
            $this->logger->error("Get PID failed: $msg");
279 10
            throw new Exception\PidNotFoundException($msg);
280
        }
281 11
        $pid = trim(file_get_contents($pid_file));
282 11
        if (!preg_match('/^[0-9]+$/', $pid)) {
283 1
            $msg = "Pid found '$pid_file' but no valid pid stored in it or corrupted file '$pid_file'.";
284 1
            $this->logger->error("Get PID failed: $msg");
285 1
            throw new Exception\PidCorruptedException($msg);
286
        }
287 11
        return (int) $pid;
288
    }
289
290
291
    /**
292
     * Return the content of the output_file
293
     * @throws Exception\RuntimeException
294
     * @return string
295
     */
296 3
    public function getOutput()
297
    {
298 3
        $log_file = $this->config->getLogFile();
299 3
        if (!file_exists($log_file)) {
300 1
            $msg = "Server output log file does not exists '$log_file'";
301 1
            $this->logger->error("Get server output failed: $msg");
302 1
            throw new Exception\RuntimeException($msg);
303 2
        } elseif (!is_readable($log_file)) {
304 1
            $msg = "Cannot read log file do to missing read permission '$log_file'";
305 1
            $this->logger->error("Get server output failed: $msg");
306 1
            throw new Exception\RuntimeException($msg);
307
        }
308 2
        $output = file_get_contents($log_file);
309 2
        return $output;
310
    }
311
312
313
    /**
314
     * Test whether the standalone server process
315
     * is effectively running
316
     *
317
     * @throws Exception\PidNotFoundException
318
     * @param boolean $throwsException if false discard exception if pidfile not exists
319
     * @return boolean
320
     */
321 11
    public function isProcessRunning($throwsException = false)
322
    {
323 11
        $running = false;
324
        try {
325 11
            $pid = $this->getPid();
326 11
            $cmd = sprintf("ps -j --no-headers -p %d", $pid);
327 11
            $this->logger->debug("Getting process with cmd: $cmd");
328 11
            $result = trim(shell_exec($cmd));
329 11
            if (preg_match("/^$pid/", $result)) {
330 11
                $running = true;
331 11
            }
332 11
        } catch (Exception\PidNotFoundException $e) {
333 1
            if ($throwsException) {
334
                throw $e;
335
            }
336
        }
337 11
        return $running;
338
    }
339
340
341
    /**
342
     * Restart the standalone server
343
     */
344 2
    public function restart()
345
    {
346 2
        $this->stop();
347 2
        $this->start();
348 2
    }
349
350
    /**
351
     * Return underlying configuration object
352
     * @return StandaloneServer\Config
353
     */
354 7
    public function getConfig()
355
    {
356 7
        return $this->config;
357
    }
358
}
359