serverengine 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,103 @@
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
+ module ServerEngine
19
+
20
+ class Server
21
+ include ConfigLoader
22
+
23
+ def initialize(worker_module, load_config_proc={}, &block)
24
+ @worker_module = worker_module
25
+
26
+ @stop = false
27
+
28
+ super(load_config_proc, &block)
29
+
30
+ reload_config
31
+ end
32
+
33
+ def before_run
34
+ end
35
+
36
+ def after_run
37
+ end
38
+
39
+ def stop(stop_graceful)
40
+ @stop = true
41
+ nil
42
+ end
43
+
44
+ def close
45
+ end
46
+
47
+ def restart(stop_graceful)
48
+ reload_config
49
+ @logger.reopen! if @logger
50
+ nil
51
+ end
52
+
53
+ def reload
54
+ reload_config
55
+ @logger.reopen! if @logger
56
+ nil
57
+ end
58
+
59
+ def install_signal_handlers
60
+ s = self
61
+ SignalThread.new do |st|
62
+ st.trap(Daemon::Signals::GRACEFUL_STOP) { s.stop(true) }
63
+ st.trap(Daemon::Signals::IMMEDIATE_STOP) { s.stop(false) }
64
+ st.trap(Daemon::Signals::GRACEFUL_RESTART) { s.restart(true) }
65
+ st.trap(Daemon::Signals::IMMEDIATE_RESTART) { s.restart(false) }
66
+ st.trap(Daemon::Signals::RELOAD) { s.reload }
67
+ st.trap(Daemon::Signals::DETACH) { s.stop(true) }
68
+ st.trap(Daemon::Signals::DUMP) { Sigdump.dump }
69
+ end
70
+ end
71
+
72
+ def main
73
+ create_logger unless @logger
74
+
75
+ before_run
76
+
77
+ begin
78
+ run
79
+ ensure
80
+ after_run
81
+ end
82
+
83
+ ensure
84
+ close
85
+ end
86
+
87
+ module WorkerInitializer
88
+ def initialize
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def create_worker(wid)
95
+ w = Worker.new(self, wid)
96
+ w.extend(WorkerInitializer)
97
+ w.extend(@worker_module)
98
+ w.instance_eval { initialize }
99
+ w
100
+ end
101
+ end
102
+
103
+ end
@@ -0,0 +1,143 @@
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
+ module ServerEngine
19
+
20
+ class SignalThread < Thread
21
+ def initialize(&block)
22
+ require 'thread'
23
+
24
+ @handlers = {}
25
+
26
+ @mutex = Mutex.new
27
+ @cond = ConditionVariable.new
28
+ @queue = []
29
+ @finished = false
30
+
31
+ block.call(self) if block
32
+
33
+ super(&method(:main))
34
+ end
35
+
36
+ def trap(sig, command=nil, &block)
37
+ # normalize signal names
38
+ sig = sig.to_s.upcase
39
+ if sig[0,3] == "SIG"
40
+ sig = sig[3..-1]
41
+ end
42
+ sig = sig.to_sym
43
+
44
+ old = @handlers[sig]
45
+ if block
46
+ Kernel.trap(sig) { enqueue(sig) }
47
+ @handlers[sig] = block
48
+ else
49
+ Kernel.trap(sig, command)
50
+ @handlers.delete(sig)
51
+ end
52
+
53
+ old
54
+ end
55
+
56
+ def handlers
57
+ handlers.dup
58
+ end
59
+
60
+ def stop
61
+ @mutex.synchronize do
62
+ ## synchronized state 1
63
+ @finished = true
64
+ @cond.broadcast
65
+ ## synchronized state 2
66
+ end
67
+ self
68
+ end
69
+
70
+ private
71
+
72
+ def main
73
+ @mutex.lock
74
+
75
+ until @finished
76
+ ## synchronized state 3
77
+
78
+ sig = @queue.shift
79
+ unless sig
80
+ ## synchronized state 4
81
+ @cond.wait(@mutex, 1)
82
+ next
83
+ end
84
+
85
+ ## synchronized state 5
86
+
87
+ @mutex.unlock
88
+ begin
89
+ @handlers[sig].call(sig)
90
+ rescue
91
+ ServerEngine.dump_uncaught_error($!)
92
+ ensure
93
+ @mutex.lock
94
+ end
95
+ end
96
+
97
+ ## synchronized state 6
98
+ nil
99
+
100
+ ensure
101
+ @mutex.unlock
102
+ @finished = false
103
+ end
104
+
105
+ def enqueue(sig)
106
+ @queue << sig
107
+
108
+ unless @mutex.try_lock
109
+ #
110
+ # here couldn't acquire @mutex.
111
+ #
112
+ # A) a thread is in synchronized state 1 or 2.
113
+ # In this case, here doesn't have to broadcast because the thread will/did broadcast.
114
+ #
115
+ # B) `self` thread is in synchronized state 3
116
+ # In this case, here doesn't have to broadcast because the `self` thread will
117
+ # take a task from the queue soon.
118
+ #
119
+ # C) `self` thread is in synchronized state 4
120
+ # In this case, here needs to broadcast but doesn't broadcast. Thus it causes
121
+ # blocking upto 1 second :(
122
+ #
123
+ # D) `self` thread is in synchronized state 5
124
+ # In this case, here doesn't have to broadcast because the `self` thread will
125
+ # change to synchronized state 3 or 6 soon.
126
+ #
127
+ # E) the main thread (the only thread which calls this method) is in synchronized
128
+ # state 7. In this case, here doesn't have to broadcast.
129
+ #
130
+ return
131
+ end
132
+
133
+ ## synchronized state 7
134
+
135
+ begin
136
+ @cond.broadcast
137
+ ensure
138
+ @mutex.unlock
139
+ end
140
+ end
141
+
142
+ end
143
+ end
@@ -0,0 +1,230 @@
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
+ module ServerEngine
19
+
20
+ class Supervisor
21
+ include ConfigLoader
22
+
23
+ def initialize(server_module, worker_module, load_config_proc={}, &block)
24
+ @server_module = server_module
25
+ @worker_module = worker_module
26
+
27
+ @detach_flag = BlockingFlag.new
28
+ @stop = false
29
+
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
+ auto_heartbeat: true,
35
+ abort_on_heartbeat_error: false,
36
+ )
37
+
38
+ super(load_config_proc, &block)
39
+
40
+ reload_config
41
+
42
+ @create_server_proc = Supervisor.create_server_proc(server_module, worker_module, @config)
43
+ @server_process_name = @config[:server_process_name]
44
+
45
+ @restart_server_process = !!@config[:restart_server_process]
46
+ @enable_detach = !!@config[:enable_detach]
47
+ @exit_on_detach = !!@config[:exit_on_detach]
48
+ @disable_reload = !!@config[:disable_reload]
49
+ end
50
+
51
+ def reload_config
52
+ super
53
+
54
+ @server_detach_wait = @config[:server_detach_wait] || 10.0
55
+ @server_restart_wait = @config[:server_restart_wait] || 1.0
56
+
57
+ @pm.configure(@config, prefix: 'server_')
58
+
59
+ nil
60
+ end
61
+
62
+ module ServerInitializer
63
+ def initialize
64
+ reload_config
65
+ end
66
+ end
67
+
68
+ def self.create_server_proc(server_module, worker_module, config)
69
+ wt = config[:worker_type] || 'embedded'
70
+ case wt
71
+ when 'embedded'
72
+ server_class = EmbeddedServer
73
+ when 'process'
74
+ server_class = MultiProcessServer
75
+ when 'thread'
76
+ server_class = MultiThreadServer
77
+ else
78
+ raise ArgumentError, "unexpected :worker_type option #{wt}"
79
+ end
80
+
81
+ lambda {|load_config_proc,logger|
82
+ s = server_class.new(worker_module, load_config_proc)
83
+ s.logger = logger
84
+ s.extend(ServerInitializer)
85
+ s.extend(server_module) if server_module
86
+ s.instance_eval { initialize }
87
+ s
88
+ }
89
+ end
90
+
91
+ def create_server(logger)
92
+ @create_server_proc.call(@load_config_proc, logger)
93
+ end
94
+
95
+ def stop(stop_graceful)
96
+ @stop = true
97
+ send_signal(stop_graceful ? Daemon::Signals::GRACEFUL_STOP : Daemon::Signals::IMMEDIATE_STOP)
98
+ end
99
+
100
+ def restart(stop_graceful)
101
+ reload_config
102
+ @logger.reopen! if @logger
103
+ if @restart_server_process
104
+ send_signal(stop_graceful ? Daemon::Signals::GRACEFUL_STOP : Daemon::Signals::IMMEDIATE_STOP)
105
+ else
106
+ send_signal(stop_graceful ? Daemon::Signals::GRACEFUL_RESTART : Daemon::Signals::IMMEDIATE_RESTART)
107
+ end
108
+ end
109
+
110
+ def reload
111
+ unless @disable_reload
112
+ reload_config
113
+ end
114
+ @logger.reopen! if @logger
115
+ send_signal(Daemon::Signals::RELOAD)
116
+ end
117
+
118
+ def detach(stop_graceful)
119
+ if @enable_detach
120
+ @detach_flag.set!
121
+ send_signal(stop_graceful ? Daemon::Signals::GRACEFUL_STOP : Daemon::Signals::IMMEDIATE_STOP)
122
+ else
123
+ stop(stop_graceful)
124
+ end
125
+ end
126
+
127
+ def install_signal_handlers
128
+ s = self
129
+ SignalThread.new do |st|
130
+ st.trap(Daemon::Signals::GRACEFUL_STOP) { s.stop(true) }
131
+ st.trap(Daemon::Signals::IMMEDIATE_STOP) { s.stop(false) }
132
+ st.trap(Daemon::Signals::GRACEFUL_RESTART) { s.restart(true) }
133
+ st.trap(Daemon::Signals::IMMEDIATE_RESTART) { s.restart(false) }
134
+ st.trap(Daemon::Signals::RELOAD) { s.reload }
135
+ st.trap(Daemon::Signals::DETACH) { s.detach(true) }
136
+ st.trap(Daemon::Signals::DUMP) { Sigdump.dump }
137
+ end
138
+ end
139
+
140
+ def main
141
+ # just in case Supervisor is not created by Daemon
142
+ create_logger unless @logger
143
+
144
+ @pmon = start_server
145
+
146
+ while true
147
+ # keep the child process alive in this loop
148
+ until @detach_flag.wait(0.5)
149
+ if stat = try_join
150
+ return if @stop # supervisor stoppped explicitly
151
+
152
+ # child process died unexpectedly.
153
+ # sleep @server_detach_wait sec and reboot process
154
+ @pmon = reboot_server
155
+ end
156
+ end
157
+
158
+ wait_until = Time.now + @server_detach_wait
159
+ while (w = wait_until - Time.now) > 0
160
+ break if try_join
161
+ sleep [0.5, w].min
162
+ end
163
+
164
+ return if @exit_on_detach
165
+
166
+ @detach_flag.reset!
167
+ end
168
+ end
169
+
170
+ def logger=(logger)
171
+ super
172
+ @pm.logger = @logger
173
+ end
174
+
175
+ private
176
+
177
+ def send_signal(sig)
178
+ @pmon.send_signal(sig) if @pmon
179
+ nil
180
+ end
181
+
182
+ def try_join
183
+ if stat = @pmon.try_join
184
+ @logger.info "Server finished#{@stop ? '' : ' unexpectedly'} with #{ProcessManager.format_join_status(stat)}"
185
+ @pmon = nil
186
+ return stat
187
+ else
188
+ @pm.tick
189
+ return false
190
+ end
191
+ end
192
+
193
+ def start_server
194
+ s = create_server(logger)
195
+ @last_start_time = Time.now
196
+
197
+ begin
198
+ m = @pm.fork do
199
+ $0 = @server_process_name if @server_process_name
200
+ s.install_signal_handlers
201
+
202
+ s.main
203
+ end
204
+
205
+ return m
206
+ ensure
207
+ s.close
208
+ end
209
+ end
210
+
211
+ def reboot_server
212
+ # try reboot for ever until @detach_flag is set
213
+ while true
214
+ wait = @server_restart_wait - (Time.now - @last_start_time)
215
+ if @detach_flag.wait(wait > 0 ? wait : 0.1)
216
+ break
217
+ end
218
+
219
+ begin
220
+ return start_server
221
+ rescue
222
+ ServerEngine.dump_uncaught_error($!)
223
+ end
224
+ end
225
+
226
+ return nil
227
+ end
228
+ end
229
+
230
+ end