serverengine 1.5.11 → 1.6.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.
- 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)
|