serverengine 2.0.0pre1-x64-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +2 -0
- data/.travis.yml +20 -0
- data/Changelog +122 -0
- data/Gemfile +2 -0
- data/LICENSE +202 -0
- data/NOTICE +3 -0
- data/README.md +514 -0
- data/Rakefile +26 -0
- data/appveyor.yml +24 -0
- data/examples/server.rb +138 -0
- data/examples/spawn_worker_script.rb +38 -0
- data/lib/serverengine.rb +46 -0
- data/lib/serverengine/blocking_flag.rb +77 -0
- data/lib/serverengine/command_sender.rb +89 -0
- data/lib/serverengine/config_loader.rb +82 -0
- data/lib/serverengine/daemon.rb +233 -0
- data/lib/serverengine/daemon_logger.rb +135 -0
- data/lib/serverengine/embedded_server.rb +67 -0
- data/lib/serverengine/multi_process_server.rb +155 -0
- data/lib/serverengine/multi_spawn_server.rb +95 -0
- data/lib/serverengine/multi_thread_server.rb +80 -0
- data/lib/serverengine/multi_worker_server.rb +150 -0
- data/lib/serverengine/privilege.rb +57 -0
- data/lib/serverengine/process_manager.rb +508 -0
- data/lib/serverengine/server.rb +178 -0
- data/lib/serverengine/signal_thread.rb +116 -0
- data/lib/serverengine/signals.rb +31 -0
- data/lib/serverengine/socket_manager.rb +171 -0
- data/lib/serverengine/socket_manager_unix.rb +98 -0
- data/lib/serverengine/socket_manager_win.rb +154 -0
- data/lib/serverengine/supervisor.rb +313 -0
- data/lib/serverengine/utils.rb +62 -0
- data/lib/serverengine/version.rb +3 -0
- data/lib/serverengine/winsock.rb +128 -0
- data/lib/serverengine/worker.rb +81 -0
- data/serverengine.gemspec +37 -0
- data/spec/blocking_flag_spec.rb +59 -0
- data/spec/daemon_logger_spec.rb +175 -0
- data/spec/daemon_spec.rb +169 -0
- data/spec/multi_process_server_spec.rb +113 -0
- data/spec/server_worker_context.rb +232 -0
- data/spec/signal_thread_spec.rb +94 -0
- data/spec/socket_manager_spec.rb +119 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/supervisor_spec.rb +215 -0
- metadata +184 -0
@@ -0,0 +1,178 @@
|
|
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/signal_thread'
|
20
|
+
require 'serverengine/worker'
|
21
|
+
|
22
|
+
module ServerEngine
|
23
|
+
|
24
|
+
class Server
|
25
|
+
include ConfigLoader
|
26
|
+
|
27
|
+
def initialize(worker_module, load_config_proc={}, &block)
|
28
|
+
@worker_module = worker_module
|
29
|
+
|
30
|
+
@stop = false
|
31
|
+
@stop_status = nil
|
32
|
+
|
33
|
+
super(load_config_proc, &block)
|
34
|
+
|
35
|
+
@log_stdout = !!@config.fetch(:log_stdout, true)
|
36
|
+
@log_stderr = !!@config.fetch(:log_stderr, true)
|
37
|
+
@log_stdout = false if logdev_from_config(@config) == STDOUT
|
38
|
+
@log_stderr = false if logdev_from_config(@config) == STDERR
|
39
|
+
|
40
|
+
@command_sender = @config.fetch(:command_sender, ServerEngine.windows? ? "pipe" : "signal")
|
41
|
+
@command_pipe = @config.fetch(:command_pipe, nil)
|
42
|
+
end
|
43
|
+
|
44
|
+
def before_run
|
45
|
+
end
|
46
|
+
|
47
|
+
def after_run
|
48
|
+
end
|
49
|
+
|
50
|
+
def stop(stop_graceful)
|
51
|
+
@logger.info "Received #{stop_graceful ? 'graceful' : 'immediate'} stop" if @logger
|
52
|
+
@stop = true
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def after_start
|
57
|
+
end
|
58
|
+
|
59
|
+
def restart(stop_graceful)
|
60
|
+
@logger.info "Received #{stop_graceful ? 'graceful' : 'immediate'} restart" if @logger
|
61
|
+
reload_config
|
62
|
+
@logger.reopen! if @logger
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def reload
|
67
|
+
@logger.info "Received reload" if @logger
|
68
|
+
reload_config
|
69
|
+
@logger.reopen! if @logger
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def install_signal_handlers
|
74
|
+
s = self
|
75
|
+
if @command_pipe
|
76
|
+
Thread.new do
|
77
|
+
until @command_pipe.closed?
|
78
|
+
case @command_pipe.gets.chomp
|
79
|
+
when "GRACEFUL_STOP"
|
80
|
+
s.stop(true)
|
81
|
+
when "IMMEDIATE_STOP"
|
82
|
+
s.stop(false)
|
83
|
+
when "GRACEFUL_RESTART"
|
84
|
+
s.restart(true)
|
85
|
+
when "IMMEDIATE_RESTART"
|
86
|
+
s.restart(false)
|
87
|
+
when "RELOAD"
|
88
|
+
s.reload
|
89
|
+
when "DETACH"
|
90
|
+
s.detach(true)
|
91
|
+
when "DUMP"
|
92
|
+
Sigdump.dump
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
else
|
97
|
+
SignalThread.new do |st|
|
98
|
+
st.trap(@config[:signal_graceful_stop] || Signals::GRACEFUL_STOP) { s.stop(true) }
|
99
|
+
st.trap(@config[:signal_detach] || Signals::DETACH) { s.stop(true) }
|
100
|
+
# Here disables signals excepting GRACEFUL_STOP == :SIGTERM because
|
101
|
+
# only SIGTERM is available on all version of Windows.
|
102
|
+
unless ServerEngine.windows?
|
103
|
+
st.trap(@config[:signal_immediate_stop] || Signals::IMMEDIATE_STOP) { s.stop(false) }
|
104
|
+
st.trap(@config[:signal_graceful_restart] || Signals::GRACEFUL_RESTART) { s.restart(true) }
|
105
|
+
st.trap(@config[:signal_immediate_restart] || Signals::IMMEDIATE_RESTART) { s.restart(false) }
|
106
|
+
st.trap(@config[:signal_reload] || Signals::RELOAD) { s.reload }
|
107
|
+
st.trap(@config[:signal_dump] || Signals::DUMP) { Sigdump.dump }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def main
|
114
|
+
create_logger unless @logger
|
115
|
+
|
116
|
+
# start threads to transfer logs from STDOUT/ERR to the logger
|
117
|
+
start_io_logging_thread(STDOUT) if @log_stdout && try_get_io_from_logger(@logger) != STDOUT
|
118
|
+
start_io_logging_thread(STDERR) if @log_stderr && try_get_io_from_logger(@logger) != STDERR
|
119
|
+
|
120
|
+
before_run
|
121
|
+
|
122
|
+
begin
|
123
|
+
run
|
124
|
+
ensure
|
125
|
+
after_run
|
126
|
+
end
|
127
|
+
if @stop_status
|
128
|
+
raise SystemExit.new(@stop_status)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
module WorkerInitializer
|
133
|
+
def initialize
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
# If :logger option is set unexpectedly, reading from STDOUT/ERR
|
140
|
+
# and writing to :logger could cause infinite loop because
|
141
|
+
# :logger may write data to STDOUT/ERR.
|
142
|
+
def try_get_io_from_logger(logger)
|
143
|
+
logdev = logger.instance_eval { @logdev }
|
144
|
+
if logdev.respond_to?(:dev)
|
145
|
+
# ::Logger
|
146
|
+
logdev.dev
|
147
|
+
else
|
148
|
+
# logdev is IO if DaemonLogger. otherwise unknown object including nil
|
149
|
+
logdev
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def create_worker(wid)
|
154
|
+
w = Worker.new(self, wid)
|
155
|
+
w.extend(WorkerInitializer)
|
156
|
+
w.extend(@worker_module)
|
157
|
+
w.instance_eval { initialize }
|
158
|
+
w
|
159
|
+
end
|
160
|
+
|
161
|
+
def start_io_logging_thread(io)
|
162
|
+
r, w = IO.pipe
|
163
|
+
io.reopen(w)
|
164
|
+
w.close
|
165
|
+
|
166
|
+
Thread.new do
|
167
|
+
begin
|
168
|
+
while line = r.gets
|
169
|
+
@logger << line
|
170
|
+
end
|
171
|
+
rescue => e
|
172
|
+
ServerEngine.dump_uncaught_error(e)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
@@ -0,0 +1,116 @@
|
|
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
|
+
module ServerEngine
|
19
|
+
|
20
|
+
class SignalThread < Thread
|
21
|
+
def initialize(&block)
|
22
|
+
@handlers = {}
|
23
|
+
|
24
|
+
@mutex = Mutex.new
|
25
|
+
@cond = ConditionVariable.new
|
26
|
+
@queue = []
|
27
|
+
@finished = false
|
28
|
+
|
29
|
+
block.call(self) if block
|
30
|
+
|
31
|
+
super(&method(:main))
|
32
|
+
end
|
33
|
+
|
34
|
+
def trap(sig, command=nil, &block)
|
35
|
+
# normalize signal names
|
36
|
+
sig = sig.to_s.upcase
|
37
|
+
if sig[0,3] == "SIG"
|
38
|
+
sig = sig[3..-1]
|
39
|
+
end
|
40
|
+
sig = sig.to_sym
|
41
|
+
|
42
|
+
old = @handlers[sig]
|
43
|
+
if block
|
44
|
+
Kernel.trap(sig) { signal_handler_main(sig) }
|
45
|
+
@handlers[sig] = block
|
46
|
+
else
|
47
|
+
Kernel.trap(sig, command)
|
48
|
+
@handlers.delete(sig)
|
49
|
+
end
|
50
|
+
|
51
|
+
old
|
52
|
+
end
|
53
|
+
|
54
|
+
def handlers
|
55
|
+
@handlers.dup
|
56
|
+
end
|
57
|
+
|
58
|
+
def stop
|
59
|
+
@mutex.synchronize do
|
60
|
+
@finished = true
|
61
|
+
@cond.broadcast
|
62
|
+
end
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def signal_handler_main(sig)
|
69
|
+
# here always creates new thread to avoid
|
70
|
+
# complicated race conditin in signal handlers
|
71
|
+
Thread.new do
|
72
|
+
begin
|
73
|
+
enqueue(sig)
|
74
|
+
rescue => e
|
75
|
+
ServerEngine.dump_uncaught_error(e)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def main
|
81
|
+
until @finished
|
82
|
+
sig = nil
|
83
|
+
|
84
|
+
@mutex.synchronize do
|
85
|
+
while true
|
86
|
+
return if @finished
|
87
|
+
|
88
|
+
sig = @queue.shift
|
89
|
+
break if sig
|
90
|
+
|
91
|
+
@cond.wait(@mutex, 1)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
begin
|
96
|
+
@handlers[sig].call(sig)
|
97
|
+
rescue => e
|
98
|
+
ServerEngine.dump_uncaught_error(e)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
nil
|
103
|
+
|
104
|
+
ensure
|
105
|
+
@finished = false
|
106
|
+
end
|
107
|
+
|
108
|
+
def enqueue(sig)
|
109
|
+
@mutex.synchronize do
|
110
|
+
@queue << sig
|
111
|
+
@cond.broadcast
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,31 @@
|
|
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
|
+
|
19
|
+
require 'serverengine/utils'
|
20
|
+
|
21
|
+
module ServerEngine
|
22
|
+
module Signals
|
23
|
+
GRACEFUL_STOP = :TERM
|
24
|
+
IMMEDIATE_STOP = ServerEngine::windows? ? :KILL : :QUIT
|
25
|
+
GRACEFUL_RESTART = :USR1
|
26
|
+
IMMEDIATE_RESTART = :HUP
|
27
|
+
RELOAD = :USR2
|
28
|
+
DETACH = :INT
|
29
|
+
DUMP = :CONT
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,171 @@
|
|
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 'socket'
|
19
|
+
require 'ipaddr'
|
20
|
+
|
21
|
+
module ServerEngine
|
22
|
+
module SocketManager
|
23
|
+
|
24
|
+
class Client
|
25
|
+
def initialize(path)
|
26
|
+
@path = path
|
27
|
+
end
|
28
|
+
|
29
|
+
def listen_tcp(bind, port)
|
30
|
+
peer = connect_peer(@path)
|
31
|
+
begin
|
32
|
+
SocketManager.send_peer(peer, [Process.pid, :listen_tcp, bind, port])
|
33
|
+
res = SocketManager.recv_peer(peer)
|
34
|
+
if res.is_a?(Exception)
|
35
|
+
raise res
|
36
|
+
else
|
37
|
+
return recv_tcp(peer, res)
|
38
|
+
end
|
39
|
+
ensure
|
40
|
+
peer.close
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def listen_udp(bind, port)
|
45
|
+
peer = connect_peer(@path)
|
46
|
+
begin
|
47
|
+
SocketManager.send_peer(peer, [Process.pid, :listen_udp, bind, port])
|
48
|
+
res = SocketManager.recv_peer(peer)
|
49
|
+
if res.is_a?(Exception)
|
50
|
+
raise res
|
51
|
+
else
|
52
|
+
return recv_udp(peer, res)
|
53
|
+
end
|
54
|
+
ensure
|
55
|
+
peer.close
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Server
|
61
|
+
def self.generate_path
|
62
|
+
if ServerEngine.windows?
|
63
|
+
for port in 10000..65535
|
64
|
+
if `netstat -na | find "#{port}"`.length == 0
|
65
|
+
return port
|
66
|
+
end
|
67
|
+
end
|
68
|
+
else
|
69
|
+
'/tmp/SERVERENGINE_SOCKETMANAGER_' + Time.now.to_s.gsub(' ', '') + '_' + Process.pid.to_s
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.open(path)
|
74
|
+
new(path)
|
75
|
+
end
|
76
|
+
|
77
|
+
def initialize(path)
|
78
|
+
@tcp_sockets = {}
|
79
|
+
@udp_sockets = {}
|
80
|
+
@mutex = Mutex.new
|
81
|
+
@path = start_server(path)
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_reader :path
|
85
|
+
|
86
|
+
def new_client
|
87
|
+
Client.new(@path)
|
88
|
+
end
|
89
|
+
|
90
|
+
def close
|
91
|
+
stop_server
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def listen_tcp(bind, port)
|
98
|
+
key, bind_ip = resolve_bind_key(bind, port)
|
99
|
+
|
100
|
+
@mutex.synchronize do
|
101
|
+
if @tcp_sockets.has_key?(key)
|
102
|
+
return @tcp_sockets[key]
|
103
|
+
else
|
104
|
+
return @tcp_sockets[key] = listen_tcp_new(bind_ip, port)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def listen_udp(bind, port)
|
110
|
+
key, bind_ip = resolve_bind_key(bind, port)
|
111
|
+
|
112
|
+
@mutex.synchronize do
|
113
|
+
if @udp_sockets.has_key?(key)
|
114
|
+
return @udp_sockets[key]
|
115
|
+
else
|
116
|
+
return @udp_sockets[key] = listen_udp_new(bind_ip, port)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def resolve_bind_key(bind, port)
|
122
|
+
bind_ip = IPAddr.new(IPSocket.getaddress(bind))
|
123
|
+
if bind_ip.ipv6?
|
124
|
+
return "[#{bind_ip}]:#{port}", bind_ip
|
125
|
+
else
|
126
|
+
# assuming ipv4
|
127
|
+
if bind_ip == "127.0.0.1" or bind_ip == "0.0.0.0"
|
128
|
+
return "localhost:#{port}", bind_ip
|
129
|
+
end
|
130
|
+
return "#{bind_ip}:#{port}", bind_ip
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def process_peer(peer)
|
135
|
+
while true
|
136
|
+
pid, method, bind, port = *SocketManager.recv_peer(peer)
|
137
|
+
begin
|
138
|
+
send_socket(peer, pid, method, bind, port)
|
139
|
+
rescue => e
|
140
|
+
SocketManager.send_peer(peer, e)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
ensure
|
144
|
+
peer.close
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def self.send_peer(peer, obj)
|
149
|
+
data = Marshal.dump(obj)
|
150
|
+
peer.write [data.bytesize].pack('N')
|
151
|
+
peer.write data
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.recv_peer(peer)
|
155
|
+
len = peer.read(4).unpack('N').first
|
156
|
+
data = peer.read(len)
|
157
|
+
Marshal.load(data)
|
158
|
+
end
|
159
|
+
|
160
|
+
if ServerEngine.windows?
|
161
|
+
require_relative 'socket_manager_win'
|
162
|
+
Client.include(SocketManagerWin::ClientModule)
|
163
|
+
Server.include(SocketManagerWin::ServerModule)
|
164
|
+
else
|
165
|
+
require_relative 'socket_manager_unix'
|
166
|
+
Client.include(SocketManagerUnix::ClientModule)
|
167
|
+
Server.include(SocketManagerUnix::ServerModule)
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|