serverengine 1.5.11 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +3 -3
- data/Changelog +9 -0
- data/README.md +108 -51
- data/appveyor.yml +18 -0
- data/lib/serverengine/config_loader.rb +2 -3
- data/lib/serverengine/daemon.rb +4 -1
- data/lib/serverengine/multi_spawn_server.rb +15 -6
- data/lib/serverengine/process_manager.rb +51 -29
- data/lib/serverengine/server.rb +9 -5
- data/lib/serverengine/socket_manager.rb +170 -0
- data/lib/serverengine/socket_manager_unix.rb +96 -0
- data/lib/serverengine/socket_manager_win.rb +147 -0
- data/lib/serverengine/supervisor.rb +4 -1
- data/lib/serverengine/utils.rb +7 -0
- data/lib/serverengine/version.rb +1 -1
- data/lib/serverengine/winsock.rb +128 -0
- data/lib/serverengine.rb +1 -0
- data/spec/signal_thread_spec.rb +10 -5
- data/spec/socket_manager_spec.rb +115 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/supervisor_spec.rb +57 -0
- metadata +10 -3
data/lib/serverengine/server.rb
CHANGED
@@ -66,12 +66,16 @@ module ServerEngine
|
|
66
66
|
s = self
|
67
67
|
SignalThread.new do |st|
|
68
68
|
st.trap(Daemon::Signals::GRACEFUL_STOP) { s.stop(true) }
|
69
|
-
st.trap(Daemon::Signals::IMMEDIATE_STOP) { s.stop(false) }
|
70
|
-
st.trap(Daemon::Signals::GRACEFUL_RESTART) { s.restart(true) }
|
71
|
-
st.trap(Daemon::Signals::IMMEDIATE_RESTART) { s.restart(false) }
|
72
|
-
st.trap(Daemon::Signals::RELOAD) { s.reload }
|
73
69
|
st.trap(Daemon::Signals::DETACH) { s.stop(true) }
|
74
|
-
|
70
|
+
# Here disables signals excepting GRACEFUL_STOP == :SIGTERM because
|
71
|
+
# only SIGTERM is available on all version of Windows.
|
72
|
+
unless ServerEngine.windows?
|
73
|
+
st.trap(Daemon::Signals::IMMEDIATE_STOP) { s.stop(false) }
|
74
|
+
st.trap(Daemon::Signals::GRACEFUL_RESTART) { s.restart(true) }
|
75
|
+
st.trap(Daemon::Signals::IMMEDIATE_RESTART) { s.restart(false) }
|
76
|
+
st.trap(Daemon::Signals::RELOAD) { s.reload }
|
77
|
+
st.trap(Daemon::Signals::DUMP) { Sigdump.dump }
|
78
|
+
end
|
75
79
|
end
|
76
80
|
end
|
77
81
|
|
@@ -0,0 +1,170 @@
|
|
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
|
+
module SocketManager
|
20
|
+
|
21
|
+
require 'socket'
|
22
|
+
require 'ipaddr'
|
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
|
+
return "#{bind_ip}:#{port}", bind_ip
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def process_peer(peer)
|
132
|
+
while true
|
133
|
+
pid, method, bind, port = *SocketManager.recv_peer(peer)
|
134
|
+
begin
|
135
|
+
send_socket(peer, pid, method, bind, port)
|
136
|
+
rescue => e
|
137
|
+
SocketManager.send_peer(peer, e)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
ensure
|
141
|
+
peer.close
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.send_peer(peer, obj)
|
146
|
+
data = Marshal.dump(obj)
|
147
|
+
peer.write [data.bytesize].pack('N')
|
148
|
+
peer.write data
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.recv_peer(peer)
|
152
|
+
len = peer.read(4).unpack('N').first
|
153
|
+
data = peer.read(len)
|
154
|
+
Marshal.load(data)
|
155
|
+
end
|
156
|
+
|
157
|
+
require_relative 'utils'
|
158
|
+
|
159
|
+
if ServerEngine.windows?
|
160
|
+
require_relative 'socket_manager_win'
|
161
|
+
Client.include(SocketManagerWin::ClientModule)
|
162
|
+
Server.include(SocketManagerWin::ServerModule)
|
163
|
+
else
|
164
|
+
require_relative 'socket_manager_unix'
|
165
|
+
Client.include(SocketManagerUnix::ClientModule)
|
166
|
+
Server.include(SocketManagerUnix::ServerModule)
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,96 @@
|
|
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
|
+
|
19
|
+
module ServerEngine
|
20
|
+
module SocketManagerUnix
|
21
|
+
|
22
|
+
module ClientModule
|
23
|
+
private
|
24
|
+
|
25
|
+
def connect_peer(path)
|
26
|
+
return UNIXSocket.new(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def recv_tcp(peer, sent)
|
30
|
+
return peer.recv_io(TCPServer)
|
31
|
+
end
|
32
|
+
|
33
|
+
def recv_udp(peer, sent)
|
34
|
+
return peer.recv_io(UDPSocket)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module ServerModule
|
39
|
+
private
|
40
|
+
|
41
|
+
def listen_tcp_new(bind_ip, port)
|
42
|
+
sock = TCPServer.new(bind_ip.to_s, port)
|
43
|
+
sock.listen(Socket::SOMAXCONN) # TODO make backlog configurable if necessary
|
44
|
+
return sock
|
45
|
+
end
|
46
|
+
|
47
|
+
def listen_udp_new(bind_ip, port)
|
48
|
+
if bind_ip.ipv6?
|
49
|
+
sock = UDPSocket.new(Socket::AF_INET6)
|
50
|
+
else
|
51
|
+
sock = UDPSocket.new
|
52
|
+
end
|
53
|
+
sock.bind(bind_ip.to_s, port)
|
54
|
+
return sock
|
55
|
+
end
|
56
|
+
|
57
|
+
def start_server(path)
|
58
|
+
# return absolute path so that client can connect to this path
|
59
|
+
# when client changed working directory
|
60
|
+
path = File.expand_path(path)
|
61
|
+
|
62
|
+
@server = UNIXServer.new(path)
|
63
|
+
|
64
|
+
@thread = Thread.new do
|
65
|
+
begin
|
66
|
+
while peer = @server.accept
|
67
|
+
Thread.new(peer, &method(:process_peer)) # process_peer calls send_socket
|
68
|
+
end
|
69
|
+
rescue => e
|
70
|
+
unless @server.closed?
|
71
|
+
ServerEngine.dump_uncaught_error(e)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
return path
|
77
|
+
end
|
78
|
+
|
79
|
+
def stop_server
|
80
|
+
@tcp_sockets.reject! {|key,lsock| lsock.close; true }
|
81
|
+
@udp_sockets.reject! {|key,usock| usock.close; true }
|
82
|
+
@server.close unless @server.closed?
|
83
|
+
@thread.join
|
84
|
+
end
|
85
|
+
|
86
|
+
def send_socket(peer, pid, method, bind, port)
|
87
|
+
sock = send(method, bind, port) # calls listen_tcp or listen_udp
|
88
|
+
|
89
|
+
SocketManager.send_peer(peer, nil)
|
90
|
+
|
91
|
+
peer.send_io sock
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,147 @@
|
|
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
|
+
|
19
|
+
module ServerEngine
|
20
|
+
module SocketManagerWin
|
21
|
+
|
22
|
+
require_relative 'winsock'
|
23
|
+
|
24
|
+
module ClientModule
|
25
|
+
private
|
26
|
+
|
27
|
+
def connect_peer(addr)
|
28
|
+
return TCPSocket.open("127.0.0.1", addr)
|
29
|
+
end
|
30
|
+
|
31
|
+
def recv_tcp(peer, sent)
|
32
|
+
proto = WinSock::WSAPROTOCOL_INFO.from_bin(sent)
|
33
|
+
|
34
|
+
handle = WinSock.WSASocketA(Socket::AF_INET, Socket::SOCK_STREAM, 0, proto, 0, 1)
|
35
|
+
if handle == WinSock::INVALID_SOCKET
|
36
|
+
RbWinSock.raise_last_error("WSASocketA(2)")
|
37
|
+
end
|
38
|
+
|
39
|
+
return RbWinSock.wrap_io_handle(TCPServer, handle, 0)
|
40
|
+
end
|
41
|
+
|
42
|
+
def recv_udp(peer, sent)
|
43
|
+
proto = WinSock::WSAPROTOCOL_INFO.from_bin(sent)
|
44
|
+
|
45
|
+
handle = WinSock.WSASocketA(Socket::AF_INET, Socket::SOCK_DGRAM, 0, proto, 0, 1)
|
46
|
+
if handle == WinSock::INVALID_SOCKET
|
47
|
+
RbWinSock.raise_last_error("WSASocketA(2)")
|
48
|
+
end
|
49
|
+
|
50
|
+
return RbWinSock.wrap_io_handle(UDPSocket, handle, 0)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module ServerModule
|
55
|
+
private
|
56
|
+
|
57
|
+
def listen_tcp_new(bind_ip, port)
|
58
|
+
sock_addr = Socket.pack_sockaddr_in(port, bind_ip.to_s)
|
59
|
+
|
60
|
+
handle = WinSock.WSASocketA(Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP, nil, 0, 1)
|
61
|
+
if handle == WinSock::INVALID_SOCKET
|
62
|
+
RbWinSock.raise_last_error("WSASocketA(2)")
|
63
|
+
end
|
64
|
+
|
65
|
+
# wrap in TCPServer immediately so that its finalizer safely closes the handle
|
66
|
+
sock = RbWinSock.wrap_io_handle(TCPServer, handle, 0)
|
67
|
+
|
68
|
+
unless WinSock.bind(sock.handle, sock_addr, sock_addr.bytesize) == 0
|
69
|
+
RbWinSock.raise_last_error("bind(2)")
|
70
|
+
end
|
71
|
+
unless WinSock.listen(sock.handle, Socket::SOMAXCONN) == 0
|
72
|
+
RbWinSock.raise_last_error("listen(2)")
|
73
|
+
end
|
74
|
+
|
75
|
+
return sock
|
76
|
+
end
|
77
|
+
|
78
|
+
def listen_udp_new(bind_ip, port)
|
79
|
+
sock_addr = Socket.pack_sockaddr_in(port, bind_ip.to_s)
|
80
|
+
|
81
|
+
handle = WinSock.WSASocketA(Socket::AF_INET, Socket::SOCK_DGRAM, Socket::IPPROTO_UDP, nil, 0, 1)
|
82
|
+
if handle == WinSock::INVALID_SOCKET
|
83
|
+
RbWinSock.raise_last_error("WSASocketA(2)")
|
84
|
+
end
|
85
|
+
|
86
|
+
# wrap in UDPSocket immediately so that its finalizer safely closes the handle
|
87
|
+
sock = RbWinSock.wrap_io_handle(UDPSocket, handle, 0)
|
88
|
+
|
89
|
+
unless WinSock.bind(sock.handle, sock_addr, sock_addr.bytesize) == 0
|
90
|
+
RbWinSock.raise_last_error("bind(2)")
|
91
|
+
end
|
92
|
+
|
93
|
+
return sock
|
94
|
+
end
|
95
|
+
|
96
|
+
def htons(h)
|
97
|
+
[h].pack("S").unpack("n")[0]
|
98
|
+
end
|
99
|
+
|
100
|
+
def start_server(addr)
|
101
|
+
# TODO: use TCPServer, but this is risky because using not conflict path is easy,
|
102
|
+
# but using not conflict port is difficult. Then We had better implement using NamedPipe.
|
103
|
+
@server = TCPServer.new("127.0.0.1", addr)
|
104
|
+
@thread = Thread.new do
|
105
|
+
begin
|
106
|
+
while peer = @server.accept
|
107
|
+
Thread.new(peer, &method(:process_peer)) # process_peer calls send_socket
|
108
|
+
end
|
109
|
+
rescue => e
|
110
|
+
unless @server.closed?
|
111
|
+
ServerEngine.dump_uncaught_error(e)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
return path
|
117
|
+
end
|
118
|
+
|
119
|
+
def stop_server
|
120
|
+
@tcp_sockets.reject! {|key,lsock| lsock.close; true }
|
121
|
+
@udp_sockets.reject! {|key,usock| usock.close; true }
|
122
|
+
@server.close unless @server.closed?
|
123
|
+
@thread.join
|
124
|
+
end
|
125
|
+
|
126
|
+
def send_socket(peer, pid, method, bind, port)
|
127
|
+
case method
|
128
|
+
when :listen_tcp
|
129
|
+
sock = listen_tcp(bind, port)
|
130
|
+
type = Socket::SOCK_STREAM
|
131
|
+
when :listen_udp
|
132
|
+
sock = listen_udp(bind, port)
|
133
|
+
type = Socket::SOCK_DGRAM
|
134
|
+
else
|
135
|
+
raise ArgumentError, "Unknown method: #{method.inspect}"
|
136
|
+
end
|
137
|
+
|
138
|
+
proto = WinSock::WSAPROTOCOL_INFO.malloc
|
139
|
+
unless WinSock.WSADuplicateSocketA(sock.handle, pid, proto) == 0
|
140
|
+
RbWinSock.raise_last_error("WSADuplicateSocketA(3)")
|
141
|
+
end
|
142
|
+
|
143
|
+
SocketManager.send_peer(peer, proto.to_bin)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -46,6 +46,9 @@ module ServerEngine
|
|
46
46
|
@disable_reload = !!@config[:disable_reload]
|
47
47
|
end
|
48
48
|
|
49
|
+
# server is available after start_server() call.
|
50
|
+
attr_reader :server
|
51
|
+
|
49
52
|
def reload_config
|
50
53
|
super
|
51
54
|
|
@@ -89,7 +92,7 @@ module ServerEngine
|
|
89
92
|
end
|
90
93
|
|
91
94
|
def create_server(logger)
|
92
|
-
@create_server_proc.call(@load_config_proc, logger)
|
95
|
+
@server = @create_server_proc.call(@load_config_proc, logger)
|
93
96
|
end
|
94
97
|
|
95
98
|
def stop(stop_graceful)
|