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,87 +1,102 @@
1
+ java_import java.io.IOException
2
+ java_import java.nio.ByteBuffer
3
+ java_import java.nio.channels.SelectionKey
4
+
1
5
  module ZMachine
2
6
  class Connection
3
7
 
4
- attr_accessor :channel
5
-
6
8
  extend Forwardable
7
9
 
8
- alias original_method method
10
+ attr_accessor :channel
9
11
 
10
- def self.new(channel, *args, &block)
12
+ def self.new(*args, &block)
11
13
  allocate.instance_eval do
12
- @channel = channel
13
14
  initialize(*args, &block)
14
15
  post_init
15
16
  self
16
17
  end
17
18
  end
18
19
 
19
- def initialize(*args, &block)
20
- end
21
-
22
- # callbacks
20
+ # channel type dispatch
23
21
 
24
- def post_init
22
+ def bind(address, port_or_type)
23
+ ZMachine.logger.debug("zmachine:connection:#{__method__}", connection: self) if ZMachine.debug
24
+ if address =~ %r{\w+://}
25
+ @channel = ZMQChannel.new(port_or_type)
26
+ @channel.bind(address)
27
+ else
28
+ @channel = TCPChannel.new
29
+ @channel.bind(address, port_or_type)
30
+ end
31
+ self
25
32
  end
26
33
 
27
- def connection_completed
34
+ def connect(address, port_or_type)
35
+ ZMachine.logger.debug("zmachine:connection:#{__method__}", connection: self) if ZMachine.debug
36
+ if address.nil? or address =~ %r{\w+://}
37
+ @channel = ZMQChannel.new(port_or_type)
38
+ @channel.connect(address) if address
39
+ else
40
+ @channel = TCPChannel.new
41
+ @channel.connect(address, port_or_type)
42
+ end
43
+ if @connect_timeout
44
+ @timer = ZMachine.add_timer(@connect_timeout) do
45
+ ZMachine.reactor.close_connection(self)
46
+ end
47
+ end
48
+ self
28
49
  end
29
50
 
30
- def receive_data(data)
31
- end
51
+ def_delegator :@channel, :bound?
52
+ def_delegator :@channel, :closed?
53
+ def_delegator :@channel, :connected?
54
+ def_delegator :@channel, :connection_pending?
32
55
 
33
- def send3(a,b,c)
34
- @channel.send3 a,b,c
35
- end
36
- def send2(a,b)
37
- @channel.send2 a,b
38
- end
56
+ # EventMachine Connection API
39
57
 
40
- def unbind
58
+ def _not_implemented
59
+ raise RuntimeError.new("API call not implemented! #{caller[0]}")
41
60
  end
42
61
 
43
- def reconnect(server, port)
44
- ZMachine.reconnect server, port, self
62
+ def close_connection(after_writing = false)
63
+ @channel.close(after_writing)
45
64
  end
46
65
 
47
- # public API
48
-
49
- def_delegator :@channel, :close_connection
66
+ alias :close :close_connection
50
67
 
51
68
  def close_connection_after_writing
52
69
  close_connection(true)
53
70
  end
54
71
 
72
+ alias :close_after_writing close_connection_after_writing
73
+
55
74
  def comm_inactivity_timeout
56
- channel.comm_inactivity_timeout
75
+ @inactivity_timeout
57
76
  end
58
77
 
59
78
  def comm_inactivity_timeout=(value)
60
- channel.comm_inactivity_timeout = value
79
+ @inactivity_timeout = value
61
80
  end
62
81
 
63
82
  alias :set_comm_inactivity_timeout :comm_inactivity_timeout=
64
83
 
84
+ def connection_accepted(channel)
85
+ end
86
+
87
+ def connection_completed
88
+ end
89
+
65
90
  def detach
66
91
  _not_implemented
67
92
  end
68
93
 
69
94
  def error?
70
- errno = ZMachine::report_connection_error_status(@signature)
71
- case errno
72
- when 0
73
- false
74
- when -1
75
- true
76
- else
77
- Errno::constants.select do |name|
78
- Errno.__send__(:const_get, name)::Errno == errno
79
- end.first
80
- end
95
+ _not_implemented
81
96
  end
82
97
 
83
98
  def get_idle_time
84
- _not_implemented
99
+ (System.nano_time - @last_activity) / 1_000_000
85
100
  end
86
101
 
87
102
  def get_peer_cert
@@ -89,7 +104,7 @@ module ZMachine
89
104
  end
90
105
 
91
106
  def get_peername
92
- if peer = @channel.peer_name
107
+ if peer = @channel.peer
93
108
  ::Socket.pack_sockaddr_in(*peer)
94
109
  end
95
110
  end
@@ -102,15 +117,16 @@ module ZMachine
102
117
  _not_implemented
103
118
  end
104
119
 
105
- def_delegator :@channel, :get_sock_opt
120
+ def get_sock_opt(level, option)
121
+ _not_implemented
122
+ end
106
123
 
107
124
  def get_sockname
108
- if sock_name = @channel.sock_name
109
- ::Socket.pack_sockaddr_in(*sock_name)
110
- end
125
+ _not_implemented
111
126
  end
112
127
 
113
128
  def get_status
129
+ _not_implemented
114
130
  end
115
131
 
116
132
  def notify_readable=(mode)
@@ -118,7 +134,7 @@ module ZMachine
118
134
  end
119
135
 
120
136
  def notify_readable?
121
- _not_implemented
137
+ true
122
138
  end
123
139
 
124
140
  def notify_writable=(mode)
@@ -126,7 +142,7 @@ module ZMachine
126
142
  end
127
143
 
128
144
  def notify_writable?
129
- _not_implemented
145
+ @channel.can_send?
130
146
  end
131
147
 
132
148
  def pause
@@ -138,10 +154,14 @@ module ZMachine
138
154
  end
139
155
 
140
156
  def pending_connect_timeout=(value)
157
+ @connect_timeout = value
141
158
  end
142
159
 
143
160
  alias :set_pending_connect_timeout :pending_connect_timeout=
144
161
 
162
+ def post_init
163
+ end
164
+
145
165
  def proxy_completed
146
166
  _not_implemented
147
167
  end
@@ -154,11 +174,22 @@ module ZMachine
154
174
  _not_implemented
155
175
  end
156
176
 
177
+ def receive_data(data)
178
+ end
179
+
180
+ def reconnect(server, port_or_type)
181
+ ZMachine.reconnect(server, port_or_type, self)
182
+ end
183
+
157
184
  def resume
158
185
  _not_implemented
159
186
  end
160
187
 
161
- def_delegator :@channel, :send_data
188
+ def send_data(data)
189
+ ZMachine.logger.debug("zmachine:connection:#{__method__}", connection: self) if ZMachine.debug
190
+ @channel.send_data(data)
191
+ update_events
192
+ end
162
193
 
163
194
  def send_datagram(data, recipient_address, recipient_port)
164
195
  _not_implemented
@@ -168,7 +199,9 @@ module ZMachine
168
199
  _not_implemented
169
200
  end
170
201
 
171
- def_delegator :@channel, :set_sock_opt
202
+ def set_sock_opt(level, optname, optval)
203
+ _not_implemented
204
+ end
172
205
 
173
206
  def ssl_handshake_completed
174
207
  _not_implemented
@@ -190,10 +223,104 @@ module ZMachine
190
223
  _not_implemented
191
224
  end
192
225
 
193
- private
226
+ def unbind
227
+ end
194
228
 
195
- def _not_implemented
196
- raise RuntimeError.new("API call not implemented! #{caller[0]}")
229
+ # triggers
230
+
231
+ def acceptable!
232
+ client = @channel.accept
233
+ connection_accepted(client) if client.connected?
234
+ ZMachine.logger.debug("zmachine:connection:#{__method__}", connection: self, client: client) if ZMachine.debug
235
+ self.class.new.tap do |connection|
236
+ connection.channel = client
237
+ end
238
+ end
239
+
240
+ def connectable!
241
+ ZMachine.logger.debug("zmachine:connection:#{__method__}", connection: self) if ZMachine.debug
242
+ @channel.finish_connecting
243
+ @timer.cancel if @timer # cancel pending connect timer
244
+ mark_active!
245
+ connection_completed if @channel.connected?
246
+ update_events
247
+ nil
197
248
  end
249
+
250
+ def readable!
251
+ ZMachine.logger.debug("zmachine:connection:#{__method__}", connection: self) if ZMachine.debug
252
+ mark_active!
253
+ data = @channel.read_inbound_data
254
+ receive_data(data) if data
255
+ nil
256
+ end
257
+
258
+ def writable!
259
+ ZMachine.logger.debug("zmachine:connection:#{__method__}", connection: self) if ZMachine.debug
260
+ mark_active!
261
+ @channel.write_outbound_data
262
+ update_events
263
+ nil
264
+ end
265
+
266
+ # selector registration
267
+
268
+ def register(selector)
269
+ ZMachine.logger.debug("zmachine:connection:#{__method__}", connection: self, fd: @channel.selectable_fd) if ZMachine.debug
270
+ @channel_key ||= @channel.selectable_fd.register(selector, current_events, self)
271
+ end
272
+
273
+ def update_events
274
+ return unless @channel_key
275
+ ZMachine.logger.debug("zmachine:connection:#{__method__}", connection: self) if ZMachine.debug
276
+ @channel_key.interest_ops(current_events)
277
+ end
278
+
279
+ def current_events
280
+ if @channel.is_a?(ZMQChannel)
281
+ return SelectionKey::OP_READ
282
+ end
283
+
284
+ if bound?
285
+ return SelectionKey::OP_ACCEPT
286
+ end
287
+
288
+ if connection_pending?
289
+ return SelectionKey::OP_CONNECT
290
+ end
291
+
292
+ events = 0
293
+
294
+ events |= SelectionKey::OP_READ if notify_readable?
295
+ events |= SelectionKey::OP_WRITE if notify_writable?
296
+
297
+ return events
298
+ end
299
+
300
+ def process_events
301
+ return unless @channel_key
302
+ ZMachine.logger.debug("zmachine:connection:#{__method__}", connection: self) if ZMachine.debug
303
+ if @channel_key.connectable?
304
+ connectable!
305
+ elsif @channel_key.acceptable?
306
+ acceptable!
307
+ else
308
+ writable! if @channel_key.writable?
309
+ readable! if @channel_key.readable?
310
+ end
311
+ end
312
+
313
+ def mark_active!
314
+ @last_activity = System.nano_time
315
+ renew_timer if @inactivity_timeout
316
+ end
317
+
318
+ def renew_timer
319
+ @timer.cancel if @timer
320
+ @timer = ZMachine.add_timer(@inactivity_timeout) do
321
+ ZMachine.reactor.close_connection(self)
322
+ end
323
+ end
324
+
198
325
  end
199
326
  end
@@ -0,0 +1,133 @@
1
+ java_import java.nio.channels.ClosedChannelException
2
+
3
+ require 'zmachine/tcp_channel'
4
+ require 'zmachine/zmq_channel'
5
+
6
+ module ZMachine
7
+ class ConnectionManager
8
+
9
+ attr_reader :connections
10
+
11
+ def initialize(selector)
12
+ ZMachine.logger.debug("zmachine:connection_manager:#{__method__}") if ZMachine.debug
13
+ @selector = selector
14
+ @connections = []
15
+ @zmq_connections = []
16
+ @new_connections = []
17
+ @unbound_connections = []
18
+ end
19
+
20
+ def idle?
21
+ @new_connections.size == 0 and
22
+ @zmq_connections.none? {|c| c.channel.has_more? } # see comment in #process
23
+ end
24
+
25
+ def shutdown
26
+ ZMachine.logger.debug("zmachine:connection_manager:#{__method__}") if ZMachine.debug
27
+ @unbound_connections += @connections
28
+ cleanup
29
+ end
30
+
31
+ def bind(address, port_or_type, handler, *args, &block)
32
+ ZMachine.logger.debug("zmachine:connection_manager:#{__method__}", address: address, port_or_type: port_or_type) if ZMachine.debug
33
+ connection = build_connection(Connection, handler, *args, &block)
34
+ connection.bind(address, port_or_type)
35
+ @new_connections << connection
36
+ end
37
+
38
+ def connect(address, port_or_type, handler, *args, &block)
39
+ ZMachine.logger.debug("zmachine:connection_manager:#{__method__}", address: address, port_or_type: port_or_type) if ZMachine.debug
40
+ connection = build_connection(Connection, handler, *args, &block)
41
+ connection.connect(address, port_or_type)
42
+ @new_connections << connection
43
+ yield connection if block_given?
44
+ rescue java.nio.channels.UnresolvedAddressException
45
+ raise ZMachine::ConnectionError.new('unable to resolve server address')
46
+ end
47
+
48
+ def process
49
+ ZMachine.logger.debug("zmachine:connection_manager:#{__method__}") if ZMachine.debug
50
+ add_new_connections
51
+ it = @selector.selected_keys.iterator
52
+ while it.has_next
53
+ process_connection(it.next.attachment)
54
+ it.remove
55
+ end
56
+ # super ugly, but ZMQ only triggers the FD if and only if you have read
57
+ # every message from the socket. under load however there will always be
58
+ # new messages in the mailbox between last recv and next select, which
59
+ # causes the FD never to be triggered again.
60
+ # the only mitigation strategy i came up with is iterating over all channels :(
61
+ @zmq_connections.each do |connection|
62
+ connection.readable! if connection.channel.has_more?
63
+ end
64
+ end
65
+
66
+ def process_connection(connection)
67
+ new_connection = connection.process_events
68
+ @new_connections << new_connection if new_connection
69
+ rescue IOException
70
+ close_connection(connection)
71
+ end
72
+
73
+ def close_connection(connection)
74
+ ZMachine.logger.debug("zmachine:connection_manager:#{__method__}", connection: connection) if ZMachine.debug
75
+ @unbound_connections << connection
76
+ end
77
+
78
+ def add_new_connections
79
+ @new_connections.compact.each do |connection|
80
+ ZMachine.logger.debug("zmachine:connection_manager:#{__method__}", connection: connection) if ZMachine.debug
81
+ begin
82
+ connection.register(@selector)
83
+ @connections << connection
84
+ @zmq_connections << connection if connection.channel.is_a?(ZMQChannel)
85
+ rescue ClosedChannelException => e
86
+ ZMachine.logger.exception(e, "failed to add connection")
87
+ @unbound_connections << connection
88
+ end
89
+ end
90
+ @new_connections.clear
91
+ end
92
+
93
+ def cleanup
94
+ return if @unbound_connections.empty?
95
+ ZMachine.logger.debug("zmachine:connection_manager:#{__method__}") if ZMachine.debug
96
+ @unbound_connections.each do |connection|
97
+ reason = nil
98
+ connection, reason = *connection if connection.is_a?(Array)
99
+ begin
100
+ @connections.delete(connection)
101
+ @zmq_connections.delete(connection)
102
+ connection.unbind
103
+ connection.close
104
+ rescue Exception => e
105
+ ZMachine.logger.exception(e, "failed to unbind connection") if ZMachine.debug
106
+ end
107
+ end
108
+ @unbound_connections.clear
109
+ end
110
+
111
+ private
112
+
113
+ def build_connection(klass = Connection, handler = nil, *args, &block)
114
+ if handler and handler.is_a?(Class)
115
+ handler.new(*args, &block)
116
+ elsif handler and handler.is_a?(Connection)
117
+ # already initialized connection on reconnect
118
+ handler
119
+ elsif handler
120
+ connection_from_module(klass, handler).new(*args, &block)
121
+ else
122
+ klass.new(*args, &block)
123
+ end
124
+ end
125
+
126
+ def connection_from_module(klass, handler)
127
+ handler::CONNECTION_CLASS
128
+ rescue NameError
129
+ handler::const_set(:CONNECTION_CLASS, Class.new(klass) { include handler })
130
+ end
131
+
132
+ end
133
+ end