serverengine 1.5.11 → 1.6.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.
- 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
|