AbstractDaemonThread::run()   A
last analyzed

Complexity

Conditions 4
Paths 19

Size

Total Lines 34
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 17
nc 19
nop 0
dl 0
loc 34
ccs 0
cts 20
cp 0
crap 20
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * \AppserverIo\Appserver\Core\AbstractDaemonThread
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Johann Zelger <[email protected]>
15
 * @copyright 2015 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/appserver-io/appserver
18
 * @link      http://www.appserver.io
19
 */
20
21
namespace AppserverIo\Appserver\Core;
22
23
use Psr\Log\LogLevel;
24
use AppserverIo\Appserver\Core\Utilities\EnumState;
25
26
/**
27
 * An abstraction implementation for daemon threads.
28
 *
29
 * @author    Tim Wagner <[email protected]>
30
 * @copyright 2015 TechDivision GmbH <[email protected]>
31
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
32
 * @link      https://github.com/appserver-io/appserver
33
 * @link      http://www.appserver.io
34
 */
35
abstract class AbstractDaemonThread extends \Thread
36
{
37
38
    /**
39
     * The name of the daemon's default shutdown method.
40
     *
41
     * @var string
42
     */
43
    const DEFAULT_SHUTDOWN_METHOD = 'defaultShutdown';
44
45
    /**
46
     * The default timeout to wait inside the daemon's while() loop.
47
     *
48
     * @var integer
49
     */
50
    const DEFAULT_TIMEOUT = 1000000;
51
52
    /**
53
     * The default timeout to re-check state during shutdown process.
54
     *
55
     * @var integer
56
     */
57
    const DEFAULT_SHUTDOWN_TIMEOUT = 50000;
58
59
    /**
60
     * The default format for log messages.
61
     *
62
     * @var string
63
     */
64
    const LOG_FORMAT = '[%s] - %s (%s): %s [%s]';
65
66
    /**
67
     * This method will be invoked before the while() loop starts and can be used
68
     * to implement some bootstrap functionality.
69
     *
70
     * @return void
71
     */
72
    public function bootstrap()
73
    {
74
        // override this to implement functionality that has to be executed before the while() loop starts.
75
    }
76
77
    /**
78
     * This method will be invoked, after the while() loop has been finished and
79
     * can be used to implement clean up functionality.
80
     *
81
     * @return void
82
     */
83
    public function cleanUp()
84
    {
85
        // override this to implement functionality that has to be executed after the while() loop finished.
86
    }
87
88
    /**
89
     * Stops the daemon and finally invokes the cleanup() method.
90
     *
91
     * @return void
92
     */
93
    public function stop()
94
    {
95
96
        // log a message that we're waiting for shutdown
97
        $this->log(LogLevel::INFO, sprintf('Now start to shutdown daemon %s', get_class($this)));
98
99
        // load the default timeout to wait for daemon shutdown
100
        $shutdownTimeout = $this->getDefaultShutdownTimeout();
101
102
        // start shutdown process
103
        $this->synchronized(function ($self) {
104
            $self->state = EnumState::get(EnumState::HALT);
105
        }, $this);
106
107
        do {
108
            // log a message that we're waiting for shutdown
109
            $this->log(LogLevel::INFO, sprintf('Wait for shutdown daemon %s', get_class($this)));
110
111
            // query whether state key is SHUTDOWN or not
112
            $waitForShutdown = $this->state->notEquals(EnumState::get(EnumState::SHUTDOWN));
113
114
            // sleep and wait for successfull daemon shutdown
115
            $this->sleep($shutdownTimeout);
116
117
        } while ($waitForShutdown);
118
119
        // log a message that we're waiting for shutdown
120
        $this->log(LogLevel::INFO, sprintf('Successfully shutdown daemon %s', get_class($this)));
121
    }
122
123
    /**
124
     * This methods has to return FALSE to stop the daemon keep running.
125
     *
126
     * @return boolean TRUE to keep the daemon running, else FALSE
127
     */
128
    public function keepRunning()
129
    {
130
        return $this->state->equals(EnumState::get(EnumState::RUNNING));
131
    }
132
133
    /**
134
     * This is invoked on every iteration of the daemons while() loop.
135
     *
136
     * @param integer $timeout The timeout before the daemon wakes up
137
     *
138
     * @return void
139
     */
140
    public function iterate($timeout)
141
    {
142
        $this->sleep($timeout);
143
    }
144
145
    /**
146
     * Let the daemon sleep for the passed value of miroseconds.
147
     *
148
     * @param integer $timeout The number of microseconds to sleep
149
     *
150
     * @return void
151
     */
152
    public function sleep($timeout)
153
    {
154
        usleep($timeout);
155
    }
156
157
    /**
158
     * The daemon's main run method. It should not be necessary to override,
159
     * instead use the main(), iterate() and cleanup() methods to implement
160
     * the daemons custom functionality.
161
     *
162
     * @return void
163
     * @see \Thread::run()
164
     */
165
    public function run()
166
    {
167
168
        try {
169
            // register shutdown handler
170
            register_shutdown_function($this->getDefaultShutdownMethod());
171
172
            // bootstrap the daemon
173
            $this->bootstrap();
174
175
            // mark the daemon as successfully shutdown
176
            $this->synchronized(function ($self) {
177
                $self->state = EnumState::get(EnumState::RUNNING);
178
            }, $this);
179
180
            // keep the daemon running
181
            while ($this->keepRunning()) {
182
                try {
183
                    $this->iterate($this->getDefaultTimeout());
184
                } catch (\Exception $e) {
185
                    $this->log(LogLevel::ERROR, $e->__toString());
186
                }
187
            }
188
189
            // clean up the instances and free memory
190
            $this->cleanUp();
191
192
            // mark the daemon as successfully shutdown
193
            $this->synchronized(function ($self) {
194
                $self->state = EnumState::get(EnumState::SHUTDOWN);
195
            }, $this);
196
197
        } catch (\Exception $e) {
198
            $this->log(LogLevel::ERROR, $e->__toString());
199
        }
200
    }
201
202
    /**
203
     * This is a very basic method to log some stuff by using the error_log() method of PHP.
204
     *
205
     * @param mixed  $level   The log level to use
206
     * @param string $message The message we want to log
207
     * @param array  $context The context we of the message
208
     *
209
     * @return void
210
     */
211
    public function log($level, $message, array $context = array())
212
    {
213
        error_log(sprintf($this->getDefaultLogFormat(), date('Y-m-d H:i:s'), gethostname(), $level, $message, json_encode($context)));
214
    }
215
216
    /**
217
     * Returns the default timeout.
218
     *
219
     * @return integer The default timeout in microseconds
220
     */
221
    public function getDefaultTimeout()
222
    {
223
        return AbstractDaemonThread::DEFAULT_TIMEOUT;
224
    }
225
226
    /**
227
     * Returns the default shutdown method registered with register_shutdown_function().
228
     *
229
     * @return callable The daemon's default shutdown method
230
     */
231
    public function getDefaultShutdownMethod()
232
    {
233
        return array(&$this, AbstractDaemonThread::DEFAULT_SHUTDOWN_METHOD);
234
    }
235
236
    /**
237
     * Returns the default shutdown timeout.
238
     *
239
     * @return integer The default shutdown timeout in microseconds
240
     */
241
    public function getDefaultShutdownTimeout()
242
    {
243
        return AbstractDaemonThread::DEFAULT_SHUTDOWN_TIMEOUT;
244
    }
245
246
    /**
247
     * Returns the default format for log messages.
248
     *
249
     * @return string The default log message format
250
     */
251
    public function getDefaultLogFormat()
252
    {
253
        return AbstractDaemonThread::LOG_FORMAT;
254
    }
255
256
    /**
257
     * This is the default shutdown method.
258
     *
259
     * @return void
260
     */
261
    public function defaultShutdown()
262
    {
263
264
        // check if there was a fatal error caused shutdown
265
        if ($lastError = error_get_last()) {
266
            // initialize error type and message
267
            $type = 0;
268
            $message = '';
269
            // extract the last error values
270
            extract($lastError);
271
            // query whether we've a fatal/user error
272
            if ($type === E_ERROR || $type === E_USER_ERROR) {
273
                $this->log(LogLevel::ERROR, $message);
274
            }
275
        }
276
    }
277
}
278