serverengine 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +14 -0
- data/Changelog +5 -0
- data/Gemfile +3 -0
- data/README.md +332 -0
- data/Rakefile +14 -0
- data/lib/serverengine.rb +52 -0
- data/lib/serverengine/blocking_flag.rb +78 -0
- data/lib/serverengine/config_loader.rb +73 -0
- data/lib/serverengine/daemon.rb +165 -0
- data/lib/serverengine/daemon_logger.rb +110 -0
- data/lib/serverengine/embedded_server.rb +47 -0
- data/lib/serverengine/multi_process_server.rb +132 -0
- data/lib/serverengine/multi_thread_server.rb +61 -0
- data/lib/serverengine/multi_worker_server.rb +130 -0
- data/lib/serverengine/process_manager.rb +415 -0
- data/lib/serverengine/server.rb +103 -0
- data/lib/serverengine/signal_thread.rb +143 -0
- data/lib/serverengine/supervisor.rb +230 -0
- data/lib/serverengine/utils.rb +32 -0
- data/lib/serverengine/version.rb +3 -0
- data/lib/serverengine/worker.rb +73 -0
- data/serverengine.gemspec +25 -0
- data/spec/blocking_flag_spec.rb +58 -0
- data/spec/daemon_logger_spec.rb +88 -0
- data/spec/daemon_spec.rb +46 -0
- data/spec/multi_process_server_spec.rb +61 -0
- data/spec/server_worker_context.rb +118 -0
- data/spec/signal_thread_spec.rb +85 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/supervisor_spec.rb +142 -0
- metadata +134 -0
data/COPYING
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Copyright (C) 2011 FURUHASHI Sadayuki
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
14
|
+
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,332 @@
|
|
1
|
+
# ServerEngine
|
2
|
+
|
3
|
+
ServerEngine is a framework to implement robust multiprocess servers like Unicorn.
|
4
|
+
|
5
|
+
**Main features:**
|
6
|
+
|
7
|
+
```
|
8
|
+
Heartbeat via pipe
|
9
|
+
& auto-restart
|
10
|
+
/ \ ---+
|
11
|
+
+------------+ / +----------+ \ +--------+ |
|
12
|
+
| Supervisor |------| Server |------| Worker | |
|
13
|
+
+------------+ +----------+\ +--------+ | Multi-process
|
14
|
+
/ \ | or multi-thread
|
15
|
+
/ \ +--------+ |
|
16
|
+
Dynamic reconfiguration | Worker | |
|
17
|
+
and live restart support +--------+ |
|
18
|
+
---+
|
19
|
+
```
|
20
|
+
|
21
|
+
**Other features:**
|
22
|
+
|
23
|
+
- logging and log rotation
|
24
|
+
- signal handlers
|
25
|
+
- stacktrace and heap dump on signal
|
26
|
+
- chuser, chgroup and chumask
|
27
|
+
- changing process names
|
28
|
+
|
29
|
+
|
30
|
+
## API
|
31
|
+
|
32
|
+
### Simplest server
|
33
|
+
|
34
|
+
What you need to implement at least is a worker module which has `run` and `stop` methods.
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'serverengine'
|
38
|
+
|
39
|
+
module MyWorker
|
40
|
+
def run
|
41
|
+
until @stop
|
42
|
+
puts "Awesome work!"
|
43
|
+
sleep 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def stop
|
48
|
+
@stop = true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
se = ServerEngine.create(nil, MyWorker, {
|
53
|
+
:daemonize => true,
|
54
|
+
:pid_path => 'myserver.pid'
|
55
|
+
})
|
56
|
+
se.run
|
57
|
+
```
|
58
|
+
|
59
|
+
Send `TERM` signal to kill the daemon. See also **Signals** section bellow.
|
60
|
+
|
61
|
+
|
62
|
+
### Multiprocess server
|
63
|
+
|
64
|
+
Simply set **process** or **thread** to `worker_type` parameter and number of workers to `workers` parameter.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
se = ServerEngine.create(nil, MyWorker, {
|
68
|
+
:daemonize => true,
|
69
|
+
:pid_path => 'myserver.pid',
|
70
|
+
:workers => 4,
|
71
|
+
:worker_type => 'process',
|
72
|
+
})
|
73
|
+
se.run
|
74
|
+
```
|
75
|
+
|
76
|
+
See also **Worker types** section bellow.
|
77
|
+
|
78
|
+
#### Multiprocess TCP server
|
79
|
+
|
80
|
+
One of typical implementation styles of TCP servers is that a parent process listens socket and child processes accept connections from clients.
|
81
|
+
|
82
|
+
You can optionally implement a server module to control the parent process.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
module MyServer
|
86
|
+
def before_run
|
87
|
+
@sock = TCPServer.new(config[:bind], config[:port])
|
88
|
+
end
|
89
|
+
|
90
|
+
attr_reader :sock
|
91
|
+
end
|
92
|
+
|
93
|
+
module MyWorker
|
94
|
+
def run
|
95
|
+
until @stop
|
96
|
+
# you should use Cool.io or EventMachine actually
|
97
|
+
c = server.sock.accept
|
98
|
+
c.write "Awesome work!"
|
99
|
+
c.close
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def stop
|
104
|
+
@stop = true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
se = ServerEngine.create(MyServer, MyWorker, {
|
109
|
+
:daemonize => true,
|
110
|
+
:pid_path => 'myserver.pid',
|
111
|
+
:workers => 4,
|
112
|
+
:worker_type => 'process',
|
113
|
+
:bind => '0.0.0.0',
|
114
|
+
:port => 9071,
|
115
|
+
})
|
116
|
+
se.run
|
117
|
+
```
|
118
|
+
|
119
|
+
|
120
|
+
### Logging
|
121
|
+
|
122
|
+
ServerEngine logger rotates logs by 1MB and keeps 5 generations by default.
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
se = ServerEngine.create(MyServer, MyWorker, {
|
126
|
+
:log => 'myserver.log',
|
127
|
+
:log_level => 'debug',
|
128
|
+
:log_rotate_age => 5,
|
129
|
+
:log_rotate_size => 1*1024*1024,
|
130
|
+
})
|
131
|
+
se.run
|
132
|
+
```
|
133
|
+
|
134
|
+
See also **Configuration** section bellow.
|
135
|
+
|
136
|
+
|
137
|
+
### Enabling supervisor process
|
138
|
+
|
139
|
+
Server programs running 24x7 hours need to survive even if a process stalled because of unexpected memory swapping or network errors.
|
140
|
+
|
141
|
+
Supervisor automatically reboots the server process if heartbeat breaks out.
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
se = ServerEngine.create(nil, MyWorker, {
|
145
|
+
:daemonize => true,
|
146
|
+
:pid_path => 'myserver.pid',
|
147
|
+
:supervisor => true, # enable supervisor process
|
148
|
+
})
|
149
|
+
se.run
|
150
|
+
```
|
151
|
+
|
152
|
+
### Live restart
|
153
|
+
|
154
|
+
You can restart a server process without waiting for completion of shutdown process (if `supervisor` and `enable_detach` parameters are enabled).
|
155
|
+
This feature is useful to minimize downtime where workers take long time to complete tasks.
|
156
|
+
|
157
|
+
```
|
158
|
+
# 1. start server
|
159
|
+
+------------+ +----------+ +-----------+
|
160
|
+
| Supervisor |----| Server |----| Worker(s) |
|
161
|
+
+------------+ +----------+ +-----------+
|
162
|
+
|
163
|
+
# 2. detach (SIGINT) and waits for completion for several seconds
|
164
|
+
+------------+ +----------+ +-----------+
|
165
|
+
| Supervisor | | Server |----| Worker(s) |
|
166
|
+
+------------+ +----------+ +-----------+
|
167
|
+
|
168
|
+
# 3. start new server if the server doesn't exit in a short time
|
169
|
+
+------------+ +----------+ +-----------+
|
170
|
+
| Supervisor |\ | Server |----| Worker(s) |
|
171
|
+
+------------+ | +----------+ +-----------+
|
172
|
+
|
|
173
|
+
| +----------+ +-----------+
|
174
|
+
\--| Server |----| Worker(s) |
|
175
|
+
+----------+ +-----------+
|
176
|
+
|
177
|
+
# 4. old server exits
|
178
|
+
+------------+
|
179
|
+
| Supervisor |\
|
180
|
+
+------------+ |
|
181
|
+
|
|
182
|
+
| +----------+ +-----------+
|
183
|
+
\--| Server |----| Worker(s) |
|
184
|
+
+----------+ +-----------+
|
185
|
+
```
|
186
|
+
|
187
|
+
Note that network servers (which listen sockets) shouldn't use live restart because it causes "Address already in use" error. Instead, simply use `worker_type=process` configuration and send `USR1` to restart only workers. USR1 signal doesn't restart server (by default. See also `restart_server_process` parameter). Restarting workers don't wait for completion of all running workers.
|
188
|
+
|
189
|
+
|
190
|
+
### Dynamic configuration reloading
|
191
|
+
|
192
|
+
Robust servers should not restart only to update configuration parameters.
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
module MyWorker
|
196
|
+
def initialize
|
197
|
+
reload
|
198
|
+
end
|
199
|
+
|
200
|
+
def reload
|
201
|
+
@message = config[:message] || "Awesome work!"
|
202
|
+
@sleep = config[:sleep] || 1
|
203
|
+
end
|
204
|
+
|
205
|
+
def run
|
206
|
+
until @stop
|
207
|
+
puts @message
|
208
|
+
sleep @sleep
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def stop
|
213
|
+
@stop = true
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
se = ServerEngine.create(nil, MyWorker) do
|
218
|
+
YAML.load_file("config.yml").merge({
|
219
|
+
:daemonize => true,
|
220
|
+
:worker_type => 'process',
|
221
|
+
})
|
222
|
+
end
|
223
|
+
se.run
|
224
|
+
```
|
225
|
+
|
226
|
+
Send `USR2` signal to reload configuration file.
|
227
|
+
|
228
|
+
|
229
|
+
## Utilities
|
230
|
+
|
231
|
+
### BlockingFlag
|
232
|
+
|
233
|
+
`ServerEngine::BlockingFlag` is recommended to stop workers because `stop` methods is called by a different thread from the `run` thread.
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
module MyWorker
|
237
|
+
def initialize
|
238
|
+
@stop_flag = ServerEngine::BlockingFlag.new
|
239
|
+
end
|
240
|
+
|
241
|
+
def run
|
242
|
+
until @stop_flag.wait_for_set(1.0) # or @stop_flag.set?
|
243
|
+
puts @message
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def stop
|
248
|
+
@stop_flag.set!
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
se = ServerEngine.create(nil, MyWorker) do
|
253
|
+
YAML.load_file(config).merge({
|
254
|
+
:daemonize => true,
|
255
|
+
:worker_type => 'process'
|
256
|
+
})
|
257
|
+
end
|
258
|
+
se.run
|
259
|
+
```
|
260
|
+
|
261
|
+
|
262
|
+
## Worker types
|
263
|
+
|
264
|
+
ServerEngine supports 3 worker types:
|
265
|
+
|
266
|
+
- **embedded**: uses a thread to run worker module (default). This type doesn't support immediate shutdown and immediate restart.
|
267
|
+
- **thread**: uses threads to run worker modules. This type doesn't support immediate shutdown and immediate restart.
|
268
|
+
- **process**: uses processes to run worker modules. This type doesn't work on Win32 platform.
|
269
|
+
|
270
|
+
|
271
|
+
## Signals
|
272
|
+
|
273
|
+
- **TERM:** graceful shutdown
|
274
|
+
- **QUIT:** immediate shutdown (available only when `worker_type` is "process")
|
275
|
+
- **USR1:** graceful restart
|
276
|
+
- **HUP:** immediate restart (available only when `worker_type` is "process")
|
277
|
+
- **USR2:** reload config file and reopen log file
|
278
|
+
- **INT:** detach process for live restarting (available only when `supervisor` and `enable_detach` parameters are true. otherwise graceful shutdown)
|
279
|
+
- **CONT:** dump stacktrace and memory information to /tmp/sigdump-<pid>.log file
|
280
|
+
|
281
|
+
Immediate shutdown and restart send SIGQUIT signal to worker processes which killes the processes.
|
282
|
+
Graceful shutdown and restart call `Worker#stop` method and wait for completion of `Worker#run` method.
|
283
|
+
|
284
|
+
|
285
|
+
## Configuration
|
286
|
+
|
287
|
+
- Daemon
|
288
|
+
- **daemonize** enables daemonize (default: false) (not dynamic reloadable)
|
289
|
+
- **pid_path** sets the path to pid file (default: don't create pid file) (not dynamic reloadable)
|
290
|
+
- **supervisor** enables supervisor if it's true (default: false) (not dynamic reloadable)
|
291
|
+
- **daemon_process_name** changes process name ($0) of server or supervisor process (not dynamic reloadable)
|
292
|
+
- **chuser** changes execution user (not dynamic reloadable)
|
293
|
+
- **chgroup** changes execution group (not dynamic reloadable)
|
294
|
+
- **chumask** changes umask (not dynamic reloadable)
|
295
|
+
- Supervisor: available only when `supervisor` parameters is true
|
296
|
+
- **server_process_name** changes process name ($0) of server process (not dynamic reloadable)
|
297
|
+
- **restart_server_process** restarts server process when it receives USR1 or HUP signal. (default: false) (not dynamic reloadable)
|
298
|
+
- **enable_detach** enables INT signal (default: true) (not dynamic reloadable)
|
299
|
+
- **exit_on_detach** exits supervisor after detaching server process instead of restarting it (default: false) (not dynamic reloadable)
|
300
|
+
- **disable_reload** disables USR2 signal (default: false) (not dynamic reloadable)
|
301
|
+
- **serverrestart_wait** sets wait time before restarting server after last restarting (default: 1.0)
|
302
|
+
- **server_detach_wait** sets wait time before starting live restart (default: 10.0)
|
303
|
+
- Multithread server and multiprocess server: available only when `worker_type` is thread or process
|
304
|
+
- **workers** sets number of workers (default: 1)
|
305
|
+
- **start_worker_delay** sets wait time before starting a new worker (default: 0)
|
306
|
+
- **start_worker_delay_rand** randomizes start_worker_delay at this ratio (default: 0.2)
|
307
|
+
- Multiprocess server: available only when `worker_type` is "process"
|
308
|
+
- **worker_heartbeat_interval**
|
309
|
+
- **worker_heartbeat_timeout**
|
310
|
+
- **worker_graceful_kill_interval**
|
311
|
+
- **worker_graceful_kill_interval_increment**
|
312
|
+
- **worker_graceful_kill_timeout**
|
313
|
+
- **worker_immediate_kill_interval**
|
314
|
+
- **worker_immediate_kill_interval_increment**
|
315
|
+
- **worker_immediate_kill_timeout**
|
316
|
+
- Logger
|
317
|
+
- **log** sets path to log file. Set "-" for STDOUT (default: STDERR)
|
318
|
+
- **log_level** log level: debug, info, warn, error or fatal. (default: debug)
|
319
|
+
- **log_rotate_age** generations to keep rotated log files (default: 5) (not dynamic reloadable)
|
320
|
+
- **log_rotate_size** sets the size to rotate log files (default: 1048576) (not dynamic reloadable)
|
321
|
+
- **log_stdout** hooks STDOUT to log file (default: true) (not dynamic reloadable)
|
322
|
+
- **log_stdout** hooks STDERR to log file (default: true) (not dynamic reloadable)
|
323
|
+
- **logger_class** class of the logger instance (default: ServerEngine::DaemonLogger)
|
324
|
+
|
325
|
+
---
|
326
|
+
|
327
|
+
```
|
328
|
+
Author: Sadayuki Furuhashi
|
329
|
+
Copyright: Copyright (c) 2012-2013 FURUHASHI Sadayuki
|
330
|
+
License: Apache License, Version 2.0
|
331
|
+
```
|
332
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
3
|
+
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/clean'
|
6
|
+
|
7
|
+
require 'rspec/core/rake_task'
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
10
|
+
t.fail_on_error = false
|
11
|
+
t.rspec_opts = %w[-rspec_helper]
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default => [:spec, :build]
|
data/lib/serverengine.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#
|
2
|
+
# ServerEngine
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012-2013 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module ServerEngine
|
19
|
+
|
20
|
+
require 'sigdump'
|
21
|
+
|
22
|
+
here = File.expand_path(File.dirname(__FILE__))
|
23
|
+
|
24
|
+
{
|
25
|
+
:BlockingFlag => 'serverengine/blocking_flag',
|
26
|
+
:SignalThread => 'serverengine/signal_thread',
|
27
|
+
:DaemonLogger => 'serverengine/daemon_logger',
|
28
|
+
:ConfigLoader => 'serverengine/config_loader',
|
29
|
+
:Daemon => 'serverengine/daemon',
|
30
|
+
:Supervisor => 'serverengine/supervisor',
|
31
|
+
:Server => 'serverengine/server',
|
32
|
+
:EmbeddedServer => 'serverengine/embedded_server',
|
33
|
+
:MultiWorkerServer => 'serverengine/multi_worker_server',
|
34
|
+
:MultiProcessServer => 'serverengine/multi_process_server',
|
35
|
+
:MultiThreadServer => 'serverengine/multi_thread_server',
|
36
|
+
:ProcessManager => 'serverengine/process_manager',
|
37
|
+
:Worker => 'serverengine/worker',
|
38
|
+
:VERSION => 'serverengine/version',
|
39
|
+
}.each_pair {|k,v|
|
40
|
+
autoload k, File.expand_path(v, File.dirname(__FILE__))
|
41
|
+
}
|
42
|
+
|
43
|
+
[
|
44
|
+
'serverengine/utils',
|
45
|
+
].each {|v|
|
46
|
+
require File.join(here, v)
|
47
|
+
}
|
48
|
+
|
49
|
+
def self.create(server_module, worker_module, &config_load_proc)
|
50
|
+
Daemon.new(server_module, worker_module, &config_load_proc)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
#
|
2
|
+
# ServerEngine
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012-2013 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
module ServerEngine
|
19
|
+
|
20
|
+
require 'thread'
|
21
|
+
|
22
|
+
class BlockingFlag
|
23
|
+
def initialize
|
24
|
+
@set = false
|
25
|
+
@mutex = Mutex.new
|
26
|
+
@cond = ConditionVariable.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def set!
|
30
|
+
toggled = false
|
31
|
+
@mutex.synchronize do
|
32
|
+
unless @set
|
33
|
+
@set = true
|
34
|
+
toggled = true
|
35
|
+
end
|
36
|
+
@cond.broadcast
|
37
|
+
end
|
38
|
+
return toggled
|
39
|
+
end
|
40
|
+
|
41
|
+
def reset!
|
42
|
+
toggled = false
|
43
|
+
@mutex.synchronize do
|
44
|
+
if @set
|
45
|
+
@set = false
|
46
|
+
toggled = true
|
47
|
+
end
|
48
|
+
@cond.broadcast
|
49
|
+
end
|
50
|
+
return toggled
|
51
|
+
end
|
52
|
+
|
53
|
+
def set?
|
54
|
+
@set
|
55
|
+
end
|
56
|
+
|
57
|
+
def wait_for_set(timeout=nil)
|
58
|
+
@mutex.synchronize do
|
59
|
+
unless @set
|
60
|
+
@cond.wait(@mutex, timeout)
|
61
|
+
end
|
62
|
+
return @set
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
alias_method :wait, :wait_for_set
|
67
|
+
|
68
|
+
def wait_for_reset(timeout=nil)
|
69
|
+
@mutex.synchronize do
|
70
|
+
if @set
|
71
|
+
@cond.wait(@mutex, timeout)
|
72
|
+
end
|
73
|
+
return !@set
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|