serverengine 1.5.11 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -3
- data/Changelog +9 -0
- data/README.md +108 -51
- data/appveyor.yml +18 -0
- data/lib/serverengine/config_loader.rb +2 -3
- data/lib/serverengine/daemon.rb +4 -1
- data/lib/serverengine/multi_spawn_server.rb +15 -6
- data/lib/serverengine/process_manager.rb +51 -29
- data/lib/serverengine/server.rb +9 -5
- data/lib/serverengine/socket_manager.rb +170 -0
- data/lib/serverengine/socket_manager_unix.rb +96 -0
- data/lib/serverengine/socket_manager_win.rb +147 -0
- data/lib/serverengine/supervisor.rb +4 -1
- data/lib/serverengine/utils.rb +7 -0
- data/lib/serverengine/version.rb +1 -1
- data/lib/serverengine/winsock.rb +128 -0
- data/lib/serverengine.rb +1 -0
- data/spec/signal_thread_spec.rb +10 -5
- data/spec/socket_manager_spec.rb +115 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/supervisor_spec.rb +57 -0
- metadata +10 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 104467c419789df193725e70a8d3de43e6351c73
|
4
|
+
data.tar.gz: 0d10ddbca870bb40408d8bf56e9b2d52c024636d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d33c621c6bc34a2029f100bfd61cf1a5f56c5bf81446702e1e28e2e814515ca312e5267816384b4e000da7a48a2977178297275018847fa4ef356cf650fe361
|
7
|
+
data.tar.gz: 334a1f7311e02f4d37b660ff5cfe832d17fd2f755120c04c509f2997b77186b28581614c5ba13193b0affb1afe7a0da6b9cd4bde2dde4984e8d6daf1839eb94c
|
data/.travis.yml
CHANGED
data/Changelog
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
2015-01-07 version 1.6.0:
|
2
|
+
|
3
|
+
* Added SocketManager, a utility class for multiprocess servers to listen on
|
4
|
+
the same TCP or UDP port dynamically.
|
5
|
+
* Added a new attr_reader accessor at Daemon#server and Supervisor#server
|
6
|
+
* Added ServerEngine.windows? method to check Windows platform
|
7
|
+
* ProcessManager now considers Windows platform
|
8
|
+
|
9
|
+
|
1
10
|
2015-09-28 version 1.5.11:
|
2
11
|
|
3
12
|
* Fix unexpected logger option handling [#22]
|
data/README.md
CHANGED
@@ -18,16 +18,27 @@ ServerEngine is a framework to implement robust multiprocess servers like Unicor
|
|
18
18
|
---+
|
19
19
|
```
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
21
|
+
ServerEngine also provides useful options and utilities such as logging, signal handlers, changing process names shown by `ps` command, chuser, stacktrace and heap dump on signal.
|
22
|
+
|
23
|
+
* [Examples](#examples)
|
24
|
+
* [Simplest server](#simplest-server)
|
25
|
+
* [Multiprocess server](#multiprocess-server)
|
26
|
+
* [Multiprocess TCP server](#multiprocess-tcp-server)
|
27
|
+
* [Multiprocess server on Windows and JRuby platforms](#multiprocess-server-on-windows-and-jruby-platforms)
|
28
|
+
* [Logging](#logging)
|
29
|
+
* [Supervisor auto restart](#supervisor-auto-restart)
|
30
|
+
* [Live restart](#live-restart)
|
31
|
+
* [Dynamic config reloading](#dynamic-config-reloading)
|
32
|
+
* [Signals](#signals)
|
33
|
+
* [Utilities](#utilities)
|
34
|
+
* [BlockingFlag](#blockingflag)
|
35
|
+
* [SocketManager](#socketmanager)
|
36
|
+
* [Module API](#module-api)
|
37
|
+
* [Worker module](#worker-module)
|
38
|
+
* [Server module](#server-module)
|
39
|
+
* [List of all configurations](#list-of-all-configurations)
|
40
|
+
|
41
|
+
## Examples
|
31
42
|
|
32
43
|
### Simplest server
|
33
44
|
|
@@ -62,7 +73,7 @@ Send `TERM` signal to kill the daemon. See also **Signals** section bellow for d
|
|
62
73
|
|
63
74
|
### Multiprocess server
|
64
75
|
|
65
|
-
Simply set **worker_type
|
76
|
+
Simply set **worker_type: "process"** or **worker_type: "thread"** parameter, and set number of workers to `workers` parameter.
|
66
77
|
|
67
78
|
```ruby
|
68
79
|
se = ServerEngine.create(nil, MyWorker, {
|
@@ -123,12 +134,12 @@ se.run
|
|
123
134
|
```
|
124
135
|
|
125
136
|
|
126
|
-
### Multiprocess server on Windows and JRuby
|
137
|
+
### Multiprocess server on Windows and JRuby platforms
|
127
138
|
|
128
|
-
Above **worker_type
|
129
|
-
ServerEngine provides **worker_type
|
139
|
+
Above **worker_type: "process"** depends on `fork` system call, which doesn't work on Windows or JRuby platform.
|
140
|
+
ServerEngine provides **worker_type: "spawn"** for those platforms (This is still EXPERIMENTAL). However, unfortunately, you need to implement different worker module because `worker_type: "spawn"` is not compatible with **worker_type: "process"** in terms of API.
|
130
141
|
|
131
|
-
What you need to implement at least to use worker_type
|
142
|
+
What you need to implement at least to use worker_type: "spawn" is `def spawn(process_manager)` method. You will call `process_manager.spawn` at the method, where `spawn` is same with `Process.spawn` excepting return value.
|
132
143
|
|
133
144
|
```ruby
|
134
145
|
module MyWorker
|
@@ -164,7 +175,7 @@ se.run
|
|
164
175
|
```
|
165
176
|
|
166
177
|
|
167
|
-
|
178
|
+
## Logging
|
168
179
|
|
169
180
|
ServerEngine logger rotates logs by 1MB and keeps 5 generations by default.
|
170
181
|
|
@@ -187,25 +198,25 @@ ServerEngine's default logger extends from Ruby's standard Logger library to:
|
|
187
198
|
See also **Configuration** section bellow.
|
188
199
|
|
189
200
|
|
190
|
-
|
201
|
+
## Supervisor auto restart
|
191
202
|
|
192
203
|
Server programs running 24x7 hours need to survive even if a process stalled because of unexpected memory swapping or network errors.
|
193
204
|
|
194
|
-
Supervisor process runs as the parent process of the server process and monitor it to restart automatically.
|
205
|
+
Supervisor process runs as the parent process of the server process and monitor it to restart automatically. You can enable supervisor process by setting `supervisor: true` parameter:
|
195
206
|
|
196
207
|
```ruby
|
197
208
|
se = ServerEngine.create(nil, MyWorker, {
|
198
209
|
daemonize: true,
|
199
210
|
pid_path: 'myserver.pid',
|
200
|
-
supervisor: true, #
|
211
|
+
supervisor: true, # enables supervisor process
|
201
212
|
})
|
202
213
|
se.run
|
203
214
|
```
|
204
215
|
|
205
216
|
|
206
|
-
|
217
|
+
## Live restart
|
207
218
|
|
208
|
-
You can restart a server process without waiting for completion of all workers using `INT` signal (`supervisor
|
219
|
+
You can restart a server process without waiting for completion of all workers using `INT` signal (`supervisor: true` and `enable_detach: true` parameters must be enabled).
|
209
220
|
This feature allows you to minimize downtime where workers take long time to complete a task.
|
210
221
|
|
211
222
|
```
|
@@ -238,10 +249,10 @@ This feature allows you to minimize downtime where workers take long time to com
|
|
238
249
|
+----------+ +-----------+
|
239
250
|
```
|
240
251
|
|
241
|
-
Note that network servers (which listen sockets) shouldn't use live restart because it causes "Address already in use" error at the server process. Instead, simply use `worker_type
|
252
|
+
Note that network servers (which listen sockets) shouldn't use live restart because it causes "Address already in use" error at the server process. Instead, simply use `worker_type: "process"` configuration and send `USR1` to restart workers instead of the server. It restarts a worker without waiting for shutdown of the other workers. This way doesn't cause downtime because server process doesn't close listening sockets and keeps accepting new clients (See also `restart_server_process` parameter if necessary).
|
242
253
|
|
243
254
|
|
244
|
-
|
255
|
+
## Dynamic config reloading
|
245
256
|
|
246
257
|
Robust servers should not restart only to update configuration parameters.
|
247
258
|
|
@@ -280,6 +291,20 @@ se.run
|
|
280
291
|
Send `USR2` signal to reload configuration file.
|
281
292
|
|
282
293
|
|
294
|
+
## Signals
|
295
|
+
|
296
|
+
- **TERM:** graceful shutdown
|
297
|
+
- **QUIT:** immediate shutdown (available only when `worker_type` is "process")
|
298
|
+
- **USR1:** graceful restart
|
299
|
+
- **HUP:** immediate restart (available only when `worker_type` is "process")
|
300
|
+
- **USR2:** reload config file and reopen log file
|
301
|
+
- **INT:** detach process for live restarting (available only when `supervisor` and `enable_detach` parameters are true. otherwise graceful shutdown)
|
302
|
+
- **CONT:** dump stacktrace and memory information to /tmp/sigdump-<pid>.log file
|
303
|
+
|
304
|
+
Immediate shutdown and restart send SIGQUIT signal to worker processes which kills the processes.
|
305
|
+
Graceful shutdown and restart call `Worker#stop` method and wait for completion of `Worker#run` method.
|
306
|
+
|
307
|
+
|
283
308
|
## Utilities
|
284
309
|
|
285
310
|
### BlockingFlag
|
@@ -313,11 +338,67 @@ se.run
|
|
313
338
|
```
|
314
339
|
|
315
340
|
|
316
|
-
|
341
|
+
### SocketManager
|
317
342
|
|
318
|
-
|
343
|
+
`ServerEngine::SocketManager` is a powerful library to listen on the same port across multiple worker processes dynamically.
|
344
|
+
|
345
|
+
```ruby
|
346
|
+
module MyServer
|
347
|
+
def before_run
|
348
|
+
@socket_manager_path = ServerEngine::SocketManager.generate_path
|
349
|
+
@socket_manager_server = ServerEngine::SocketManager::Server.open(@socket_manager_path)
|
350
|
+
end
|
351
|
+
|
352
|
+
def after_run
|
353
|
+
@socket_manager_server.close
|
354
|
+
end
|
355
|
+
|
356
|
+
attr_reader :socket_manager_path
|
357
|
+
end
|
358
|
+
|
359
|
+
module MyWorker
|
360
|
+
def initialize
|
361
|
+
@stop_flag = ServerEngine::BlockingFlag.new
|
362
|
+
@socket_manager = ServerEngine::SocketManager::Client.new(server.socket_manager_path)
|
363
|
+
end
|
364
|
+
|
365
|
+
def run
|
366
|
+
lsock = @socket_manager.listen_tcp('0.0.0.0', 12345)
|
367
|
+
until @stop
|
368
|
+
c = lsock.accept
|
369
|
+
c.write "Awesome work!"
|
370
|
+
c.close
|
371
|
+
end
|
372
|
+
end
|
319
373
|
|
320
|
-
|
374
|
+
def stop
|
375
|
+
@stop = true
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
se = ServerEngine.create(MyServer, MyWorker, {
|
380
|
+
daemonize: true,
|
381
|
+
log: 'myserver.log',
|
382
|
+
pid_path: 'myserver.pid',
|
383
|
+
worker_type: 'process',
|
384
|
+
workers: 4,
|
385
|
+
bind: '0.0.0.0',
|
386
|
+
port: 9071,
|
387
|
+
})
|
388
|
+
se.run
|
389
|
+
```
|
390
|
+
|
391
|
+
|
392
|
+
## Module API
|
393
|
+
|
394
|
+
Available methods are different depending on `worker_type`. ServerEngine supports 3 worker types:
|
395
|
+
|
396
|
+
- **embedded**: uses a thread to run worker module (default). This type doesn't support immediate shutdown or immediate restart.
|
397
|
+
- **thread**: uses threads to run worker modules. This type doesn't support immediate shutdown or immediate restart.
|
398
|
+
- **process**: uses processes to run worker modules. This type doesn't work on Windows or JRuby platform.
|
399
|
+
- **spawn**: uses processes to run worker modules. This type works on Windows and JRuby platform but available interface of worker module is limited (See also Worker module section).
|
400
|
+
|
401
|
+
### Worker module
|
321
402
|
|
322
403
|
- interface
|
323
404
|
- `initialize` is called in the parent process (or thread) in contrast to the other methods
|
@@ -350,31 +431,7 @@ Available methods are different depending on `worker_type`.
|
|
350
431
|
- `logger` logger
|
351
432
|
|
352
433
|
|
353
|
-
##
|
354
|
-
|
355
|
-
ServerEngine supports 3 worker types:
|
356
|
-
|
357
|
-
- **embedded**: uses a thread to run worker module (default). This type doesn't support immediate shutdown or immediate restart.
|
358
|
-
- **thread**: uses threads to run worker modules. This type doesn't support immediate shutdown or immediate restart.
|
359
|
-
- **process**: uses processes to run worker modules. This type doesn't work on Windows or JRuby platform.
|
360
|
-
- **spawn**: uses processes to run worker modules. This type works on Windows and JRuby platform but available interface of worker module is limited (See also Worker module section).
|
361
|
-
|
362
|
-
|
363
|
-
## Signals
|
364
|
-
|
365
|
-
- **TERM:** graceful shutdown
|
366
|
-
- **QUIT:** immediate shutdown (available only when `worker_type` is "process")
|
367
|
-
- **USR1:** graceful restart
|
368
|
-
- **HUP:** immediate restart (available only when `worker_type` is "process")
|
369
|
-
- **USR2:** reload config file and reopen log file
|
370
|
-
- **INT:** detach process for live restarting (available only when `supervisor` and `enable_detach` parameters are true. otherwise graceful shutdown)
|
371
|
-
- **CONT:** dump stacktrace and memory information to /tmp/sigdump-<pid>.log file
|
372
|
-
|
373
|
-
Immediate shutdown and restart send SIGQUIT signal to worker processes which kills the processes.
|
374
|
-
Graceful shutdown and restart call `Worker#stop` method and wait for completion of `Worker#run` method.
|
375
|
-
|
376
|
-
|
377
|
-
## Configuration
|
434
|
+
## List of all configurations
|
378
435
|
|
379
436
|
- Daemon
|
380
437
|
- **daemonize** enables daemonize (default: false)
|
data/appveyor.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
install:
|
3
|
+
- SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
|
4
|
+
- ruby --version
|
5
|
+
- gem --version
|
6
|
+
- bundle install
|
7
|
+
build: off
|
8
|
+
test_script:
|
9
|
+
- bundle exec rake -rdevkit
|
10
|
+
|
11
|
+
environment:
|
12
|
+
matrix:
|
13
|
+
- ruby_version: "200"
|
14
|
+
- ruby_version: "200-x64"
|
15
|
+
- ruby_version: "21"
|
16
|
+
- ruby_version: "21-x64"
|
17
|
+
- ruby_version: "22"
|
18
|
+
- ruby_version: "22-x64"
|
@@ -58,10 +58,9 @@ module ServerEngine
|
|
58
58
|
def create_logger
|
59
59
|
if logger = @config[:logger]
|
60
60
|
@logger = logger
|
61
|
-
|
61
|
+
else
|
62
|
+
@logger = @logger_class.new(logdev_from_config(@config), @config)
|
62
63
|
end
|
63
|
-
|
64
|
-
@logger = @logger_class.new(logdev_from_config(@config), @config)
|
65
64
|
end
|
66
65
|
|
67
66
|
def logdev_from_config(config)
|
data/lib/serverengine/daemon.rb
CHANGED
@@ -49,6 +49,9 @@ module ServerEngine
|
|
49
49
|
@chumask = @config[:chumask]
|
50
50
|
end
|
51
51
|
|
52
|
+
# server is available when run() is called. It is a Supervisor instance if supervisor is set to true. Otherwise a Server instance.
|
53
|
+
attr_reader :server
|
54
|
+
|
52
55
|
module Signals
|
53
56
|
GRACEFUL_STOP = :TERM
|
54
57
|
IMMEDIATE_STOP = :QUIT
|
@@ -157,7 +160,7 @@ module ServerEngine
|
|
157
160
|
private
|
158
161
|
|
159
162
|
def create_server(logger)
|
160
|
-
@create_server_proc.call(@load_config_proc, logger)
|
163
|
+
@server = @create_server_proc.call(@load_config_proc, logger)
|
161
164
|
end
|
162
165
|
end
|
163
166
|
end
|
@@ -19,12 +19,21 @@ module ServerEngine
|
|
19
19
|
|
20
20
|
class MultiSpawnServer < MultiWorkerServer
|
21
21
|
def initialize(worker_module, load_config_proc={}, &block)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
if ServerEngine.windows?
|
23
|
+
@pm = ProcessManager.new(
|
24
|
+
auto_tick: false,
|
25
|
+
graceful_kill_signal: Daemon::Signals::GRACEFUL_STOP,
|
26
|
+
immediate_kill_signal: false,
|
27
|
+
enable_heartbeat: false,
|
28
|
+
)
|
29
|
+
else
|
30
|
+
@pm = ProcessManager.new(
|
31
|
+
auto_tick: false,
|
32
|
+
graceful_kill_signal: Daemon::Signals::GRACEFUL_STOP,
|
33
|
+
immediate_kill_signal: Daemon::Signals::IMMEDIATE_STOP,
|
34
|
+
enable_heartbeat: false,
|
35
|
+
)
|
36
|
+
end
|
28
37
|
|
29
38
|
super(worker_module, load_config_proc, &block)
|
30
39
|
|
@@ -34,6 +34,10 @@ module ServerEngine
|
|
34
34
|
@auto_tick_interval = config[:auto_tick_interval] || 1
|
35
35
|
|
36
36
|
@enable_heartbeat = !!config[:enable_heartbeat]
|
37
|
+
if ServerEngine.windows?
|
38
|
+
# heartbeat is not supported on Windows platform. See also spawn method.
|
39
|
+
@enable_heartbeat = false
|
40
|
+
end
|
37
41
|
@auto_heartbeat = !!config.fetch(:auto_heartbeat, true)
|
38
42
|
|
39
43
|
case op = config[:on_heartbeat_error]
|
@@ -93,6 +97,10 @@ module ServerEngine
|
|
93
97
|
end
|
94
98
|
|
95
99
|
def fork(&block)
|
100
|
+
if ServerEngine.windows?
|
101
|
+
raise NotImplementedError, "fork is not available on this platform. Please use spawn (worker_type: 'spawn')."
|
102
|
+
end
|
103
|
+
|
96
104
|
rpipe, wpipe = new_pipe_pair
|
97
105
|
|
98
106
|
begin
|
@@ -143,12 +151,14 @@ module ServerEngine
|
|
143
151
|
|
144
152
|
# pipe is necessary even if @enable_heartbeat == false because
|
145
153
|
# parent process detects shutdown of a child process using it
|
146
|
-
rpipe, wpipe = new_pipe_pair
|
147
|
-
|
148
154
|
begin
|
149
|
-
|
150
|
-
|
151
|
-
|
155
|
+
unless ServerEngine.windows?
|
156
|
+
# heartbeat is not supported on Windows platform
|
157
|
+
rpipe, wpipe = new_pipe_pair
|
158
|
+
options[[wpipe.fileno]] = wpipe
|
159
|
+
if @enable_heartbeat
|
160
|
+
env['SERVERENGINE_HEARTBEAT_PIPE'] = wpipe.fileno.to_s
|
161
|
+
end
|
152
162
|
end
|
153
163
|
|
154
164
|
pid = Process.spawn(env, *args, options)
|
@@ -156,13 +166,16 @@ module ServerEngine
|
|
156
166
|
m = Monitor.new(self, pid)
|
157
167
|
|
158
168
|
@monitors << m
|
159
|
-
|
160
|
-
|
169
|
+
|
170
|
+
unless ServerEngine.windows?
|
171
|
+
@rpipes[rpipe] = m
|
172
|
+
rpipe = nil
|
173
|
+
end
|
161
174
|
|
162
175
|
return m
|
163
176
|
|
164
177
|
ensure
|
165
|
-
wpipe.close
|
178
|
+
wpipe.close if wpipe
|
166
179
|
rpipe.close if rpipe
|
167
180
|
end
|
168
181
|
end
|
@@ -199,30 +212,34 @@ module ServerEngine
|
|
199
212
|
raise AlreadyClosedError.new
|
200
213
|
end
|
201
214
|
|
202
|
-
|
203
|
-
sleep blocking_timeout if blocking_timeout > 0
|
204
|
-
return nil
|
205
|
-
end
|
215
|
+
time ||= Time.now
|
206
216
|
|
207
|
-
|
217
|
+
unless ServerEngine.windows?
|
218
|
+
# heartbeat is not supported on Windows platform.
|
208
219
|
|
209
|
-
|
220
|
+
if @rpipes.empty?
|
221
|
+
sleep blocking_timeout if blocking_timeout > 0
|
222
|
+
return nil
|
223
|
+
end
|
210
224
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
225
|
+
ready_pipes, _, _ = IO.select(@rpipes.keys, nil, nil, blocking_timeout)
|
226
|
+
|
227
|
+
if ready_pipes
|
228
|
+
ready_pipes.each do |r|
|
229
|
+
begin
|
230
|
+
r.read_nonblock(1024, @read_buffer)
|
231
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
232
|
+
next
|
233
|
+
rescue #EOFError
|
234
|
+
m = @rpipes.delete(r)
|
235
|
+
m.start_immediate_stop!
|
236
|
+
r.close rescue nil
|
237
|
+
next
|
238
|
+
end
|
223
239
|
|
224
|
-
|
225
|
-
|
240
|
+
if m = @rpipes[r]
|
241
|
+
m.last_heartbeat_time = time
|
242
|
+
end
|
226
243
|
end
|
227
244
|
end
|
228
245
|
end
|
@@ -275,6 +292,7 @@ module ServerEngine
|
|
275
292
|
end
|
276
293
|
|
277
294
|
attr_accessor :last_heartbeat_time
|
295
|
+
attr_reader :pid
|
278
296
|
|
279
297
|
def heartbeat_delay
|
280
298
|
now = Time.now
|
@@ -389,7 +407,11 @@ module ServerEngine
|
|
389
407
|
end
|
390
408
|
|
391
409
|
begin
|
392
|
-
|
410
|
+
if ServerEngine.windows? && (signal == :KILL || signal == :SIGKILL)
|
411
|
+
system("taskkill /f /pid #{pid}")
|
412
|
+
else
|
413
|
+
Process.kill(signal, pid)
|
414
|
+
end
|
393
415
|
rescue #Errno::ECHILD, Errno::ESRCH, Errno::EPERM
|
394
416
|
# assume that any errors mean the child process is dead
|
395
417
|
@pid = nil
|