zmachine 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +6 -14
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -2
- data/README.md +3 -1
- data/Rakefile +1 -0
- data/benchmarks/benchmark.sh +12 -0
- data/benchmarks/tcp_channel.rb +26 -0
- data/benchmarks/zmq_channel.rb +23 -0
- data/echo_client.rb +10 -28
- data/echo_server.rb +15 -22
- data/lib/zmachine.rb +71 -23
- data/lib/zmachine/channel.rb +29 -27
- data/lib/zmachine/connection.rb +178 -51
- data/lib/zmachine/connection_manager.rb +133 -0
- data/lib/zmachine/hashed_wheel.rb +23 -31
- data/lib/zmachine/reactor.rb +66 -393
- data/lib/zmachine/tcp_channel.rb +47 -109
- data/lib/zmachine/zmq_channel.rb +74 -115
- data/spec/connection_manager_spec.rb +16 -0
- data/spec/connection_spec.rb +56 -0
- data/spec/hashed_wheel_spec.rb +15 -12
- data/spec/spec_helper.rb +22 -14
- data/spec/tcp_channel_spec.rb +109 -0
- data/spec/zmq_channel_spec.rb +113 -0
- data/zmachine.gemspec +4 -2
- metadata +54 -26
data/lib/zmachine/tcp_channel.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
java_import java.io.IOException
|
2
|
+
java_import java.net.InetSocketAddress
|
3
|
+
java_import java.nio.channels.SocketChannel
|
1
4
|
java_import java.nio.channels.ServerSocketChannel
|
2
5
|
|
3
6
|
require 'zmachine/channel'
|
@@ -5,93 +8,81 @@ require 'zmachine/channel'
|
|
5
8
|
module ZMachine
|
6
9
|
class TCPChannel < Channel
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
def initialize(selector)
|
11
|
-
super(selector)
|
12
|
-
@close_scheduled = false
|
13
|
-
@connect_pending = false
|
14
|
-
@server_socket = false
|
15
|
-
end
|
16
|
-
|
17
|
-
def register
|
18
|
-
@channel_key ||= @socket.register(@selector, current_events, self)
|
11
|
+
def selectable_fd
|
12
|
+
@socket
|
19
13
|
end
|
20
14
|
|
21
15
|
def bind(address, port)
|
22
|
-
@server_socket = true
|
23
16
|
address = InetSocketAddress.new(address, port)
|
24
17
|
@socket = ServerSocketChannel.open
|
25
18
|
@socket.configure_blocking(false)
|
26
19
|
@socket.bind(address)
|
27
20
|
end
|
28
21
|
|
22
|
+
def bound?
|
23
|
+
@socket.is_a?(ServerSocketChannel) and @socket.bound?
|
24
|
+
end
|
25
|
+
|
29
26
|
def accept
|
30
|
-
client_socket = socket.accept
|
27
|
+
client_socket = @socket.accept
|
31
28
|
return unless client_socket
|
32
29
|
client_socket.configure_blocking(false)
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
TCPChannel.new.tap do |channel|
|
31
|
+
channel.socket = client_socket
|
32
|
+
end
|
36
33
|
end
|
37
34
|
|
38
35
|
def connect(address, port)
|
39
36
|
address = InetSocketAddress.new(address, port)
|
40
37
|
@socket = SocketChannel.open
|
41
38
|
@socket.configure_blocking(false)
|
42
|
-
|
43
39
|
if socket.connect(address)
|
44
40
|
# Connection returned immediately. Can happen with localhost
|
45
41
|
# connections.
|
46
42
|
# WARNING, this code is untested due to lack of available test
|
47
43
|
# conditions. Ought to be be able to come here from a localhost
|
48
44
|
# connection, but that doesn't happen on Linux. (Maybe on FreeBSD?)
|
49
|
-
# The reason for not handling this until we can test it is that we
|
50
|
-
# really need to return from this function WITHOUT triggering any EM
|
51
|
-
# events. That's because until the user code has seen the signature
|
52
|
-
# we generated here, it won't be able to properly dispatch them. The
|
53
|
-
# C++ EM deals with this by setting pending mode as a flag in ALL
|
54
|
-
# eventable descriptors and making the descriptor select for
|
55
|
-
# writable. Then, it can send UNBOUND and CONNECTION_COMPLETED on the
|
56
|
-
# next pass through the loop, because writable will fire.
|
57
45
|
raise RuntimeError.new("immediate-connect unimplemented")
|
58
46
|
end
|
59
47
|
end
|
60
48
|
|
61
|
-
def
|
62
|
-
@
|
49
|
+
def connection_pending?
|
50
|
+
@socket.connection_pending?
|
63
51
|
end
|
64
52
|
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
@socket.close rescue nil
|
53
|
+
def finish_connecting
|
54
|
+
return unless connection_pending?
|
55
|
+
@socket.finish_connect # XXX: finish_connect might return false
|
56
|
+
return true
|
72
57
|
end
|
73
58
|
|
74
|
-
def
|
75
|
-
|
76
|
-
buffer = ByteBuffer.wrap(data.to_java_bytes)
|
77
|
-
if buffer.remaining() > 0
|
78
|
-
@outbound_queue << buffer
|
79
|
-
update_events
|
80
|
-
end
|
59
|
+
def connected?
|
60
|
+
@socket.connected?
|
81
61
|
end
|
82
62
|
|
83
|
-
def read_inbound_data
|
63
|
+
def read_inbound_data
|
64
|
+
buffer = @inbound_buffer
|
84
65
|
buffer.clear
|
85
|
-
raise IOException.new("
|
66
|
+
raise IOException.new("EOF") if @socket.read(buffer) == -1
|
86
67
|
buffer.flip
|
87
68
|
return if buffer.limit == 0
|
88
69
|
String.from_java_bytes(buffer.array[buffer.position...buffer.limit])
|
89
70
|
end
|
90
71
|
|
72
|
+
def send_data(data)
|
73
|
+
raise RuntimeError.new("send_data called after close") if @close_scheduled
|
74
|
+
return unless data
|
75
|
+
data = data.to_java_bytes if data.is_a?(String) # EM compat
|
76
|
+
buffer = ByteBuffer.wrap(data)
|
77
|
+
if buffer.has_remaining
|
78
|
+
@outbound_queue << buffer
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
91
82
|
def write_outbound_data
|
92
|
-
|
83
|
+
while can_send?
|
93
84
|
buffer = @outbound_queue.first
|
94
|
-
@socket.write(buffer) if buffer.
|
85
|
+
@socket.write(buffer) if buffer.has_remaining
|
95
86
|
# Did we consume the whole outbound buffer? If yes,
|
96
87
|
# pop it off and keep looping. If no, the outbound network
|
97
88
|
# buffers are full, so break out of here.
|
@@ -102,80 +93,27 @@ module ZMachine
|
|
102
93
|
end
|
103
94
|
end
|
104
95
|
|
105
|
-
if
|
106
|
-
|
107
|
-
end
|
108
|
-
|
109
|
-
return (@close_scheduled && @outbound_queue.empty?) ? false : true
|
110
|
-
end
|
111
|
-
|
112
|
-
def finish_connecting
|
113
|
-
@socket.finish_connect
|
114
|
-
@connect_pending = false
|
115
|
-
update_events
|
116
|
-
return true
|
117
|
-
end
|
118
|
-
|
119
|
-
def schedule_close(after_writing)
|
120
|
-
@outbound_queue.clear unless after_writing
|
121
|
-
|
122
|
-
if @outbound_queue.empty?
|
123
|
-
return true
|
124
|
-
else
|
125
|
-
update_events
|
126
|
-
@close_scheduled = true
|
96
|
+
if can_send?
|
97
|
+
# network buffers are full
|
127
98
|
return false
|
128
99
|
end
|
129
|
-
end
|
130
|
-
|
131
|
-
# TODO: fix these
|
132
|
-
def peer_name
|
133
|
-
sock = @socket.socket
|
134
|
-
[sock.port, sock.inet_address.host_address]
|
135
|
-
end
|
136
100
|
|
137
|
-
|
138
|
-
|
139
|
-
[sock.local_port, sock.local_address.host_address]
|
101
|
+
close if @close_scheduled
|
102
|
+
return true
|
140
103
|
end
|
141
104
|
|
142
|
-
def
|
143
|
-
|
144
|
-
|
105
|
+
def close(after_writing = false)
|
106
|
+
super
|
107
|
+
@socket.close unless can_send?
|
145
108
|
end
|
146
109
|
|
147
|
-
def
|
148
|
-
|
149
|
-
events = current_events
|
150
|
-
if @channel_key.interest_ops != events
|
151
|
-
@channel_key.interest_ops(events)
|
152
|
-
end
|
110
|
+
def closed?
|
111
|
+
@socket.socket.closed?
|
153
112
|
end
|
154
113
|
|
155
|
-
|
156
|
-
|
157
|
-
false
|
114
|
+
def peer
|
115
|
+
[@socket.socket.port, @socket.socket.inet_address.host_address]
|
158
116
|
end
|
159
117
|
|
160
|
-
def can_send?
|
161
|
-
false
|
162
|
-
end
|
163
|
-
|
164
|
-
def current_events
|
165
|
-
if @socket.is_a?(ServerSocketChannel)
|
166
|
-
return SelectionKey::OP_ACCEPT
|
167
|
-
end
|
168
|
-
|
169
|
-
events = 0
|
170
|
-
|
171
|
-
if @connect_pending
|
172
|
-
events |= SelectionKey::OP_CONNECT
|
173
|
-
else
|
174
|
-
events |= SelectionKey::OP_READ
|
175
|
-
events |= SelectionKey::OP_WRITE unless @outbound_queue.empty?
|
176
|
-
end
|
177
|
-
|
178
|
-
return events
|
179
|
-
end
|
180
118
|
end
|
181
119
|
end
|
data/lib/zmachine/zmq_channel.rb
CHANGED
@@ -1,144 +1,96 @@
|
|
1
|
+
require 'zmachine/jeromq-0.3.0-SNAPSHOT.jar'
|
1
2
|
java_import org.zeromq.ZMQ
|
2
|
-
java_import org.zeromq.ZMsg
|
3
3
|
java_import org.zeromq.ZMQException
|
4
4
|
|
5
|
+
require 'zmachine'
|
5
6
|
require 'zmachine/channel'
|
6
7
|
|
7
|
-
class ZMsg
|
8
|
-
# for performance reason we alias the method here (otherwise it uses reflections all the time!)
|
9
|
-
java_alias :post, :send, [org.zeromq.ZMQ::Socket, Java::boolean]
|
10
|
-
end
|
11
|
-
|
12
|
-
# this needs to be moved to a seperate file
|
13
8
|
class ZMQ
|
14
9
|
class Socket
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
socket.setRcvHWM(opts[:rcvhwm]) if opts[:rcvhwm]
|
20
|
-
socket.set_router_mandatory(true) if type == ZMQ::ROUTER
|
21
|
-
socket.connect(opts[:connect]) if opts[:connect]
|
22
|
-
socket.bind(opts[:bind]) if opts[:bind]
|
23
|
-
socket
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.pair(opts = {})
|
27
|
-
create_socket_with_opts(ZMQ::PAIR, opts)
|
28
|
-
end
|
29
|
-
def self.router(opts = {})
|
30
|
-
create_socket_with_opts(ZMQ::ROUTER, opts)
|
31
|
-
end
|
32
|
-
def self.pull(opts = {})
|
33
|
-
create_socket_with_opts(ZMQ::PULL, opts)
|
34
|
-
end
|
35
|
-
def self.push(opts = {})
|
36
|
-
create_socket_with_opts(ZMQ::PUSH, opts)
|
37
|
-
end
|
38
|
-
def self.dealer(opts = {})
|
39
|
-
create_socket_with_opts(ZMQ::DEALER, opts)
|
40
|
-
end
|
41
|
-
def self.pub(opts = {})
|
42
|
-
create_socket_with_opts(ZMQ::PUB, opts)
|
43
|
-
end
|
44
|
-
|
45
|
-
def send2(a,b)
|
46
|
-
sent = send a, ZMQ::SNDMORE | ZMQ::DONTWAIT
|
47
|
-
sent &= send b, ZMQ::DONTWAIT
|
48
|
-
sent
|
49
|
-
end
|
50
|
-
|
51
|
-
def send3(a,b,c)
|
52
|
-
sent = send a, ZMQ::SNDMORE | ZMQ::DONTWAIT
|
53
|
-
sent &= send b, ZMQ::SNDMORE | ZMQ::DONTWAIT
|
54
|
-
sent &= send c, ZMQ::DONTWAIT
|
55
|
-
sent
|
56
|
-
end
|
10
|
+
# for performance reason we alias the method here (otherwise it uses reflections all the time!)
|
11
|
+
# super ugly, since we need to dynamically infer the java class of byte[]
|
12
|
+
java_alias :send_byte_array, :send, [[].to_java(:byte).java_class, Java::int]
|
13
|
+
java_alias :recv_byte_array, :recv, [Java::int]
|
57
14
|
end
|
58
15
|
end
|
59
16
|
|
60
17
|
module ZMachine
|
61
18
|
class ZMQChannel < Channel
|
62
19
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
@
|
20
|
+
def initialize(type)
|
21
|
+
super()
|
22
|
+
@socket = ZMachine.context.create_socket(type)
|
23
|
+
@bound = false
|
24
|
+
@connected = false
|
25
|
+
@closed = false
|
69
26
|
end
|
70
27
|
|
71
|
-
def
|
72
|
-
socket
|
73
|
-
socket.setLinger(opts[:linger]) if opts[:linger]
|
74
|
-
socket.setSndHWM(opts[:sndhwm]) if opts[:sndhwm]
|
75
|
-
socket.setRcvHWM(opts[:rcvhwm]) if opts[:rcvhwm]
|
76
|
-
socket.set_router_mandatory(true) if type == ZMQ::ROUTER
|
77
|
-
socket.connect(opts[:connect]) if opts[:connect]
|
78
|
-
socket.bind(opts[:bind]) if opts[:bind]
|
79
|
-
socket
|
28
|
+
def selectable_fd
|
29
|
+
@socket.fd
|
80
30
|
end
|
81
31
|
|
82
|
-
def
|
83
|
-
@
|
32
|
+
def bind(address, port = nil)
|
33
|
+
@bound = true
|
34
|
+
@socket.bind(address)
|
84
35
|
end
|
85
36
|
|
86
|
-
def
|
87
|
-
@
|
37
|
+
def bound?
|
38
|
+
@bound
|
88
39
|
end
|
89
40
|
|
90
41
|
def connect(address)
|
91
|
-
@
|
42
|
+
@connected = true
|
43
|
+
@socket.connect(address)
|
92
44
|
end
|
93
45
|
|
94
|
-
def
|
95
|
-
|
46
|
+
def connection_pending?
|
47
|
+
false
|
96
48
|
end
|
97
49
|
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
@channel_key = nil
|
102
|
-
end
|
50
|
+
def connected?
|
51
|
+
@connected
|
52
|
+
end
|
103
53
|
|
104
|
-
|
54
|
+
def read_inbound_data
|
55
|
+
data = [@socket.recv_byte_array(0)]
|
56
|
+
while @socket.hasReceiveMore
|
57
|
+
data << @socket.recv_byte_array(0)
|
58
|
+
end
|
59
|
+
data
|
105
60
|
end
|
106
61
|
|
107
62
|
def send_data(data)
|
108
|
-
|
63
|
+
parts, last = data[0..-2], data.last
|
64
|
+
parts.each do |part|
|
65
|
+
@socket.send_byte_array(part, ZMQ::SNDMORE | ZMQ::DONTWAIT)
|
66
|
+
end
|
67
|
+
@socket.send_byte_array(last, ZMQ::DONTWAIT)
|
68
|
+
rescue ZMQException
|
69
|
+
@outbound_queue << data
|
109
70
|
end
|
110
71
|
|
111
|
-
|
112
|
-
|
72
|
+
# to get around iterating over an array in #send_data we pass message parts
|
73
|
+
# as arguments
|
74
|
+
def send1(a)
|
75
|
+
@socket.send_byte_array(a, ZMQ::DONTWAIT)
|
113
76
|
end
|
114
77
|
|
115
|
-
def
|
116
|
-
@socket.
|
78
|
+
def send2(a, b)
|
79
|
+
@socket.send_byte_array(a, ZMQ::DONTWAIT | ZMQ::DONTWAIT)
|
80
|
+
@socket.send_byte_array(b, ZMQ::DONTWAIT)
|
117
81
|
end
|
118
82
|
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
return false
|
83
|
+
def send3(a, b, c)
|
84
|
+
@socket.send_byte_array(a, ZMQ::DONTWAIT | ZMQ::DONTWAIT)
|
85
|
+
@socket.send_byte_array(b, ZMQ::DONTWAIT | ZMQ::DONTWAIT)
|
86
|
+
@socket.send_byte_array(c, ZMQ::DONTWAIT)
|
124
87
|
end
|
125
88
|
|
126
|
-
def
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
def write_outbound_data
|
132
|
-
until @outbound_queue.empty?
|
133
|
-
data = @outbound_queue.first
|
134
|
-
if send_msg(data)
|
135
|
-
@outbound_queue.shift
|
136
|
-
else
|
137
|
-
break
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
return true
|
89
|
+
def send4(a, b, c, d)
|
90
|
+
@socket.send_byte_array(a, ZMQ::DONTWAIT | ZMQ::DONTWAIT)
|
91
|
+
@socket.send_byte_array(b, ZMQ::DONTWAIT | ZMQ::DONTWAIT)
|
92
|
+
@socket.send_byte_array(c, ZMQ::DONTWAIT | ZMQ::DONTWAIT)
|
93
|
+
@socket.send_byte_array(d, ZMQ::DONTWAIT)
|
142
94
|
end
|
143
95
|
|
144
96
|
def has_more?
|
@@ -146,26 +98,33 @@ module ZMachine
|
|
146
98
|
end
|
147
99
|
|
148
100
|
def can_send?
|
149
|
-
@socket.events & ZMQ::Poller::POLLOUT == ZMQ::Poller::POLLOUT
|
101
|
+
super and (@socket.events & ZMQ::Poller::POLLOUT == ZMQ::Poller::POLLOUT)
|
150
102
|
end
|
151
103
|
|
152
|
-
def
|
153
|
-
|
104
|
+
def write_outbound_data
|
105
|
+
while can_send?
|
106
|
+
data = @outbound_queue.shift
|
107
|
+
send_data(data)
|
108
|
+
end
|
109
|
+
|
110
|
+
close if @close_scheduled
|
111
|
+
return true
|
154
112
|
end
|
155
113
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
114
|
+
def close(after_writing = false)
|
115
|
+
super
|
116
|
+
@closed = true
|
117
|
+
@connected = false
|
118
|
+
@bound = false
|
119
|
+
ZMachine.context.destroySocket(@socket) unless can_send?
|
160
120
|
end
|
161
121
|
|
162
|
-
def
|
163
|
-
|
164
|
-
[sock.local_port, sock.local_address.host_address]
|
122
|
+
def closed?
|
123
|
+
@closed
|
165
124
|
end
|
166
125
|
|
167
|
-
def
|
168
|
-
|
126
|
+
def peer
|
127
|
+
raise RuntimeError.new("ZMQChannel has no peer")
|
169
128
|
end
|
170
129
|
|
171
130
|
end
|