serverengine 2.0.0pre1-x64-mingw32

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +20 -0
  5. data/Changelog +122 -0
  6. data/Gemfile +2 -0
  7. data/LICENSE +202 -0
  8. data/NOTICE +3 -0
  9. data/README.md +514 -0
  10. data/Rakefile +26 -0
  11. data/appveyor.yml +24 -0
  12. data/examples/server.rb +138 -0
  13. data/examples/spawn_worker_script.rb +38 -0
  14. data/lib/serverengine.rb +46 -0
  15. data/lib/serverengine/blocking_flag.rb +77 -0
  16. data/lib/serverengine/command_sender.rb +89 -0
  17. data/lib/serverengine/config_loader.rb +82 -0
  18. data/lib/serverengine/daemon.rb +233 -0
  19. data/lib/serverengine/daemon_logger.rb +135 -0
  20. data/lib/serverengine/embedded_server.rb +67 -0
  21. data/lib/serverengine/multi_process_server.rb +155 -0
  22. data/lib/serverengine/multi_spawn_server.rb +95 -0
  23. data/lib/serverengine/multi_thread_server.rb +80 -0
  24. data/lib/serverengine/multi_worker_server.rb +150 -0
  25. data/lib/serverengine/privilege.rb +57 -0
  26. data/lib/serverengine/process_manager.rb +508 -0
  27. data/lib/serverengine/server.rb +178 -0
  28. data/lib/serverengine/signal_thread.rb +116 -0
  29. data/lib/serverengine/signals.rb +31 -0
  30. data/lib/serverengine/socket_manager.rb +171 -0
  31. data/lib/serverengine/socket_manager_unix.rb +98 -0
  32. data/lib/serverengine/socket_manager_win.rb +154 -0
  33. data/lib/serverengine/supervisor.rb +313 -0
  34. data/lib/serverengine/utils.rb +62 -0
  35. data/lib/serverengine/version.rb +3 -0
  36. data/lib/serverengine/winsock.rb +128 -0
  37. data/lib/serverengine/worker.rb +81 -0
  38. data/serverengine.gemspec +37 -0
  39. data/spec/blocking_flag_spec.rb +59 -0
  40. data/spec/daemon_logger_spec.rb +175 -0
  41. data/spec/daemon_spec.rb +169 -0
  42. data/spec/multi_process_server_spec.rb +113 -0
  43. data/spec/server_worker_context.rb +232 -0
  44. data/spec/signal_thread_spec.rb +94 -0
  45. data/spec/socket_manager_spec.rb +119 -0
  46. data/spec/spec_helper.rb +19 -0
  47. data/spec/supervisor_spec.rb +215 -0
  48. metadata +184 -0
