serverengine 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|