serverengine 1.6.4 → 2.0.0pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/Changelog +11 -0
  4. data/README.md +31 -3
  5. data/Rakefile +16 -3
  6. data/appveyor.yml +11 -5
  7. data/examples/server.rb +138 -0
  8. data/examples/spawn_worker_script.rb +38 -0
  9. data/lib/serverengine/blocking_flag.rb +2 -3
  10. data/lib/serverengine/command_sender.rb +89 -0
  11. data/lib/serverengine/config_loader.rb +2 -0
  12. data/lib/serverengine/daemon.rb +114 -86
  13. data/lib/serverengine/daemon_logger.rb +3 -139
  14. data/lib/serverengine/embedded_server.rb +2 -0
  15. data/lib/serverengine/multi_process_server.rb +28 -7
  16. data/lib/serverengine/multi_spawn_server.rb +17 -18
  17. data/lib/serverengine/multi_thread_server.rb +6 -0
  18. data/lib/serverengine/multi_worker_server.rb +14 -0
  19. data/lib/serverengine/privilege.rb +57 -0
  20. data/lib/serverengine/process_manager.rb +66 -48
  21. data/lib/serverengine/server.rb +45 -11
  22. data/lib/serverengine/signal_thread.rb +0 -2
  23. data/lib/serverengine/signals.rb +31 -0
  24. data/lib/serverengine/socket_manager.rb +3 -5
  25. data/lib/serverengine/socket_manager_unix.rb +1 -0
  26. data/lib/serverengine/socket_manager_win.rb +4 -2
  27. data/lib/serverengine/supervisor.rb +105 -25
  28. data/lib/serverengine/utils.rb +23 -0
  29. data/lib/serverengine/version.rb +1 -1
  30. data/lib/serverengine/worker.rb +10 -7
  31. data/lib/serverengine.rb +9 -27
  32. data/serverengine.gemspec +12 -1
  33. data/spec/daemon_logger_spec.rb +17 -12
  34. data/spec/daemon_spec.rb +147 -24
  35. data/spec/multi_process_server_spec.rb +59 -7
  36. data/spec/server_worker_context.rb +104 -0
  37. data/spec/signal_thread_spec.rb +61 -56
  38. data/spec/supervisor_spec.rb +113 -99
  39. metadata +40 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e1905531d61188949bea25a5bd1aa56882b9fd0e
4
- data.tar.gz: 225c5c5204167c726710c4790def2f5b7794e179
3
+ metadata.gz: 9c2c41ca45cff7f607a22a6861bd7b55e03a70b7
4
+ data.tar.gz: 97948ab60c218ef17becfb05723270592cc7540c
5
5
  SHA512:
6
- metadata.gz: c2a704566a28f1e90f44934d97e976e82aee1d6867b77bc6497b015c38fef2f538583bb614bdef18de91061b9e1927aa864c7eb50e5e1b0448f8b73eeb4293b6
7
- data.tar.gz: c5bab7f53971af7eeaeb4a0a7a58bab5c921d6d39d2c0e00ea154c6cedce39ebe23bf675f8f31896d0dc5fed3b36b218d1e8e079402c97e1d86894dd0448b2a1
6
+ metadata.gz: 38ff87ed7b173030bb5293c0d3e89a824efae90b9e87c5a6fefa22f61c3e14b737c4e28cc92e783fc51568f517a5aea5393b69a46de7c8aa26892f24ec0190d9
7
+ data.tar.gz: 143bfeb1386750fc4275e68d9555551e450ffa57e9c9a408a6fb2e313b421301e4bef62d320ad3a8f0906936bc43f453a92d895ec1103a2735478f1561b3d76a
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
1
  Gemfile.lock
