zmachine 0.1.3 → 0.2.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 +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
|