zmachine 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- attr_reader :connect_pending
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
- channel = TCPChannel.new(@selector)
34
- channel.socket = client_socket
35
- channel
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 close_connection(flush = true)
62
- @reactor.unbind_channel(self) if schedule_close(flush)
49
+ def connection_pending?
50
+ @socket.connection_pending?
63
51
  end
64
52
 
65
- def close
66
- if @channel_key
67
- @channel_key.cancel
68
- @channel_key = nil
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 send_data(data)
75
- return if @close_scheduled
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(buffer)
63
+ def read_inbound_data
64
+ buffer = @inbound_buffer
84
65
  buffer.clear
85
- raise IOException.new("eof") if @socket.read(buffer) == -1
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
- until @outbound_queue.empty?
83
+ while can_send?
93
84
  buffer = @outbound_queue.first
94
- @socket.write(buffer) if buffer.remaining > 0
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 @outbound_queue.empty? && !@close_scheduled
106
- update_events
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
- def sock_name
138
- sock = @socket.socket
139
- [sock.local_port, sock.local_address.host_address]
101
+ close if @close_scheduled
102
+ return true
140
103
  end
141
104
 
142
- def connect_pending=(value)
143
- @connect_pending = value
144
- update_events
105
+ def close(after_writing = false)
106
+ super
107
+ @socket.close unless can_send?
145
108
  end
146
109
 
147
- def update_events
148
- return unless @channel_key
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
- # these two are a bit misleading .. only used for the zmq channel
156
- def has_more?
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
@@ -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
- def self.create_socket_with_opts(type, opts = {})
16
- socket = ZMachine.context.create_socket(type)
17
- socket.setLinger(opts[:linger]) if opts[:linger]
18
- socket.setSndHWM(opts[:sndhwm]) if opts[:sndhwm]
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
- attr_reader :port
64
- attr_reader :socket
65
-
66
- def initialize(type, selector)
67
- super(selector)
68
- @socket = ZMQChannel.create_socket_with_opts type, linger: 0
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 self.create_socket_with_opts(type, opts = {})
72
- socket = ZMachine.context.create_socket(type)
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 register
83
- @channel_key ||= @socket.fd.register(@selector, current_events, self)
32
+ def bind(address, port = nil)
33
+ @bound = true
34
+ @socket.bind(address)
84
35
  end
85
36
 
86
- def bind(address)
87
- @port = @socket.bind(address)
37
+ def bound?
38
+ @bound
88
39
  end
89
40
 
90
41
  def connect(address)
91
- @socket.connect(address) if address
42
+ @connected = true
43
+ @socket.connect(address)
92
44
  end
93
45
 
94
- def identity=(value)
95
- @socket.identity = value.to_java_bytes
46
+ def connection_pending?
47
+ false
96
48
  end
97
49
 
98
- def close
99
- if @channel_key
100
- @channel_key.cancel
101
- @channel_key = nil
102
- end
50
+ def connected?
51
+ @connected
52
+ end
103
53
 
104
- @socket.close
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
- @outbound_queue << data unless send_msg(data)
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
- def send2(a,b)
112
- @socket.send2 a,b
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 send3(a,b,c)
116
- @socket.send3 a,b,c
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 send_msg(msg)
120
- msg.post(@socket, true)
121
- return true
122
- rescue ZMQException => e
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 read_inbound_data(buffer)
127
- return unless has_more?
128
- ZMsg.recv_msg(@socket)
129
- end
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 schedule_close(after_writing)
153
- true
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
- # TODO: fix me
157
- def peer_name
158
- sock = @socket.socket
159
- [sock.port, sock.inet_address.host_address]
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 sock_name
163
- sock = @socket.socket
164
- [sock.local_port, sock.local_address.host_address]
122
+ def closed?
123
+ @closed
165
124
  end
166
125
 
167
- def current_events
168
- SelectionKey::OP_READ
126
+ def peer
127
+ raise RuntimeError.new("ZMQChannel has no peer")
169
128
  end
170
129
 
171
130
  end