2
2
  tmp/*
3
3
  pkg/*
4
+ myserver.log
5
+ myserver.pid
data/Changelog CHANGED
@@ -1,3 +1,14 @@
1
+ 2016-08-23 version 2.0.0:
2
+
3
+ * Add windows-pr gem dependency to get ruby_bin_path correctly
4
+ * Add command sender feature to use pipe to control workers for Windows
5
+ * Delete MultiprocessLogDevice implementation to use Ruby's one always. This
6
+ means removal of backward workaround code for Ruby < 2.1.
7
+ * Refactor modules and methods to clean internal file dependency Internal
8
+ symbol `ServerEngine::Daemon::Signals` is moved to `ServerEngine::Signals`
9
+ * Add example script to run servers
10
+ * Fix required Ruby version to 2.1 or later
11
+
1
12
  2016-05-19 version 1.6.4:
2
13
 
3
14
  * Refactor to delete some warnings
data/README.md CHANGED
@@ -68,7 +68,7 @@ se = ServerEngine.create(nil, MyWorker, {
68
68
  se.run
69
69
  ```
70
70
 
71
- Send `TERM` signal to kill the daemon. See also **Signals** section bellow for details.
71
+ Send `TERM` signal (or `KILL` on Windows) to kill the daemon. See also **Signals** section bellow for details.
72
72
 
73
73
 
74
74
  ### Multiprocess server
@@ -141,6 +141,12 @@ ServerEngine provides **worker_type: "spawn"** for those platforms (This is stil
141
141
 
142
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.
143
143
 
144
+ In addition, Windows does not support signals. ServerEngine provides **command_sender: "pipe"** for Windows (and for other platforms, if you want to use it).
145
+ When using **command_sender: "pipe"**, the child process have to handle commands sent from parent process via STDIN.
146
+ On Windows, **command_sender: "pipe"** is default.
147
+
148
+ You can call `Server#stop(stop_graceful)` and `Server#restart(stop_graceful)` instead of sending signals.
149
+
144
150
  ```ruby
145
151
  module MyWorker
146
152
  def spawn(process_manager)
@@ -155,8 +161,20 @@ module MyWorker
155
161
  logger = ServerEngine::DaemonLogger.new(conf[:log] || STDOUT, conf)
156
162
 
157
163
  @stop = false
158
- trap(:SIGTERM) { @stop = true }
159
- trap(:SIGINT) { @stop = true }
164
+ command_pipe = STDIN.dup
165
+ STDIN.reopen(File::NULL)
166
+ Thread.new do
167
+ until @stop
168
+ case command_pipe.gets.chomp
169
+ when "GRACEFUL_STOP"
170
+ @stop = true
171
+ when "IMMEDIATE_STOP"
172
+ @stop = true
173
+ when "GRACEFUL_RESTART", "IMMEDIATE_RESTART"
174
+ # do something...
175
+ end
176
+ end
177
+ end
160
178
 
161
179
  until @stop
162
180
  logger.info 'Awesome work!'
@@ -169,6 +187,7 @@ end
169
187
 
170
188
  se = ServerEngine.create(nil, MyWorker, {
171
189
  worker_type: 'spawn',
190
+ command_sender: 'pipe',
172
191
  log: 'myserver.log',
173
192
  })
174
193
  se.run
@@ -213,6 +232,7 @@ se = ServerEngine.create(nil, MyWorker, {
213
232
  se.run
214
233
  ```
215
234
 
235
+ This auto restart reature will be suppressed for workers which exits with exit code specified by `unrecoverable_exit_codes`. At this case, whole process will exit without error (exit code 0).
216
236
 
217
237
  ## Live restart
218
238
 
@@ -304,6 +324,10 @@ Send `USR2` signal to reload configuration file.
304
324
  Immediate shutdown and restart send SIGQUIT signal to worker processes which kills the processes.
305
325
  Graceful shutdown and restart call `Worker#stop` method and wait for completion of `Worker#run` method.
306
326
 
327
+ Note that signals are not supported on Windows.
328
+ You have to use piped command instead of signals on Windows.
329
+ See also **Multiprocess server on Windows and JRuby platforms** section.
330
+
307
331
 
308
332
  ## Utilities
309
333
 
@@ -388,6 +412,8 @@ se = ServerEngine.create(MyServer, MyWorker, {
388
412
  se.run
389
413
  ```
390
414
 
415
+ See also [examples](https://github.com/fluent/serverengine/tree/master/examples).
416
+
391
417
 
392
418
  ## Module API
393
419
 
@@ -464,6 +490,8 @@ Available methods are different depending on `worker_type`. ServerEngine support
464
490
  - **worker_immediate_kill_interval** sets the first interval of QUIT signals in seconds (default: 10) [dynamic reloadable]
465
491
  - **worker_immediate_kill_interval_increment** sets increment of QUIT signal interval in seconds (default: 10) [dynamic reloadable]
466
492
  - **worker_immediate_kill_timeout** sets promotion timeout from QUIT to KILL signal in seconds. -1 means no timeout (default: 600) [dynamic reloadable]
493
+ - **unrecoverable_exit_codes** handles worker processes, which exits with code included in this value, as unrecoverable (not to restart) (default: [])
494
+ - **stop_immediately_at_unrecoverable_exit** stops server immediately if a worker exits with unrecoverable exit status (default: false)
467
495
  - Multiprocess spawn server: available only when `worker_type` is "spawn"
468
496
  - all parameters of multiprocess server excepting worker_process_name
469
497
  - **worker_reload_signal** sets the signal to notice configuration reload to a spawned process. Set nil to disable (default: nil)
data/Rakefile CHANGED
@@ -6,8 +6,21 @@ require 'rake/clean'
6
6
 
7
7
  require 'rspec/core/rake_task'
8
8
 
9
- RSpec::Core::RakeTask.new(:spec) do |t|
10
- t.fail_on_error = false
9
+ RSpec::Core::RakeTask.new(:spec)
10
+ task :default => [:spec, :build]
11
+
12
+ # 1. update Changelog and lib/serverengine/version.rb
13
+ # 2. bundle && bundle exec rake build:all
14
+ # 3. release 3 packages built on pkg/ directory
15
+ namespace :build do
16
+ desc 'Build gems for all platforms'
17
+ task :all do
18
+ Bundler.with_clean_env do
19
+ %w[ruby x86-mingw32 x64-mingw32].each do |name|
20
+ ENV['GEM_BUILD_FAKE_PLATFORM'] = name
21
+ Rake::Task["build"].execute
22
+ end
23
+ end
24
+ end
11
25
  end
12
26
 
13
- task :default => [:spec, :build]
data/appveyor.yml CHANGED
@@ -10,9 +10,15 @@ test_script:
10
10
 
11
11
  environment:
12
12
  matrix:
13
- - ruby_version: "200"
14
- - ruby_version: "200-x64"
15
- - ruby_version: "21"
16
- - ruby_version: "21-x64"
13
+ - ruby_version: "23-x64"
14
+ devkit: C:\Ruby23-x64\DevKit
15
+ - ruby_version: "23"
16
+ devkit: C:\Ruby23\DevKit
17
+ - ruby_version: "22-x64"
18
+ devkit: C:\Ruby23-x64\DevKit
17
19
  - ruby_version: "22"
18
- - ruby_version: "22-x64"
20
+ devkit: C:\Ruby23\DevKit
21
+ - ruby_version: "21-x64"
22
+ devkit: C:\Ruby23-x64\DevKit
23
+ - ruby_version: "21"
24
+ devkit: C:\Ruby23\DevKit
@@ -0,0 +1,138 @@
1
+ require 'serverengine'
2
+
3
+ require 'json'
4
+ require 'optparse'
5
+
6
+ # This is a script to run ServerEngine and SocketManager as a real process.
7
+ # bundle exec ruby example/server.rb [-t TYPE] [-w NUM]
8
+ # available type of workers are: embedded(default), process, thread, spawn
9
+
10
+ foreground = false
11
+ supervisor = false
12
+ worker_type = nil
13
+ workers = 4
14
+ exit_with_code = nil
15
+ exit_at_seconds = 5
16
+ exit_at_random = false
17
+ stop_immediately_at_exit = false
18
+ unrecoverable_exit_codes = []
19
+
20
+ opt = OptionParser.new
21
+ opt.on('-f'){ foreground = true }
22
+ opt.on('-x'){ supervisor = true }
23
+ opt.on('-t TYPE'){|v| worker_type = v }
24
+ opt.on('-w NUM'){|v| workers = v.to_i }
25
+ opt.on('-e NUM'){|v| exit_with_code = v.to_i }
26
+ opt.on('-s NUM'){|v| exit_at_seconds = v.to_i }
27
+ opt.on('-r'){ exit_at_random = true }
28
+ opt.on('-i'){ stop_immediately_at_exit = true }
29
+ opt.on('-u NUM'){|v| unrecoverable_exit_codes << v.to_i }
30
+ opt.parse!(ARGV)
31
+
32
+ if exit_with_code
33
+ ENV['EXIT_WITH_CODE'] = exit_with_code.to_s
34
+ ENV['EXIT_AT_SECONDS'] = exit_at_seconds.to_s
35
+ if exit_at_random
36
+ ENV['EXIT_AT_RANDOM'] = 'true'
37
+ end
38
+ end
39
+
40
+ module MyServer
41
+ attr_reader :socket_manager_path
42
+
43
+ def before_run
44
+ @socket_manager_path = ServerEngine::SocketManager::Server.generate_path
45
+ @socket_manager_server = ServerEngine::SocketManager::Server.open(@socket_manager_path)
46
+ rescue Exception => e
47
+ logger.error "unexpected error in server, class #{e.class}: #{e.message}"
48
+ raise
49
+ end
50
+
51
+ def after_run
52
+ logger.info "Server stopped."
53
+ @socket_manager_server.close
54
+ end
55
+ end
56
+
57
+ module MyWorker
58
+ def initialize
59
+ @stop = false
60
+ @socket_manager = ServerEngine::SocketManager::Client.new(server.socket_manager_path)
61
+ @exit_with_code = ENV.key?('EXIT_WITH_CODE') ? ENV['EXIT_WITH_CODE'].to_i : nil
62
+ @exit_at_seconds = ENV.key?('EXIT_AT_SECONDS') ? ENV['EXIT_AT_SECONDS'].to_i : nil
63
+ @exit_at_random = ENV.key?('EXIT_AT_RANDOM')
64
+ end
65
+
66
+ def main
67
+ # test to listen the same port
68
+ logger.info "Starting to run Worker."
69
+ _listen_sock = @socket_manager.listen_tcp('0.0.0.0', 12345)
70
+ stop_at = if @exit_with_code
71
+ stop_seconds = @exit_at_random ? rand(@exit_at_seconds) : @exit_at_seconds
72
+ logger.info "Stop #{stop_seconds} seconds later with code #{@exit_with_code}."
73
+ Time.now + stop_seconds
74
+ else
75
+ nil
76
+ end
77
+ until @stop
78
+ if stop_at && Time.now >= stop_at
79
+ logger.info "Exitting with code #{@exit_with_code}"
80
+ exit! @exit_with_code
81
+ end
82
+ logger.info "Awesome work!"
83
+ sleep 1
84
+ end
85
+ logger.info "Exitting"
86
+ rescue Exception => e
87
+ logger.warn "unexpected error, class #{e.class}: #{e.message}"
88
+ raise
89
+ end
90
+
91
+ def stop
92
+ @stop = true
93
+ end
94
+ end
95
+
96
+ module MySpawnWorker
97
+ def spawn(process_manager)
98
+ env = {
99
+ 'SERVER_ENGINE_CONFIG' => config.to_json,
100
+ 'SERVER_ENGINE_SOCKET_MANAGER_PATH' => server.socket_manager_path,
101
+ }
102
+ if ENV['EXIT_WITH_CODE']
103
+ env['EXIT_WITH_CODE'] = ENV['EXIT_WITH_CODE']
104
+ env['EXIT_AT_SECONDS'] = ENV['EXIT_AT_SECONDS']
105
+ if ENV['EXIT_AT_RANDOM']
106
+ env['EXIT_AT_RANDOM'] = 'true'
107
+ end
108
+ end
109
+ process_manager.spawn(env, "ruby", File.expand_path("../spawn_worker_script.rb", __FILE__))
110
+ rescue Exception => e
111
+ logger.error "unexpected error, class #{e.class}: #{e.message}"
112
+ raise
113
+ end
114
+ end
115
+
116
+ opts = {
117
+ daemonize: !foreground,
118
+ daemon_process_name: 'mydaemon',
119
+ supervisor: supervisor,
120
+ log: 'myserver.log',
121
+ pid_path: 'myserver.pid',
122
+ worker_type: worker_type,
123
+ workers: workers,
124
+ }
125
+ if stop_immediately_at_exit
126
+ opts[:stop_immediately_at_unrecoverable_exit] = true
127
+ end
128
+ unless unrecoverable_exit_codes.empty?
129
+ opts[:unrecoverable_exit_codes] = unrecoverable_exit_codes
130
+ end
131
+
132
+ worker_klass = MyWorker
133
+ if worker_type == 'spawn'
134
+ worker_klass = MySpawnWorker
135
+ end
136
+ se = ServerEngine.create(MyServer, worker_klass, opts)
137
+
138
+ se.run
@@ -0,0 +1,38 @@
1
+ $LOAD_PATH.unshift File.expand_path("../..", __FILE__)
2
+
3
+ require 'serverengine'
4
+ require 'json'
5
+
6
+ begin
7
+ conf = JSON.parse(ENV['SERVER_ENGINE_CONFIG'], symbolize_names: true)
8
+ logger = ServerEngine::DaemonLogger.new(conf[:log] || STDOUT, conf)
9
+ logger.info "Starting to run Worker."
10
+ socket_manager = ServerEngine::SocketManager::Client.new(ENV['SERVER_ENGINE_SOCKET_MANAGER_PATH'])
11
+ exit_with_code = ENV.key?('EXIT_WITH_CODE') ? ENV['EXIT_WITH_CODE'].to_i : nil
12
+ exit_at_seconds = ENV.key?('EXIT_AT_SECONDS') ? ENV['EXIT_AT_SECONDS'].to_i : nil
13
+ exit_at_random = ENV.key?('EXIT_AT_RANDOM')
14
+ stop_at = if exit_with_code
15
+ stop_seconds = exit_at_random ? rand(exit_at_seconds) : exit_at_seconds
16
+ logger.info "Stop #{stop_seconds} seconds later with code #{exit_with_code}."
17
+ Time.now + stop_seconds
18
+ else
19
+ nil
20
+ end
21
+
22
+ @stop = false
23
+ trap(:SIGTERM) { @stop = true }
24
+ trap(:SIGINT) { @stop = true }
25
+
26
+ _listen_sock = socket_manager.listen_tcp('0.0.0.0', 12345)
27
+ until @stop
28
+ if stop_at && Time.now >= stop_at
29
+ logger.info "Exitting with code #{exit_with_code}"
30
+ exit! exit_with_code
31
+ end
32
+ logger.info 'Awesome work!'
33
+ sleep 1
34
+ end
35
+ logger.info 'Exitting'
36
+ rescue Exception => e
37
+ logger.error "unexpected error in spawn worker, class #{e.class}: #{e.message}"
38
+ end
@@ -15,10 +15,9 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
- module ServerEngine
19
-
20
- require 'thread'
18
+ require 'thread'
21
19
 
20
+ module ServerEngine
22
21
  class BlockingFlag
23
22
  def initialize
24
23
  @set = false
@@ -0,0 +1,89 @@
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
+ require 'serverengine/signals'
19
+
20
+ module ServerEngine
21
+ module CommandSender
22
+ # requires send_signal method or @pid
23
+ module Signal
24
+ private
25
+ def _stop(graceful)
26
+ _send_signal(!ServerEngine.windows? && graceful ? Signals::GRACEFUL_STOP : Signals::IMMEDIATE_STOP)
27
+ end
28
+
29
+ def _restart(graceful)
30
+ _send_signal(graceful ? Signals::GRACEFUL_RESTART : Signals::IMMEDIATE_RESTART)
31
+ end
32
+
33
+ def _reload
34
+ _send_signal(Signals::RELOAD)
35
+ end
36
+
37
+ def _detach
38
+ _send_signal(Signals::DETACH)
39
+ end
40
+
41
+ def _dump
42
+ _send_signal(Signals::DUMP)
43
+ end
44
+
45
+ def _send_signal(sig)
46
+ if respond_to?(:send_signal, true)
47
+ send_signal(sig)
48
+ else
49
+ Process.kill(sig, @pid)
50
+ end
51
+ end
52
+ end
53
+
54
+ # requires @command_sender_pipe
55
+ module Pipe
56
+ private
57
+ def _stop(graceful)
58
+ begin
59
+ _send_command(graceful ? "GRACEFUL_STOP" : "IMMEDIATE_STOP")
60
+ rescue Errno::EPIPE
61
+ # already stopped, then nothing to do
62
+ ensure
63
+ @command_sender_pipe.close rescue nil
64
+ @command_sender_pipe = nil
65
+ end
66
+ end
67
+
68
+ def _restart(graceful)
69
+ _send_command(graceful ? "GRACEFUL_RESTART" : "IMMEDIATE_RESTART")
70
+ end
71
+
72
+ def _reload
73
+ _send_command("RELOAD")
74
+ end
75
+
76
+ def _detach
77
+ _send_command("DETACH")
78
+ end
79
+
80
+ def _dump
81
+ _send_command("DUMP")
82
+ end
83
+
84
+ def _send_command(cmd)
85
+ @command_sender_pipe.write cmd + "\n"
86
+ end
87
+ end
88
+ end
89
+ end
@@ -15,6 +15,8 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
+ require 'serverengine/daemon_logger'
19
+
18
20
  module ServerEngine
19
21
 
20
22
  module ConfigLoader