serverengine 2.0.0pre1-x86-mingw32
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.
- 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
|