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 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/Changelog ADDED
@@ -0,0 +1,5 @@
1
+
2
+ 2013-06-01 version 1.5.0:
3
+
4
+ * First release
5
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org/'
2
+
3
+ gemspec
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]
@@ -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