@@ -0,0 +1,169 @@
1
+
2
+ describe ServerEngine::Daemon do
3
+ include_context 'test server and worker'
4
+
5
+ it 'run and graceful stop by signal' do
6
+ pending "not supported signal base commands on Windows" if ServerEngine.windows?
7
+
8
+ dm = Daemon.new(TestServer, TestWorker, daemonize: true, pid_path: "tmp/pid", command_sender: "signal")
9
+ dm.main
10
+
11
+ wait_for_fork
12
+
13
+ test_state(:server_initialize).should == 1
14
+
15
+ begin
16
+ dm.stop(true)
17
+ wait_for_stop
18
+
19
+ test_state(:server_stop_graceful).should == 1
20
+ test_state(:worker_stop).should == 1
21
+ test_state(:server_after_run).should == 1
22
+ ensure
23
+ dm.stop(false) rescue nil
24
+ end
25
+ end
26
+
27
+ it 'signals' do
28
+ pending "not supported signal base commands on Windows" if ServerEngine.windows?
29
+ dm = Daemon.new(TestServer, TestWorker, daemonize: true, pid_path: "tmp/pid", command_sender: "signal")
30
+ dm.main
31
+
32
+ wait_for_fork
33
+
34
+ begin
35
+ dm.reload
36
+ wait_for_stop
37
+ test_state(:server_reload).should == 1
38
+
39
+ dm.restart(true)
40
+ wait_for_stop
41
+ test_state(:server_restart_graceful).should == 1
42
+
43
+ dm.restart(false)
44
+ wait_for_stop
45
+ test_state(:server_restart_immediate).should == 1
46
+
47
+ dm.stop(false)
48
+ wait_for_stop
49
+ test_state(:server_stop_immediate).should == 1
50
+ ensure
51
+ dm.stop(true) rescue nil
52
+ end
53
+ end
54
+
55
+ it 'run and graceful stop by pipe' do
56
+ dm = Daemon.new(TestServer, TestWorker, daemonize: true, pid_path: "tmp/pid", windows_daemon_cmdline: windows_daemon_cmdline, command_sender: "pipe")
57
+ dm.main
58
+
59
+ wait_for_fork
60
+
61
+ test_state(:server_initialize).should == 1
62
+
63
+ begin
64
+ dm.stop(true)
65
+ wait_for_stop
66
+
67
+ test_state(:server_stop_graceful).should == 1
68
+ test_state(:worker_stop).should == 1
69
+ test_state(:server_after_run).should == 1
70
+ ensure
71
+ dm.stop(false) rescue nil
72
+ end
73
+ end
74
+
75
+ it 'recieve commands from pipe' do
76
+ dm = Daemon.new(TestServer, TestWorker, daemonize: true, pid_path: "tmp/pid", windows_daemon_cmdline: windows_daemon_cmdline, command_sender: "pipe")
77
+ dm.main
78
+
79
+ wait_for_fork
80
+
81
+ begin
82
+ dm.reload
83
+ wait_for_stop
84
+ test_state(:server_reload).should == 1
85
+
86
+ dm.restart(true)
87
+ wait_for_stop
88
+ test_state(:server_restart_graceful).should == 1
89
+
90
+ dm.restart(false)
91
+ wait_for_stop
92
+ test_state(:server_restart_immediate).should == 1
93
+
94
+ dm.stop(false)
95
+ wait_for_stop
96
+ test_state(:server_stop_immediate).should == 1
97
+ ensure
98
+ dm.stop(true) rescue nil
99
+ end
100
+ end
101
+
102
+ it 'exits with status 0 when it was stopped normally' do
103
+ pending "worker type process(fork) cannot be used in Windows" if ServerEngine.windows?
104
+ dm = Daemon.new(
105
+ TestServer,
106
+ TestWorker,
107
+ daemonize: false,
108
+ supervisor: false,
109
+ pid_path: "tmp/pid",
110
+ log_stdout: false,
111
+ log_stderr: false,
112
+ unrecoverable_exit_codes: [3,4,5],
113
+ )
114
+ exit_code = nil
115
+ t = Thread.new { exit_code = dm.main }
116
+ sleep 0.1 until dm.instance_eval{ @pid }
117
+ dm.stop(true)
118
+
119
+ t.join
120
+
121
+ exit_code.should == 0
122
+ end
123
+
124
+ it 'exits with status of workers if worker exits with status specified in unrecoverable_exit_codes, without supervisor' do
125
+ pending "worker type process(fork) cannot be used in Windows" if ServerEngine.windows?
126
+
127
+ dm = Daemon.new(
128
+ TestServer,
129
+ TestExitWorker,
130
+ daemonize: false,
131
+ supervisor: false,
132
+ worker_type: 'process',
133
+ pid_path: "tmp/pid",
134
+ log_stdout: false,
135
+ log_stderr: false,
136
+ unrecoverable_exit_codes: [3,4,5],
137
+ )
138
+ exit_code = nil
139
+ t = Thread.new { exit_code = dm.main }
140
+ sleep 0.1 until dm.instance_eval{ @pid }
141
+
142
+ t.join
143
+
144
+ exit_code.should == 5
145
+ end
146
+
147
+ it 'exits with status of workers if worker exits with status specified in unrecoverable_exit_codes, with supervisor' do
148
+ pending "worker type process(fork) cannot be used in Windows" if ServerEngine.windows?
149
+
150
+ dm = Daemon.new(
151
+ TestServer,
152
+ TestExitWorker,
153
+ daemonize: false,
154
+ supervisor: true,
155
+ worker_type: 'process',
156
+ pid_path: "tmp/pid",
157
+ log_stdout: false,
158
+ log_stderr: false,
159
+ unrecoverable_exit_codes: [3,4,5],
160
+ )
161
+ exit_code = nil
162
+ t = Thread.new { exit_code = dm.main }
163
+ sleep 0.1 until dm.instance_eval{ @pid }
164
+
165
+ t.join
166
+
167
+ exit_code.should == 5
168
+ end
169
+ end
@@ -0,0 +1,113 @@
1
+ [ServerEngine::MultiThreadServer, ServerEngine::MultiProcessServer].each do |impl_class|
2
+ # MultiProcessServer uses fork(2) internally, then it doesn't support Windows.
3
+
4
+ describe impl_class do
5
+ include_context 'test server and worker'
6
+
7
+ it 'scale up' do
8
+ pending "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
9
+
10
+ config = {workers: 2, log_stdout: false, log_stderr: false}
11
+
12
+ s = impl_class.new(TestWorker) { config.dup }
13
+ t = Thread.new { s.main }
14
+
15
+ begin
16
+ wait_for_fork
17
+ test_state(:worker_run).should == 2
18
+
19
+ config[:workers] = 3
20
+ s.reload
21
+
22
+ wait_for_restart
23
+ test_state(:worker_run).should == 3
24
+
25
+ test_state(:worker_stop).should == 0
26
+
27
+ ensure
28
+ s.stop(true)
29
+ t.join
30
+ end
31
+
32
+ test_state(:worker_stop).should == 3
33
+ end
34
+
35
+ it 'scale down' do
36
+ pending "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
37
+
38
+ config = {workers: 2, log_stdout: false, log_stderr: false}
39
+
40
+ s = impl_class.new(TestWorker) { config.dup }
41
+ t = Thread.new { s.main }
42
+
43
+ begin
44
+ wait_for_fork
45
+ test_state(:worker_run).should == 2
46
+
47
+ config[:workers] = 1
48
+ s.restart(true)
49
+
50
+ wait_for_restart
51
+ test_state(:worker_run).should == 3
52
+
53
+ test_state(:worker_stop).should == 2
54
+
55
+ ensure
56
+ s.stop(true)
57
+ t.join
58
+ end
59
+
60
+ test_state(:worker_stop).should == 3
61
+ end
62
+
63
+ it 'raises SystemExit when all workers exit with specified code by unrecoverable_exit_codes' do
64
+ pending "unrecoverable_exit_codes supported only for multi process workers" if impl_class == ServerEngine::MultiThreadServer
65
+ pending "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
66
+
67
+ config = {workers: 4, log_stdout: false, log_stderr: false, unrecoverable_exit_codes: [3, 4, 5]}
68
+
69
+ s = impl_class.new(TestExitWorker) { config.dup }
70
+ raised_error = nil
71
+ t = Thread.new do
72
+ begin
73
+ s.main
74
+ rescue SystemExit => e
75
+ raised_error = e
76
+ end
77
+ end
78
+
79
+ wait_for_fork
80
+ test_state(:worker_run).should == 4
81
+ t.join
82
+
83
+ test_state(:worker_stop).to_i.should == 0
84
+ raised_error.status.should == 3 # 4th process's exit status
85
+ end
86
+
87
+ it 'raises SystemExit immediately when a worker exits if stop_immediately_at_unrecoverable_exit specified' do
88
+ pending "unrecoverable_exit_codes supported only for multi process workers" if impl_class == ServerEngine::MultiThreadServer
89
+ pending "Windows environment does not support fork" if ServerEngine.windows? && impl_class == ServerEngine::MultiProcessServer
90
+
91
+ config = {workers: 4, log_stdout: false, log_stderr: false, unrecoverable_exit_codes: [3, 4, 5], stop_immediately_at_unrecoverable_exit: true}
92
+
93
+ s = impl_class.new(TestExitWorker) { config.dup }
94
+ raised_error = nil
95
+ t = Thread.new do
96
+ begin
97
+ s.main
98
+ rescue SystemExit => e
99
+ raised_error = e
100
+ end
101
+ end
102
+
103
+ wait_for_fork
104
+ test_state(:worker_run).should == 4
105
+ t.join
106
+
107
+ test_state(:worker_stop).to_i.should == 3
108
+ test_state(:worker_finished).to_i.should == 3
109
+ raised_error.should_not be_nil
110
+ raised_error.status.should == 5 # 1st process's exit status
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,232 @@
1
+
2
+ require 'thread'
3
+ require 'yaml'
4
+
5
+ def reset_test_state
6
+ FileUtils.mkdir_p 'tmp'
7
+ FileUtils.rm_f 'tmp/state.yml'
8
+ FileUtils.touch 'tmp/state.yml'
9
+ $state_file_mutex = Mutex.new
10
+ if ServerEngine.windows?
11
+ open("tmp/daemon.rb", "w") do |f|
12
+ f.puts <<-'end_of_script'
13
+ require "serverengine"
14
+ require "rspec"
15
+ $state_file_mutex = Mutex.new # TODO
16
+ require "server_worker_context"
17
+ include ServerEngine
18
+ command_pipe = STDIN.dup
19
+ STDIN.reopen(File::NULL)
20
+ Daemon.run_server(TestServer, TestWorker, command_pipe: command_pipe)
21
+ end_of_script
22
+ end
23
+
24
+ open("tmp/supervisor.rb", "w") do |f|
25
+ f.puts <<-'end_of_script'
26
+ require "serverengine"
27
+ require "rspec"
28
+ $state_file_mutex = Mutex.new # TODO
29
+ require "server_worker_context"
30
+ include ServerEngine
31
+ server = TestServer
32
+ worker = TestWorker
33
+ config = {command_pipe: STDIN.dup}
34
+ STDIN.reopen(File::NULL)
35
+ ARGV.each do |arg|
36
+ case arg
37
+ when /^server=(.*)$/
38
+ server = Object.const_get($1)
39
+ when /^worker=(.*)$/
40
+ worker = Object.const_get($1)
41
+ when /^(.*)=(\d+)$/
42
+ config[$1.to_sym] = $2.to_i
43
+ when /^(.*)=(.*)$/
44
+ config[$1.to_sym] = $2
45
+ else
46
+ raise "Unknown parameter: [#{arg}]"
47
+ end
48
+ end
49
+ sv = Supervisor.new(server, worker, config)
50
+ s = sv.create_server(nil)
51
+ s.install_signal_handlers
52
+ t = Thread.new{ s.main }
53
+ s.after_start
54
+ t.join
55
+ end_of_script
56
+ end
57
+ end
58
+ end
59
+
60
+ def windows_daemon_cmdline
61
+ if ServerEngine.windows?
62
+ [ServerEngine.ruby_bin_path, '-I', File.dirname(__FILE__), 'tmp/daemon.rb']
63
+ else
64
+ nil
65
+ end
66
+ end
67
+
68
+ def windows_supervisor_cmdline(server = nil, worker = nil, config = {})
69
+ if ServerEngine.windows?
70
+ cmd = [ServerEngine.ruby_bin_path, '-I', File.dirname(__FILE__), 'tmp/supervisor.rb']
71
+ cmd << "server=#{server}" if server
72
+ cmd << "worker=#{worker}" if worker
73
+ config.each_pair do |k, v|
74
+ cmd << "#{k}=#{v}"
75
+ end
76
+ cmd
77
+ else
78
+ nil
79
+ end
80
+ end
81
+
82
+ def incr_test_state(key)
83
+ File.open('tmp/state.yml', 'r+') do |f|
84
+ f.flock(File::LOCK_EX)
85
+
86
+ $state_file_mutex.synchronize do
87
+ data = YAML.load(f.read) || {} rescue {}
88
+ data[key] ||= 0
89
+ data[key] += 1
90
+
91
+ f.pos = 0
92
+ f.write YAML.dump(data)
93
+ data[key]
94
+ end
95
+ end
96
+ end
97
+
98
+ def test_state(key)
99
+ data = YAML.load_file('tmp/state.yml') || {} rescue {}
100
+ return data[key] || 0
101
+ end
102
+
103
+ module TestServer
104
+ def initialize
105
+ incr_test_state :server_initialize
106
+ end
107
+
108
+ def before_run
109
+ incr_test_state :server_before_run
110
+ end
111
+
112
+ def after_run
113
+ incr_test_state :server_after_run
114
+ end
115
+
116
+ def after_start
117
+ incr_test_state :server_after_start
118
+ end
119
+
120
+ def stop(stop_graceful)
121
+ incr_test_state :server_stop
122
+ if stop_graceful
123
+ incr_test_state :server_stop_graceful
124
+ else
125
+ incr_test_state :server_stop_immediate
126
+ end
127
+ super
128
+ end
129
+
130
+ def restart(stop_graceful)
131
+ incr_test_state :server_restart
132
+ if stop_graceful
133
+ incr_test_state :server_restart_graceful
134
+ else
135
+ incr_test_state :server_restart_immediate
136
+ end
137
+ super
138
+ end
139
+
140
+ def reload
141
+ incr_test_state :server_reload
142
+ super
143
+ end
144
+
145
+ def detach
146
+ incr_test_state :server_detach
147
+ super
148
+ end
149
+ end
150
+
151
+ module TestWorker
152
+ def initialize
153
+ incr_test_state :worker_initialize
154
+ @stop_flag = BlockingFlag.new
155
+ end
156
+
157
+ def before_fork
158
+ incr_test_state :worker_before_fork
159
+ end
160
+
161
+ def run
162
+ incr_test_state :worker_run
163
+ 5.times do
164
+ # repeats 5 times because signal handlers
165
+ # interrupts wait
166
+ @stop_flag.wait(5.0)
167
+ end
168
+ @stop_flag.reset!
169
+ end
170
+
171
+ def stop
172
+ incr_test_state :worker_stop
173
+ @stop_flag.set!
174
+ end
175
+
176
+ def reload
177
+ incr_test_state :worker_reload
178
+ end
179
+
180
+ def after_start
181
+ incr_test_state :worker_after_start
182
+ end
183
+ end
184
+
185
+ module RunErrorWorker
186
+ def run
187
+ incr_test_state :worker_run
188
+ raise StandardError, "error test"
189
+ end
190
+ end
191
+
192
+ module TestExitWorker
193
+ def initialize
194
+ @stop_flag = BlockingFlag.new
195
+ @worker_num = incr_test_state :worker_initialize
196
+ @exit_code = case @worker_num
197
+ when 1 then 5
198
+ when 4 then 3
199
+ else 4
200
+ end
201
+ end
202
+
203
+ def run
204
+ incr_test_state :worker_run
205
+ exit_at = Time.now + @worker_num * 2
206
+ until @stop_flag.wait(0.1)
207
+ exit!(@exit_code) if Time.now >= exit_at
208
+ end
209
+ incr_test_state :worker_finished
210
+ end
211
+
212
+ def stop
213
+ incr_test_state :worker_stop
214
+ @stop_flag.set!
215
+ end
216
+ end
217
+
218
+ shared_context 'test server and worker' do
219
+ before { reset_test_state }
220
+
221
+ def wait_for_fork
222
+ sleep 0.8
223
+ end
224
+
225
+ def wait_for_stop
226
+ sleep 0.8
227
+ end
228
+
229
+ def wait_for_restart
230
+ sleep 1.5
231
+ end
232
+ end