serverengine 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,73 @@
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
+ module ConfigLoader
21
+ def initialize(load_config_proc={}, &block)
22
+ if block
23
+ @load_config_proc = block
24
+ else
25
+ if load_config_proc.is_a?(Hash)
26
+ @load_config_proc = lambda { load_config_proc }
27
+ else
28
+ @load_config_proc = load_config_proc
29
+ end
30
+ end
31
+ end
32
+
33
+ attr_reader :config
34
+ attr_accessor :logger
35
+
36
+ def reload_config
37
+ @config = @load_config_proc.call
38
+
39
+ @logger_class = @config[:logger_class] || DaemonLogger
40
+
41
+ case c = @config[:log]
42
+ when nil # default
43
+ @log_dev = STDERR
44
+ when "-"
45
+ @log_dev = STDOUT
46
+ else
47
+ @log_dev = c
48
+ end
49
+
50
+ if @logger
51
+ if @log_dev.is_a?(String)
52
+ @logger.path = @log_dev
53
+ end
54
+
55
+ @logger.level = @config[:log_level] || 'debug'
56
+ end
57
+
58
+ nil
59
+ end
60
+
61
+ private
62
+
63
+ def create_logger
64
+ if logger = @config[:logger]
65
+ @logger = logger
66
+ return
67
+ end
68
+
69
+ @logger = @logger_class.new(@log_dev, @config)
70
+ end
71
+ end
72
+
73
+ end
@@ -0,0 +1,165 @@
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
+ require 'shellwords'
21
+
22
+ class Daemon
23
+ include ConfigLoader
24
+
25
+ def initialize(server_module, worker_module, load_config_proc={}, &block)
26
+ @server_module = server_module
27
+ @worker_module = worker_module
28
+
29
+ super(load_config_proc, &block)
30
+
31
+ reload_config
32
+
33
+ @daemonize = @config.fetch(:daemonize, false)
34
+
35
+ if @config.fetch(:supervisor, false)
36
+ @create_server_proc = lambda do |load_config_proc,logger|
37
+ s = Supervisor.new(server_module, worker_module, load_config_proc)
38
+ s.logger = logger
39
+ s
40
+ end
41
+ else
42
+ @create_server_proc = Supervisor.create_server_proc(server_module, worker_module, @config)
43
+ end
44
+
45
+ @daemon_process_name = @config[:daemon_process_name]
46
+
47
+ @pid_path = @config[:pid_path]
48
+ @chuser = @config[:chuser]
49
+ @chgroup = @config[:chgroup]
50
+ @chumask = @config[:chumask]
51
+ end
52
+
53
+ module Signals
54
+ GRACEFUL_STOP = :TERM
55
+ IMMEDIATE_STOP = :QUIT
56
+ GRACEFUL_RESTART = :USR1
57
+ IMMEDIATE_RESTART = :HUP
58
+ RELOAD = :USR2
59
+ DETACH = :INT
60
+ DUMP = :CONT
61
+ end
62
+
63
+ def self.change_privilege(user, group)
64
+ if group
65
+ chgid = group.to_i
66
+ if chgid.to_s != group
67
+ chgid = `id -g #{Shellwords.escape group}`.to_i
68
+ exit! 1 unless $?.success?
69
+ end
70
+ Process::GID.change_privilege(chgid)
71
+ end
72
+
73
+ if user
74
+ chuid = user.to_i
75
+ if chuid.to_s != user
76
+ chuid = `id -u #{Shellwords.escape user}`.to_i
77
+ exit! 1 unless $?.success?
78
+ end
79
+
80
+ user_groups = `id -G #{Shellwords.escape user}`.split.map(&:to_i)
81
+ exit! 1 unless $?.success?
82
+
83
+ Process.groups = Process.groups | user_groups
84
+ Process::UID.change_privilege(chuid)
85
+ end
86
+
87
+ nil
88
+ end
89
+
90
+ def run
91
+ begin
92
+ exit main
93
+ rescue
94
+ ServerEngine.dump_uncaught_error($!)
95
+ exit 1 # TODO exit code
96
+ end
97
+ end
98
+
99
+ def main
100
+ unless @daemonize
101
+ s = create_server(create_logger)
102
+ s.install_signal_handlers
103
+ s.main
104
+ return 0
105
+ end
106
+
107
+ rpipe, wpipe = IO.pipe
108
+ wpipe.sync = true
109
+
110
+ Process.fork do
111
+ begin
112
+ rpipe.close
113
+
114
+ Process.setsid
115
+ Process.fork do
116
+ $0 = @daemon_process_name if @daemon_process_name
117
+ wpipe.write "#{Process.pid}\n"
118
+
119
+ Daemon.change_privilege(@chumask, @chgroup)
120
+ File.umask(@chumask) if @chumask
121
+
122
+ s = create_server(create_logger)
123
+
124
+ STDIN.reopen("/dev/null")
125
+ STDOUT.reopen("/dev/null", "wb")
126
+ STDERR.reopen("/dev/null", "wb")
127
+
128
+ s.install_signal_handlers
129
+
130
+ wpipe.write "\n"
131
+ wpipe.close
132
+
133
+ s.main
134
+ end
135
+
136
+ exit 0
137
+ ensure
138
+ exit! 1 # TODO exit code
139
+ end
140
+ end
141
+
142
+ wpipe.close
143
+
144
+ pid = rpipe.gets.to_i
145
+ if @pid_path
146
+ File.open(@pid_path, "w") {|f|
147
+ f.write "#{pid}\n"
148
+ }
149
+ end
150
+
151
+ data = rpipe.read
152
+ if data != "\n"
153
+ return 1 # TODO exit code
154
+ end
155
+
156
+ return 0
157
+ end
158
+
159
+ private
160
+
161
+ def create_server(logger)
162
+ @create_server_proc.call(@load_config_proc, logger)
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,110 @@
1
+ #
2
+ # ServerEngine
3
+ #
4
+ # Copyright (C) 2012 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
+ require 'logger'
21
+
22
+ class DaemonLogger < Logger
23
+ def initialize(dev, config={})
24
+ @hook_stdout = config.fetch(:log_stdout, true)
25
+ @hook_stderr = config.fetch(:log_stderr, true)
26
+ rotate_age = config[:log_rotate_age] || 5
27
+ rotate_size = config[:log_rotate_size] || 1048576
28
+
29
+ if dev.is_a?(String)
30
+ @path = dev
31
+ @io = File.open(@path, "a")
32
+ @io.sync = true
33
+ else
34
+ @io = dev
35
+ end
36
+
37
+ hook_stdout! if @hook_stdout
38
+ hook_stderr! if @hook_stderr
39
+
40
+ super(@io, rotate_age, rotate_size)
41
+
42
+ self.level = config[:level] || 'debug'
43
+ end
44
+
45
+ attr_accessor :path
46
+
47
+ def level=(expr)
48
+ case expr.to_s
49
+ when 'fatal', Logger::FATAL.to_s
50
+ e = Logger::FATAL
51
+ when 'error', Logger::ERROR.to_s
52
+ e = Logger::ERROR
53
+ when 'warn', Logger::WARN.to_s
54
+ e = Logger::WARN
55
+ when 'info', Logger::INFO.to_s
56
+ e = Logger::INFO
57
+ when 'debug', Logger::DEBUG.to_s
58
+ e = Logger::DEBUG
59
+ else
60
+ raise ArgumentError, "invalid log level: #{expr}"
61
+ end
62
+
63
+ super(e)
64
+ end
65
+
66
+ def hook_stdout!
67
+ STDOUT.sync = true
68
+ @hook_stdout = true
69
+
70
+ STDOUT.reopen(@io) if @io != STDOUT
71
+ self
72
+ end
73
+
74
+ def hook_stderr!
75
+ STDERR.sync = true
76
+ @hook_stderr = true
77
+
78
+ STDERR.reopen(@io) if @io != STDERR
79
+ self
80
+ end
81
+
82
+ def reopen!
83
+ if @path
84
+ @io.reopen(@path, "a")
85
+ @io.sync = true
86
+ hook_stdout! if @hook_stdout
87
+ hook_stderr! if @hook_stderr
88
+ end
89
+ nil
90
+ end
91
+
92
+ def reopen
93
+ begin
94
+ reopen!
95
+ return true
96
+ rescue
97
+ # TODO log?
98
+ return false
99
+ end
100
+ end
101
+
102
+ def close
103
+ if @path
104
+ @io.close unless @io.closed?
105
+ end
106
+ nil
107
+ end
108
+ end
109
+
110
+ end
@@ -0,0 +1,47 @@
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 EmbeddedServer < Server
21
+ def run
22
+ @worker = create_worker(0)
23
+ until @stop
24
+ @worker.main
25
+ end
26
+ end
27
+
28
+ def stop(stop_graceful)
29
+ super
30
+ Thread.new { @worker.stop }
31
+ nil
32
+ end
33
+
34
+ def restart(stop_graceful)
35
+ super
36
+ Thread.new { @worker.stop }
37
+ nil
38
+ end
39
+
40
+ def reload
41
+ super
42
+ Thread.new { @worker.reload }
43
+ nil
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,132 @@
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 MultiProcessServer < MultiWorkerServer
21
+ def initialize(worker_module, load_config_proc={}, &block)
22
+ @pm = ProcessManager.new(
23
+ auto_tick: false,
24
+ graceful_kill_signal: Daemon::Signals::GRACEFUL_STOP,
25
+ auto_heartbeat: true,
26
+ abort_on_heartbeat_error: Proc.new do
27
+ @logger.fatal "parent process unexpectedly terminated"
28
+ exit! 1
29
+ end
30
+ )
31
+
32
+ super(worker_module, load_config_proc, &block)
33
+
34
+ @worker_process_name = @config[:worker_process_name]
35
+ end
36
+
37
+ def run
38
+ super
39
+ ensure
40
+ @pm.close
41
+ end
42
+
43
+ def logger=(logger)
44
+ super
45
+ @pm.logger = logger
46
+ end
47
+
48
+ private
49
+
50
+ def reload_config
51
+ super
52
+
53
+ @chuser = @config[:worker_chuser]
54
+ @chgroup = @config[:worker_chgroup]
55
+ @chumask = @config[:worker_chumask]
56
+
57
+ @pm.configure(@config, prefix: 'worker_')
58
+
59
+ nil
60
+ end
61
+
62
+ def start_worker(wid)
63
+ w = create_worker(wid)
64
+
65
+ w.before_fork
66
+ begin
67
+ pmon = @pm.fork do |t|
68
+ $0 = @worker_process_name % [wid] if @worker_process_name
69
+ w.install_signal_handlers
70
+
71
+ Daemon.change_privilege(@chumask, @chgroup)
72
+ File.umask(@chumask) if @chumask
73
+
74
+ ## recreate the logger created at Server#main
75
+ #create_logger
76
+
77
+ w.main
78
+ end
79
+
80
+ ensure
81
+ w.close
82
+ end
83
+
84
+ return WorkerMonitor.new(w, wid, pmon)
85
+ end
86
+
87
+ def wait_tick
88
+ @pm.tick(0.5)
89
+ end
90
+
91
+ class WorkerMonitor
92
+ def initialize(worker, wid, pmon)
93
+ @worker = worker
94
+ @wid = wid
95
+ @pmon = pmon
96
+ end
97
+
98
+ def send_stop(stop_graceful)
99
+ @stop = true
100
+ if stop_graceful
101
+ @pmon.start_graceful_stop! if @pmon
102
+ else
103
+ @pmon.start_immediate_stop! if @pmon
104
+ end
105
+ nil
106
+ end
107
+
108
+ def send_reload
109
+ @pmon.send_signal(Daemon::Signals::RELOAD) if @pmon
110
+ nil
111
+ end
112
+
113
+ #def join
114
+ # @pmon.join if @pmon
115
+ # nil
116
+ #end
117
+
118
+ def alive?
119
+ return false unless @pmon
120
+
121
+ if stat = @pmon.try_join
122
+ @worker.logger.info "Worker #{@wid} finished#{@stop ? '' : ' unexpectedly'} with #{ProcessManager.format_join_status(stat)}"
123
+ @pmon = nil
124
+ return false
125
+ else
126
+ return true
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ end