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,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