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,67 @@
1
+ #
2
+ # ServerEngine
3
+ #
4
+ # Copyright (C) 2012-2013 Sadayuki Furuhashi
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/server'
19
+
20
+ module ServerEngine
21
+
22
+ class EmbeddedServer < Server
23
+ def run
24
+ @worker = create_worker(0)
25
+ until @stop
26
+ @worker.main
27
+ end
28
+ end
29
+
30
+ def stop(stop_graceful)
31
+ super
32
+ Thread.new do
33
+ begin
34
+ @worker.stop
35
+ rescue => e
36
+ ServerEngine.dump_uncaught_error(e)
37
+ end
38
+ end
39
+ nil
40
+ end
41
+
42
+ def restart(stop_graceful)
43
+ super
44
+ Thread.new do
45
+ begin
46
+ @worker.stop
47
+ rescue => e
48
+ ServerEngine.dump_uncaught_error(e)
49
+ end
50
+ end
51
+ nil
52
+ end
53
+
54
+ def reload
55
+ super
56
+ Thread.new do
57
+ begin
58
+ @worker.reload
59
+ rescue => e
60
+ ServerEngine.dump_uncaught_error(e)
61
+ end
62
+ end
63
+ nil
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,155 @@
1
+ #
2
+ # ServerEngine
3
+ #
4
+ # Copyright (C) 2012-2013 Sadayuki Furuhashi
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
+ require 'serverengine/daemon'
20
+ require 'serverengine/process_manager'
21
+ require 'serverengine/multi_worker_server'
22
+ require 'serverengine/privilege'
23
+
24
+ module ServerEngine
25
+
26
+ class MultiProcessServer < MultiWorkerServer
27
+ def initialize(worker_module, load_config_proc={}, &block)
28
+ @pm = ProcessManager.new(
29
+ auto_tick: false,
30
+ graceful_kill_signal: Signals::GRACEFUL_STOP,
31
+ immediate_kill_signal: Signals::IMMEDIATE_STOP,
32
+ enable_heartbeat: true,
33
+ auto_heartbeat: true,
34
+ on_heartbeat_error: Proc.new do
35
+ @logger.fatal "parent process unexpectedly terminated"
36
+ exit 1
37
+ end
38
+ )
39
+
40
+ super(worker_module, load_config_proc, &block)
41
+
42
+ @worker_process_name = @config[:worker_process_name]
43
+ @unrecoverable_exit_codes = @config.fetch(:unrecoverable_exit_codes, [])
44
+ end
45
+
46
+ def run
47
+ super
48
+ ensure
49
+ @pm.close
50
+ end
51
+
52
+ def logger=(logger)
53
+ super
54
+ @pm.logger = logger
55
+ end
56
+
57
+ private
58
+
59
+ def reload_config
60
+ super
61
+
62
+ @chuser = @config[:worker_chuser]
63
+ @chgroup = @config[:worker_chgroup]
64
+ @chumask = @config[:worker_chumask]
65
+
66
+ @pm.configure(@config, prefix: 'worker_')
67
+
68
+ nil
69
+ end
70
+
71
+ def start_worker(wid)
72
+ w = create_worker(wid)
73
+
74
+ w.before_fork
75
+ begin
76
+ pmon = @pm.fork do |t|
77
+ $0 = @worker_process_name % [wid] if @worker_process_name
78
+ w.install_signal_handlers
79
+
80
+ Privilege.change(@chuser, @chgroup)
81
+ File.umask(@chumask) if @chumask
82
+
83
+ ## recreate the logger created at Server#main
84
+ #create_logger
85
+
86
+ w.main
87
+ end
88
+
89
+ ensure
90
+ w.after_start
91
+ end
92
+
93
+ return WorkerMonitor.new(w, wid, pmon, unrecoverable_exit_codes: @unrecoverable_exit_codes)
94
+ end
95
+
96
+ def wait_tick
97
+ @pm.tick(0.5)
98
+ end
99
+
100
+ class WorkerMonitor
101
+ def initialize(worker, wid, pmon, reload_signal = Signals::RELOAD, unrecoverable_exit_codes: [])
102
+ @worker = worker
103
+ @wid = wid
104
+ @pmon = pmon
105
+ @reload_signal = reload_signal
106
+ @unrecoverable_exit_codes = unrecoverable_exit_codes
107
+ @unrecoverable_exit = false
108
+ @exitstatus = nil
109
+ end
110
+
111
+ attr_reader :exitstatus
112
+
113
+ def send_stop(stop_graceful)
114
+ @stop = true
115
+ if stop_graceful
116
+ @pmon.start_graceful_stop! if @pmon
117
+ else
118
+ @pmon.start_immediate_stop! if @pmon
119
+ end
120
+ nil
121
+ end
122
+
123
+ def send_reload
124
+ @pmon.send_signal(@reload_signal) if @pmon
125
+ nil
126
+ end
127
+
128
+ def join
129
+ @pmon.join if @pmon
130
+ nil
131
+ end
132
+
133
+ def alive?
134
+ return false unless @pmon
135
+
136
+ if stat = @pmon.try_join
137
+ @worker.logger.info "Worker #{@wid} finished#{@stop ? '' : ' unexpectedly'} with #{ServerEngine.format_join_status(stat)}"
138
+ if stat.is_a?(Process::Status) && stat.exited? && @unrecoverable_exit_codes.include?(stat.exitstatus)
139
+ @unrecoverable_exit = true
140
+ @exitstatus = stat.exitstatus
141
+ end
142
+ @pmon = nil
143
+ return false
144
+ else
145
+ return true
146
+ end
147
+ end
148
+
149
+ def recoverable?
150
+ !@unrecoverable_exit
151
+ end
152
+ end
153
+ end
154
+
155
+ end
@@ -0,0 +1,95 @@
1
+ #
2
+ # ServerEngine
3
+ #
4
+ # Copyright (C) 2012-2013 Sadayuki Furuhashi
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
+ require 'serverengine/process_manager'
20
+ require 'serverengine/multi_worker_server'
21
+
22
+ module ServerEngine
23
+
24
+ class MultiSpawnServer < MultiWorkerServer
25
+ def initialize(worker_module, load_config_proc={}, &block)
26
+ if ServerEngine.windows?
27
+ @pm = ProcessManager.new(
28
+ auto_tick: false,
29
+ graceful_kill_signal: Signals::GRACEFUL_STOP,
30
+ immediate_kill_signal: false,
31
+ enable_heartbeat: false,
32
+ )
33
+ else
34
+ @pm = ProcessManager.new(
35
+ auto_tick: false,
36
+ graceful_kill_signal: Signals::GRACEFUL_STOP,
37
+ immediate_kill_signal: Signals::IMMEDIATE_STOP,
38
+ enable_heartbeat: false,
39
+ )
40
+ end
41
+
42
+ super(worker_module, load_config_proc, &block)
43
+
44
+ @reload_signal = @config[:worker_reload_signal]
45
+ @unrecoverable_exit_codes = @config.fetch(:unrecoverable_exit_codes, [])
46
+ @pm.command_sender = @command_sender
47
+ end
48
+
49
+ def stop(stop_graceful)
50
+ if @command_sender == "pipe"
51
+ @pm.command_sender_pipe.write(stop_graceful ? "GRACEFUL_STOP\n" : "IMMEDIATE_STOP\n")
52
+ end
53
+ super
54
+ end
55
+
56
+ def run
57
+ super
58
+ ensure
59
+ @pm.close
60
+ end
61
+
62
+ def logger=(logger)
63
+ super
64
+ @pm.logger = logger
65
+ end
66
+
67
+ private
68
+
69
+ def reload_config
70
+ super
71
+
72
+ @pm.configure(@config, prefix: 'worker_')
73
+
74
+ nil
75
+ end
76
+
77
+ def start_worker(wid)
78
+ w = create_worker(wid)
79
+
80
+ w.before_fork
81
+ begin
82
+ pmon = w.spawn(@pm)
83
+ ensure
84
+ w.after_start
85
+ end
86
+
87
+ return MultiProcessServer::WorkerMonitor.new(w, wid, pmon, @reload_signal, unrecoverable_exit_codes: @unrecoverable_exit_codes)
88
+ end
89
+
90
+ def wait_tick
91
+ @pm.tick(0.5)
92
+ end
93
+ end
94
+
95
+ end
@@ -0,0 +1,80 @@
1
+ #
2
+ # ServerEngine
3
+ #
4
+ # Copyright (C) 2012-2013 Sadayuki Furuhashi
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/multi_worker_server'
19
+
20
+ module ServerEngine
21
+
22
+ class MultiThreadServer < MultiWorkerServer
23
+ private
24
+
25
+ def start_worker(wid)
26
+ w = create_worker(wid)
27
+
28
+ w.before_fork
29
+ begin
30
+ thread = Thread.new(&w.method(:main))
31
+ ensure
32
+ w.after_start
33
+ end
34
+
35
+ return WorkerMonitor.new(w, thread)
36
+ end
37
+
38
+ class WorkerMonitor
39
+ def initialize(worker, thread)
40
+ @worker = worker
41
+ @thread = thread
42
+ end
43
+
44
+ def send_stop(stop_graceful)
45
+ Thread.new do
46
+ begin
47
+ @worker.stop
48
+ rescue => e
49
+ ServerEngine.dump_uncaught_error(e)
50
+ end
51
+ end
52
+ nil
53
+ end
54
+
55
+ def send_reload
56
+ Thread.new do
57
+ begin
58
+ @worker.reload
59
+ rescue => e
60
+ ServerEngine.dump_uncaught_error(e)
61
+ end
62
+ end
63
+ nil
64
+ end
65
+
66
+ def join
67
+ @thread.join
68
+ end
69
+
70
+ def alive?
71
+ @thread.alive?
72
+ end
73
+
74
+ def recoverable?
75
+ true
76
+ end
77
+ end
78
+ end
79
+
80
+ end
@@ -0,0 +1,150 @@
1
+ #
2
+ # ServerEngine
3
+ #
4
+ # Copyright (C) 2012-2013 Sadayuki Furuhashi
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/server'
19
+
20
+ module ServerEngine
21
+
22
+ class MultiWorkerServer < Server
23
+ def initialize(worker_module, load_config_proc={}, &block)
24
+ @monitors = []
25
+ @last_start_worker_time = 0
26
+
27
+ super(worker_module, load_config_proc, &block)
28
+
29
+ @stop_immediately_at_unrecoverable_exit = @config.fetch(:stop_immediately_at_unrecoverable_exit, false)
30
+ end
31
+
32
+ def stop(stop_graceful)
33
+ super
34
+ @monitors.each do |m|
35
+ m.send_stop(stop_graceful) if m
36
+ end
37
+ nil
38
+ end
39
+
40
+ def restart(stop_graceful)
41
+ super
42
+ @monitors.each do |m|
43
+ m.send_stop(stop_graceful) if m
44
+ end
45
+ nil
46
+ end
47
+
48
+ def reload
49
+ super
50
+ @monitors.each_with_index do |m|
51
+ m.send_reload if m
52
+ end
53
+ nil
54
+ end
55
+
56
+ def run
57
+ while true
58
+ num_alive = keepalive_workers
59
+ break if num_alive == 0
60
+ wait_tick
61
+ end
62
+ end
63
+
64
+ def scale_workers(n)
65
+ @num_workers = n
66
+
67
+ plus = n - @monitors.size
68
+ if plus > 0
69
+ @monitors.concat Array.new(plus, nil)
70
+ end
71
+
72
+ nil
73
+ end
74
+
75
+ def join_workers
76
+ @monitors.each {|m|
77
+ m.join if m
78
+ }
79
+ end
80
+
81
+ private
82
+
83
+ def reload_config
84
+ super
85
+
86
+ @start_worker_delay = @config[:start_worker_delay] || 0
87
+ @start_worker_delay_rand = @config[:start_worker_delay_rand] || 0.2
88
+
89
+ scale_workers(@config[:workers] || 1)
90
+
91
+ nil
92
+ end
93
+
94
+ def wait_tick
95
+ sleep 0.5
96
+ end
97
+
98
+ def keepalive_workers
99
+ num_alive = 0
100
+
101
+ @monitors.each_with_index do |m,wid|
102
+ if m && m.alive?
103
+ # alive
104
+ num_alive += 1
105
+
106
+ elsif m && m.respond_to?(:recoverable?) && !m.recoverable?
107
+ # exited, with unrecoverable exit code
108
+ if @stop_immediately_at_unrecoverable_exit
109
+ stop(true) # graceful stop for workers
110
+ # @stop is set by Server#stop
111
+ end
112
+ # server will stop when all workers exited in this state
113
+ # the last status will be used for server/supervisor/daemon
114
+ @stop_status = m.exitstatus if m.exitstatus
115
+
116
+ elsif wid < @num_workers
117
+ # scale up or reboot
118
+ unless @stop
119
+ @monitors[wid] = delayed_start_worker(wid)
120
+ num_alive += 1
121
+ end
122
+
123
+ elsif m
124
+ # scale down
125
+ @monitors[wid] = nil
126
+ end
127
+ end
128
+
129
+ return num_alive
130
+ end
131
+
132
+ def delayed_start_worker(wid)
133
+ if @start_worker_delay > 0
134
+ delay = @start_worker_delay +
135
+ Kernel.rand * @start_worker_delay * @start_worker_delay_rand -
136
+ @start_worker_delay * @start_worker_delay_rand / 2
137
+
138
+ now = Time.now.to_f
139
+
140
+ wait = delay - (now - @last_start_worker_time)
141
+ sleep wait if wait > 0
142
+
143
+ @last_start_worker_time = now
144
+ end
145
+
146
+ start_worker(wid)
147
+ end
148
+ end
149
+
150
+ end