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.
@@ -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