serverengine 2.0.0pre1-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
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