serverengine 1.5.0

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.
@@ -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