zmachine 0.1.1 → 0.1.3

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 ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ODFlZWJhODg3YmM5NWMxYmJmZmRmNmM5ZGI0N2NkMzIzNWU3YjEwZg==
5
+ data.tar.gz: !binary |-
6
+ NGMyYTk4ZmIwODI5OWZlMjY1ZDQxYjRkYTBlZGJiZjAyZGY0MDY4Mw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YTVjMmVhODBmZjU3NTA4MmQ4MDBkOTRlZmE4OTMzNjA4ZWM4NjgwZmM2ZWVm
10
+ YWRlYTM0Mzg0NTlhYzYwYzgyOWIwZmMzMzcyZTEzNDU2MjFjNGQyYjliMmFh
11
+ YzJmMWYwZWU5MTdjYmJmZjk0ZDgwZmQ2YzZhOGQxZjdmNTNmZTA=
12
+ data.tar.gz: !binary |-
13
+ OGRmZjQ3YWI2NTllOWI2MzQzODVhOGVjYzAzMjI5ZDgwNTQ3NTU4MjM4NjVk
14
+ Yjk5MzU0MmJmM2QwNjUxYzllZTZlMWIyMTVlMGM5OTE1YWZmOTA1OTIzZGRm
15
+ Y2VjYjI4N2JkNzRjOTcxNDgzMTA0NTU1YmM0ZWYwOWFmMzQ1M2I=
@@ -4,8 +4,16 @@ module ZMachine
4
4
  attr_reader :args
5
5
  attr_reader :callback
6
6
 
7
- def initialize(channel, klass, *args, &block)
8
- @klass, @args, @callback = klass, args, block
7
+ def initialize(channel, klass, *args )
8
+ @klass, @args, @callback = klass, args[0...-1], args.last
9
+ @channel = channel
10
+ end
11
+
12
+ def unbind
13
+ end
14
+
15
+ def close
16
+ @channel.close
9
17
  end
10
18
 
11
19
  end
@@ -1,13 +1,42 @@
1
1
  module ZMachine
2
2
  class Channel
3
3
 
4
- attr_reader :socket
5
- attr_reader :selector
4
+ attr_accessor :socket
5
+ attr_reader :selector
6
6
  attr_accessor :handler
7
+ attr_accessor :reactor
8
+
9
+ attr_reader :comm_inactivity_timeout
10
+ attr_reader :last_comm_activity
7
11
 
8
12
  def initialize(selector)
9
13
  @selector = selector
10
14
  @outbound_queue = []
15
+ @comm_inactivity_timeout = 0
16
+ @timedout = false
17
+ mark_active!
18
+ end
19
+
20
+ # assigned in seconds!!
21
+ def comm_inactivity_timeout=(value)
22
+ # we are in nanos
23
+ @comm_inactivity_timeout = value * 1000_000_000
24
+ end
25
+
26
+ def mark_active!
27
+ @last_comm_activity = System.nano_time
28
+ end
29
+
30
+ def timedout?
31
+ @timedout
32
+ end
33
+
34
+ def timedout!
35
+ @timedout = true
36
+ end
37
+
38
+ def was_active?(now)
39
+ @last_comm_activity + @comm_inactivity_timeout >= now
11
40
  end
12
41
 
13
42
  end
@@ -30,9 +30,20 @@ module ZMachine
30
30
  def receive_data(data)
31
31
  end
32
32
 
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
39
+
33
40
  def unbind
34
41
  end
35
42
 
43
+ def reconnect(server, port)
44
+ ZMachine.reconnect server, port, self
45
+ end
46
+
36
47
  # public API
37
48
 
38
49
  def_delegator :@channel, :close_connection
@@ -42,9 +53,11 @@ module ZMachine
42
53
  end
43
54
 
44
55
  def comm_inactivity_timeout
56
+ channel.comm_inactivity_timeout
45
57
  end
46
58
 
47
59
  def comm_inactivity_timeout=(value)
60
+ channel.comm_inactivity_timeout = value
48
61
  end
49
62
 
50
63
  alias :set_comm_inactivity_timeout :comm_inactivity_timeout=
@@ -180,7 +193,7 @@ module ZMachine
180
193
  private
181
194
 
182
195
  def _not_implemented
183
- raise RuntimeError.new("API call not implemented!")
196
+ raise RuntimeError.new("API call not implemented! #{caller[0]}")
184
197
  end
185
198
  end
186
199
  end
@@ -0,0 +1,77 @@
1
+ module ZMachine
2
+
3
+ class HashedWheelTimeout
4
+ attr_accessor :round
5
+ attr_reader :deadline
6
+ attr_reader :callback
7
+ def initialize(round, deadline, &block)
8
+ @round = round
9
+ @deadline = deadline
10
+ @callback = block
11
+ end
12
+ end
13
+
14
+ class HashedWheel
15
+
16
+ attr_reader :slots
17
+ attr_accessor :last
18
+
19
+ def initialize(number_of_slots, tick_length, start_time = System.nano_time)
20
+ @slots = Array.new(number_of_slots) { [] }
21
+ @tick_length = tick_length * 1_000_000
22
+ @last = start_time
23
+ @current_tick = 0
24
+
25
+ @next = nil
26
+ end
27
+
28
+ def next_deadline
29
+ return Float::INFINITY if !@next
30
+ @next.deadline
31
+ end
32
+
33
+ def add(timeout, &block)
34
+ timeout *= 1_000_000 # ms to ns
35
+ idx = (timeout / @tick_length) % @slots.length
36
+ round = (timeout / @tick_length) / @slots.length
37
+ @slots[idx] << hwt = HashedWheelTimeout.new(round, System.nano_time + timeout, &block)
38
+ if !@next
39
+ @next = hwt
40
+ else
41
+ @next = hwt if @next.deadline > hwt.deadline
42
+ end
43
+ hwt
44
+ end
45
+
46
+ def reset(time = System.nano_time)
47
+ @slots = Array.new(@slots.length) { [] }
48
+ @current_tick = 0
49
+ @last = time
50
+ end
51
+
52
+ # returns all timeouts
53
+ def advance(now = System.nano_time)
54
+ # how many tickts have passed?
55
+ passed_ticks = (now - @last) / @tick_length
56
+ round = 0
57
+ result = []
58
+ begin
59
+ q, @current_tick = @current_tick.divmod slots.length
60
+ round += q
61
+ # collect timeouts
62
+ @slots[@current_tick].delete_if do |timeout|
63
+ timeout.round -= round
64
+ result << timeout if timeout.round <= 0 && timeout.deadline < now
65
+ end
66
+ @current_tick += 1
67
+ passed_ticks -= 1
68
+ end while passed_ticks > 0
69
+ @last = now
70
+ result
71
+ end
72
+
73
+ end
74
+ end
75
+
76
+
77
+
@@ -1,3 +1,4 @@
1
+ java_import java.lang.System
1
2
  java_import java.io.FileDescriptor
2
3
  java_import java.io.IOException
3
4
  java_import java.net.InetSocketAddress
@@ -17,11 +18,46 @@ java_import org.zeromq.ZContext
17
18
  require 'zmachine/acceptor'
18
19
  require 'zmachine/tcp_channel'
19
20
  require 'zmachine/zmq_channel'
21
+ require 'zmachine/hashed_wheel'
20
22
 
21
23
  module ZMachine
24
+
25
+ class NotReactorOwner < Exception
26
+ end
27
+
28
+ class NoReactorError < Exception
29
+ end
30
+
22
31
  class Reactor
23
32
 
33
+ @mutex = Mutex.new
34
+
35
+ def self.register_reactor(reactor)
36
+ @mutex.synchronize do
37
+ @reactors ||= []
38
+ @reactors << reactor
39
+ end
40
+ end
41
+
42
+ def self.terminate_all_reactors
43
+ # should have a lock ....
44
+ @mutex.synchronize do
45
+ @reactors.each(&:stop_event_loop)
46
+ @reactors.clear
47
+ end
48
+ end
49
+
50
+ def self.unregister_reactor(reactor)
51
+ @mutex.synchronize do
52
+ @reactors.delete(reactor)
53
+ end
54
+ end
55
+
24
56
  def initialize
57
+ # a 10 ms tick wheel with a 512 slots => ~5s for a round
58
+ @wheel = HashedWheel.new(512, 10)
59
+ @last = now = System.nano_time
60
+ @heartbeat_interval = 50_000_000 # 50 ms
25
61
  @timers = TreeMap.new
26
62
  @timer_callbacks = {}
27
63
  @channels = []
@@ -40,13 +76,23 @@ module ZMachine
40
76
  @shutdown_hooks << block
41
77
  end
42
78
 
79
+ def xadd_timer(*args, &block)
80
+ check_reactor_thread
81
+ interval = args.shift
82
+ callback = args.shift || block
83
+ return unless callback
84
+ @wheel.add((interval.to_f * 1000).to_i, &callback)
85
+ end
86
+
87
+
43
88
  def add_timer(*args, &block)
89
+ check_reactor_thread
44
90
  interval = args.shift
45
91
  callback = args.shift || block
46
92
  return unless callback
47
93
 
48
94
  signature = next_signature
49
- deadline = java.util.Date.new.time + (interval.to_f * 1000).to_i
95
+ deadline = System.nano_time + (interval.to_f * 1000_000_000).to_i
50
96
 
51
97
  if @timers.contains_key(deadline)
52
98
  @timers.get(deadline) << signature
@@ -59,6 +105,12 @@ module ZMachine
59
105
  signature
60
106
  end
61
107
 
108
+ def unbind_channel(channel)
109
+ channel.handler = nil
110
+ ZMachine.logger.debug "zmachine:unbind_channel", channel:channel if ZMachine.debug
111
+ @unbound_channels << channel
112
+ end
113
+
62
114
  def cancel_timer(timer_or_sig)
63
115
  if timer_or_sig.respond_to?(:cancel)
64
116
  timer_or_sig.cancel
@@ -70,11 +122,21 @@ module ZMachine
70
122
 
71
123
  def connect(server, port_or_type=nil, handler=nil, *args, &block)
72
124
  ZMachine.logger.debug("zmachine:#{__method__}", server: server, port_or_type: port_or_type) if ZMachine.debug
125
+ check_reactor_thread
73
126
  if server.nil? or server =~ %r{\w+://}
74
127
  _connect_zmq(server, port_or_type, handler, *args, &block)
75
128
  else
76
129
  _connect_tcp(server, port_or_type, handler, *args, &block)
77
130
  end
131
+ rescue java.nio.channels.UnresolvedAddressException
132
+ raise ZMachine::ConnectionError.new('unable to resolve server address')
133
+ end
134
+
135
+ def reconnect(server, port, handler)
136
+ # No reconnect for zmq ... handled by zmq itself anyway
137
+ return if handler && handler.channel.is_a?(ZMQChannel) #
138
+ # TODO : we might want to check if this connection is really dead?
139
+ connect server, port, handler
78
140
  end
79
141
 
80
142
  def connection_count
@@ -87,7 +149,7 @@ module ZMachine
87
149
 
88
150
  def next_tick(callback=nil, &block)
89
151
  @next_tick_queue << (callback || block)
90
- signal_loopbreak if running?
152
+ signal_loopbreak if reactor_running?
91
153
  end
92
154
 
93
155
  def run(callback=nil, shutdown_hook=nil, &block)
@@ -98,9 +160,16 @@ module ZMachine
98
160
  add_shutdown_hook(shutdown_hook) if shutdown_hook
99
161
 
100
162
  begin
163
+ # list of active reactors
164
+ Reactor.register_reactor(self)
165
+
101
166
  @running = true
102
167
 
103
- add_timer(0, @callback) if @callback
168
+ if @callback
169
+ add_timer(0) do
170
+ @callback.call(self)
171
+ end
172
+ end
104
173
 
105
174
  @selector = Selector.open
106
175
  @run_reactor = true
@@ -111,11 +180,20 @@ module ZMachine
111
180
  break unless @run_reactor
112
181
  run_timers
113
182
  break unless @run_reactor
183
+ # advance_timer_wheel
184
+ # break unless @run_reactor
185
+ remove_timed_out_channels
114
186
  remove_unbound_channels
115
187
  check_io
116
188
  add_new_channels
117
189
  process_io
118
190
  end
191
+ rescue => e
192
+ puts e.message
193
+ raise e
194
+ # # maybe add error check callback here
195
+ # puts "FATAL reactor died : #{e.message}"
196
+ # puts e.backtrace
119
197
  ensure
120
198
  ZMachine.logger.debug("zmachine:#{__method__}", stop: :selector) if ZMachine.debug
121
199
  @selector.close rescue nil
@@ -127,8 +205,16 @@ module ZMachine
127
205
  @shutdown_hooks.pop.call until @shutdown_hooks.empty?
128
206
  @next_tick_queue = ConcurrentLinkedQueue.new
129
207
  @running = false
208
+ Reactor.unregister_reactor(self)
130
209
  ZMachine.logger.debug("zmachine:#{__method__}", stop: :zcontext) if ZMachine.debug
131
210
  ZMachine.context.destroy
211
+ ZMachine.clear_current_reactor
212
+ end
213
+ end
214
+
215
+ def advance_timer_wheel
216
+ @wheel.advance.each do |timeout|
217
+ timeout.callback.call
132
218
  end
133
219
  end
134
220
 
@@ -156,11 +242,18 @@ module ZMachine
156
242
 
157
243
  private
158
244
 
245
+ def check_reactor_thread
246
+ raise NoReactorError if !Thread.current[:reactor]
247
+ raise NotReactorOwner if Thread.current[:reactor] != self
248
+ end
249
+
250
+
159
251
  def _bind_tcp(address, port, handler, *args, &block)
160
252
  klass = _klass_from_handler(Connection, handler)
161
253
  channel = TCPChannel.new(@selector)
162
254
  channel.bind(address, port)
163
- add_channel(channel, Acceptor, klass, *args, &block)
255
+ add_channel(channel, Acceptor, klass, *(args+[block]) )
256
+ channel.handler
164
257
  end
165
258
 
166
259
  def _bind_zmq(address, type, handler, *args, &block)
@@ -168,6 +261,7 @@ module ZMachine
168
261
  channel = ZMQChannel.new(type, @selector)
169
262
  channel.bind(address)
170
263
  add_channel(channel, klass, *args, &block)
264
+ channel.handler
171
265
  end
172
266
 
173
267
  def _connect_tcp(address, port, handler=nil, *args, &block)
@@ -176,6 +270,8 @@ module ZMachine
176
270
  channel.connect(address, port)
177
271
  channel.connect_pending = true
178
272
  add_channel(channel, klass, *args, &block)
273
+ channel.mark_active!
274
+ channel.handler
179
275
  end
180
276
 
181
277
  def _connect_zmq(address, type, handler=nil, *args, &block)
@@ -184,28 +280,43 @@ module ZMachine
184
280
  add_channel(channel, klass, *args, &block)
185
281
  channel.connect(address)
186
282
  channel.handler.connection_completed
187
- channel
283
+ channel.handler
188
284
  end
189
285
 
190
286
  def check_io
287
+
288
+ now = System.nano_time
289
+
191
290
  if @new_channels.size > 0
192
291
  timeout = -1
193
292
  elsif !@timers.empty?
194
- now = java.util.Date.new.time
195
293
  timer_key = @timers.first_key
196
- timeout = timer_key - now
294
+ timeout = (timer_key - now)
197
295
  timeout = -1 if timeout <= 0
198
296
  else
297
+ # puts "hb = #{@heartbeat_interval}"
298
+ #timeout = (@heartbeat_interval - (now - @last)) / 1_000_000
199
299
  timeout = 0
200
300
  end
201
301
 
202
- if @channels.any?(&:has_more?)
203
- timeout = -1
302
+ # timeout = @heartbeat_interval - (now - @last)
303
+ # wnd = @wheel.next_deadline - now
304
+ # timeout = wnd if timeout > wnd
305
+ # timeout = -1 if timeout == 0
306
+ # @last = now
307
+
308
+ # sucks a bit ...
309
+ timeout = -1 if @channels.any?(&:has_more?) # iterate all channels????
310
+
311
+ # we have to convert the timeout from ns to ms
312
+ if timeout > 0
313
+ timeout /= 1_000_000
314
+ timeout = -1 if timeout == 0 # check if it would be below 1ms .. 0 would be an indefinite wait but we want to select NOW
204
315
  end
205
316
 
206
317
  ZMachine.logger.debug("zmachine:#{__method__}", timeout: timeout) if ZMachine.debug
207
318
 
208
- if timeout == -1
319
+ if timeout < 0
209
320
  @selector.select_now
210
321
  else
211
322
  @selector.select(timeout)
@@ -218,7 +329,6 @@ module ZMachine
218
329
  while it.has_next
219
330
  selected_key = it.next
220
331
  it.remove
221
-
222
332
  if selected_key.connectable?
223
333
  is_connectable(selected_key.attachment)
224
334
  elsif selected_key.acceptable?
@@ -237,42 +347,66 @@ module ZMachine
237
347
 
238
348
  def is_acceptable(channel)
239
349
  ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
240
- client_channel = channel.accept(next_signature)
350
+ client_channel = channel.accept
241
351
  acceptor = channel.handler
242
- add_channel(client_channel, acceptor.klass, *acceptor.args, &acceptor.callback)
243
- rescue IOException
352
+ new_channel = add_channel(client_channel, acceptor.klass, *acceptor.args, &acceptor.callback)
353
+ new_channel.mark_active!
354
+ new_channel
355
+ rescue IOException => e
356
+ ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, e:e.message) if ZMachine.debug
244
357
  channel.close
245
358
  end
246
359
 
247
360
  def is_readable(channel)
248
361
  ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
362
+ channel.mark_active!
249
363
  data = channel.read_inbound_data(@read_buffer)
250
364
  channel.handler.receive_data(data) if data
251
- rescue IOException
365
+ rescue IOException => e
366
+ ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, e:e.message) if ZMachine.debug
252
367
  @unbound_channels << channel
253
368
  end
254
369
 
255
370
  def is_writable(channel)
256
371
  ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
372
+ channel.mark_active!
257
373
  @unbound_channels << channel unless channel.write_outbound_data
258
- rescue IOException
374
+ rescue IOException => e
375
+ ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, e:e.message) if ZMachine.debug
259
376
  @unbound_channels << channel
260
377
  end
261
378
 
262
379
  def is_connectable(channel)
380
+ channel.mark_active!
263
381
  ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
264
- channel.finish_connecting
265
- channel.handler.connection_completed
266
- rescue IOException
382
+ result = channel.finish_connecting
383
+ if !result
384
+ ZMachine.logger.warn("zmachine:finish_connecting failed", channel: channel) if !result
385
+ @unbound_channels << channel
386
+ else
387
+ channel.handler.connection_completed
388
+ end
389
+ rescue IOException => e
390
+ ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, e:e.message) if ZMachine.debug
267
391
  @unbound_channels << channel
268
392
  end
269
393
 
270
- def add_channel(channel, klass, *args, &block)
394
+ def add_channel(channel, klass_or_instance, *args, &block)
271
395
  ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
272
- channel.handler = klass.new(channel, *args)
396
+ if klass_or_instance.is_a?(Connection)
397
+ # if klass_or_instance is not a class but already an instance
398
+ channel.handler = klass_or_instance
399
+ klass_or_instance.channel = channel
400
+ else
401
+ channel.handler = klass_or_instance.new(channel, *args)
402
+ end
403
+ channel.reactor = self
273
404
  @new_channels << channel
274
405
  block.call(channel.handler) if block
275
406
  channel
407
+ rescue => e
408
+ puts "ERROR adding channel #{e.message}"
409
+ puts e.backtrace
276
410
  end
277
411
 
278
412
  def add_new_channels
@@ -280,17 +414,40 @@ module ZMachine
280
414
  begin
281
415
  channel.register
282
416
  @channels << channel
283
- rescue ClosedChannelException
417
+ rescue ClosedChannelException => e
418
+ puts "UNBIND on add_channel #{e.message}"
284
419
  @unbound_channels << channel
285
420
  end
286
421
  end
287
422
  @new_channels.clear
288
423
  end
289
424
 
425
+ def remove_timed_out_channels
426
+ # TODO : we want to replace all of our timer handling with hashed wheels in the future
427
+ now = now = System.nano_time
428
+ @channels.each do |channel|
429
+ next if channel.comm_inactivity_timeout == 0
430
+ if !channel.was_active?(now)
431
+ ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, now:now,last:channel.last_comm_activity, delta:(now - channel.last_comm_activity), timeout:timeout=channel.comm_inactivity_timeout) if ZMachine.debug
432
+ channel.timedout!
433
+ @unbound_channels << channel
434
+ end
435
+ end
436
+ end
437
+
290
438
  def remove_unbound_channels
439
+ return if @unbound_channels.empty?
291
440
  @unbound_channels.each do |channel|
292
- channel.handler.unbind if channel.handler
293
- channel.close
441
+ reason = nil
442
+ channel, reason = *channel if channel.is_a?(Array)
443
+ begin
444
+ # puts "#{channel} unbound"
445
+ @channels.delete(channel)
446
+ channel.handler.unbind if channel.handler
447
+ channel.close
448
+ rescue Exception => e
449
+ ZMachine.logger.debug("zmachine:#{__method__} unbind error", channel: channel, reason:reason, klass:e.class, msg:e.message) if ZMachine.debug
450
+ end
294
451
  end
295
452
  @unbound_channels.clear
296
453
  end
@@ -302,21 +459,20 @@ module ZMachine
302
459
  begin
303
460
  size -= 1
304
461
  callback.call
305
- ensure
306
- ZMachine.next_tick {} if $!
462
+ # ensure
463
+ # ZMachine.next_tick {} if $!
307
464
  end
308
465
  end
309
466
  end
310
467
 
468
+ # TODO : we should definitly optimize periodic timers ... right now they are wasting a hell of cycle es they are recreated on every invocation
311
469
  def run_timers
312
- now = java.util.Date.new.time
470
+ now = System.nano_time
313
471
  until @timers.empty?
314
472
  timer_key = @timers.first_key
315
473
  break if timer_key > now
316
-
317
474
  signatures = @timers.get(timer_key)
318
475
  @timers.remove(timer_key)
319
-
320
476
  # Fire all timers at this timestamp
321
477
  signatures.each do |signature|
322
478
  callback = @timer_callbacks.delete(signature)
@@ -338,8 +494,11 @@ module ZMachine
338
494
  def _klass_from_handler(klass = Connection, handler = nil)
339
495
  if handler and handler.is_a?(Class)
340
496
  handler
497
+ elsif handler and handler.is_a?(Connection)
498
+ # can happen on reconnect
499
+ handler
341
500
  elsif handler
342
- _handler_from_klass(klass, handler)
501
+ _handler_from_module(klass, handler)
343
502
  else
344
503
  klass
345
504
  end
@@ -11,6 +11,7 @@ module ZMachine
11
11
  super(selector)
12
12
  @close_scheduled = false
13
13
  @connect_pending = false
14
+ @server_socket = false
14
15
  end
15
16
 
16
17
  def register
@@ -18,6 +19,7 @@ module ZMachine
18
19
  end
19
20
 
20
21
  def bind(address, port)
22
+ @server_socket = true
21
23
  address = InetSocketAddress.new(address, port)
22
24
  @socket = ServerSocketChannel.open
23
25
  @socket.configure_blocking(false)
@@ -28,7 +30,9 @@ module ZMachine
28
30
  client_socket = socket.accept
29
31
  return unless client_socket
30
32
  client_socket.configure_blocking(false)
31
- TCPChannel.new(client_socket, @selector)
33
+ channel = TCPChannel.new(@selector)
34
+ channel.socket = client_socket
35
+ channel
32
36
  end
33
37
 
34
38
  def connect(address, port)
@@ -54,6 +58,10 @@ module ZMachine
54
58
  end
55
59
  end
56
60
 
61
+ def close_connection(flush = true)
62
+ @reactor.unbind_channel(self) if schedule_close(flush)
63
+ end
64
+
57
65
  def close
58
66
  if @channel_key
59
67
  @channel_key.cancel
@@ -84,7 +92,6 @@ module ZMachine
84
92
  until @outbound_queue.empty?
85
93
  buffer = @outbound_queue.first
86
94
  @socket.write(buffer) if buffer.remaining > 0
87
-
88
95
  # Did we consume the whole outbound buffer? If yes,
89
96
  # pop it off and keep looping. If no, the outbound network
90
97
  # buffers are full, so break out of here.
@@ -145,12 +152,17 @@ module ZMachine
145
152
  end
146
153
  end
147
154
 
155
+ # these two are a bit misleading .. only used for the zmq channel
148
156
  def has_more?
149
157
  false
150
158
  end
151
159
 
160
+ def can_send?
161
+ false
162
+ end
163
+
152
164
  def current_events
153
- if @socket.respond_to?(:accept)
165
+ if @socket.is_a?(ServerSocketChannel)
154
166
  return SelectionKey::OP_ACCEPT
155
167
  end
156
168
 
@@ -4,16 +4,79 @@ java_import org.zeromq.ZMQException
4
4
 
5
5
  require 'zmachine/channel'
6
6
 
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
+ class ZMQ
14
+ 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
57
+ end
58
+ end
59
+
7
60
  module ZMachine
8
61
  class ZMQChannel < Channel
9
62
 
10
63
  attr_reader :port
64
+ attr_reader :socket
11
65
 
12
66
  def initialize(type, selector)
13
67
  super(selector)
14
- @socket = ZMachine.context.create_socket(type)
15
- @socket.linger = 0
16
- @socket.set_router_mandatory(true) if type == ZMQ::ROUTER
68
+ @socket = ZMQChannel.create_socket_with_opts type, linger: 0
69
+ end
70
+
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
17
80
  end
18
81
 
19
82
  def register
@@ -45,10 +108,18 @@ module ZMachine
45
108
  @outbound_queue << data unless send_msg(data)
46
109
  end
47
110
 
111
+ def send2(a,b)
112
+ @socket.send2 a,b
113
+ end
114
+
115
+ def send3(a,b,c)
116
+ @socket.send3 a,b,c
117
+ end
118
+
48
119
  def send_msg(msg)
49
- msg.java_send(:send, [org.zeromq.ZMQ::Socket], @socket)
120
+ msg.post(@socket, true)
50
121
  return true
51
- rescue ZMQException
122
+ rescue ZMQException => e
52
123
  return false
53
124
  end
54
125
 
data/lib/zmachine.rb CHANGED
@@ -17,6 +17,10 @@ module ZMachine
17
17
  attr_accessor :debug
18
18
  end
19
19
 
20
+ def self.clear_current_reactor
21
+ Thread.current[:reactor] = nil
22
+ end
23
+
20
24
  def self.reactor
21
25
  Thread.current[:reactor] ||= Reactor.new
22
26
  end
@@ -43,7 +47,7 @@ module ZMachine
43
47
  end
44
48
 
45
49
  def self._not_implemented
46
- raise RuntimeError.new("API call not implemented!")
50
+ raise RuntimeError.new("API call not implemented! #{caller[0]}")
47
51
  end
48
52
 
49
53
  def self.add_periodic_timer(*args, &block)
@@ -112,6 +116,10 @@ module ZMachine
112
116
  _not_implemented
113
117
  end
114
118
 
119
+ def self.stop
120
+ Reactor.terminate_all_reactors
121
+ end
122
+
115
123
  def self.run_block(&block)
116
124
  pr = proc {
117
125
  block.call
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+ require 'lib/zmachine/hashed_wheel'
3
+
4
+ describe ZMachine::HashedWheelTimeout do
5
+ it 'calculates the stop index correctly'
6
+ it 'calculates remaining rounds correctly'
7
+ it 'can be cancelled'
8
+ it 'supports an action'
9
+ end
10
+
11
+ describe ZMachine::HashedWheel do
12
+ let(:wheel) { ZMachine::HashedWheel.new(16, 100) }
13
+
14
+ it 'returns a timeout on add' do
15
+ expect(wheel.add(0)).to be_instance_of(ZMachine::HashedWheelTimeout)
16
+ end
17
+
18
+ it 'adds timeouts to the correct slot' do
19
+ wheel.add 0
20
+ wheel.add 90
21
+ wheel.add 110
22
+ wheel.add 1000
23
+ wheel.add 1600
24
+ wheel.add 3200
25
+ expect(wheel.slots[0].length).to eq(4)
26
+ expect(wheel.slots[1].length).to eq(1)
27
+ expect(wheel.slots[10].length).to eq(1)
28
+ end
29
+
30
+ it 'times out same slot timeouts correctly' do
31
+ now = wheel.reset
32
+ wheel.add 10
33
+ wheel.add 50
34
+ timedout = wheel.advance(now + 30 * 1_000_000)
35
+ expect(timedout.length).to eq(1)
36
+ end
37
+
38
+ it 'calculates the timeouted set correctly' do
39
+ now = wheel.reset
40
+ wheel.add 10
41
+ wheel.add 40
42
+ wheel.add 1900
43
+ wheel.add 3300
44
+ wheel.add 4000
45
+ timedout = wheel.advance( now + 3900 * 1_000_000)
46
+ expect(timedout).to be
47
+ expect(timedout.length).to eq(4)
48
+ end
49
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
data/zmachine.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "zmachine"
5
- spec.version = "0.1.1"
6
- spec.authors = ["madvertise Mobile Advertising GmbH"]
7
- spec.email = ["tech@madvertise.com"]
8
- spec.description = %q{pure JRuby multi-threaded EventMachine compatible event loop}
9
- spec.summary = %q{pure JRuby multi-threaded EventMachine compatible event loop}
5
+ spec.version = "0.1.3"
6
+ spec.authors = ["LiquidM, Inc."]
7
+ spec.email = ["tech@liquidm.com"]
8
+ spec.description = %q{pure JRuby multi-threaded mostly EventMachine compatible event loop}
9
+ spec.summary = %q{pure JRuby multi-threaded mostly EventMachine compatible event loop}
10
10
  spec.homepage = "https://github.com/madvertise/zmachine"
11
11
  spec.license = "MIT"
12
12
 
@@ -17,4 +17,5 @@ Gem::Specification.new do |spec|
17
17
 
18
18
  spec.add_development_dependency "bundler", "~> 1.3"
19
19
  spec.add_development_dependency "rake"
20
+ spec.add_development_dependency "rspec"
20
21
  end
metadata CHANGED
@@ -1,51 +1,60 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zmachine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
5
- prerelease:
4
+ version: 0.1.3
6
5
  platform: ruby
7
6
  authors:
8
- - madvertise Mobile Advertising GmbH
9
- autorequire:
7
+ - LiquidM, Inc.
8
+ autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-08-28 00:00:00.000000000 Z
11
+ date: 2013-11-05 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: bundler
16
- version_requirements: !ruby/object:Gem::Requirement
15
+ requirement: !ruby/object:Gem::Requirement
17
16
  requirements:
18
17
  - - ~>
19
18
  - !ruby/object:Gem::Version
20
19
  version: '1.3'
21
- none: false
22
- requirement: !ruby/object:Gem::Requirement
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.3'
27
- none: false
28
- prerelease: false
29
- type: :development
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
32
36
  version_requirements: !ruby/object:Gem::Requirement
33
37
  requirements:
34
- - - '>='
38
+ - - ! '>='
35
39
  - !ruby/object:Gem::Version
36
40
  version: '0'
37
- none: false
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
38
43
  requirement: !ruby/object:Gem::Requirement
39
44
  requirements:
40
- - - '>='
45
+ - - ! '>='
41
46
  - !ruby/object:Gem::Version
42
47
  version: '0'
43
- none: false
44
- prerelease: false
45
48
  type: :development
46
- description: pure JRuby multi-threaded EventMachine compatible event loop
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: pure JRuby multi-threaded mostly EventMachine compatible event loop
47
56
  email:
48
- - tech@madvertise.com
57
+ - tech@liquidm.com
49
58
  executables: []
50
59
  extensions: []
51
60
  extra_rdoc_files: []
@@ -62,36 +71,40 @@ files:
62
71
  - lib/zmachine/channel.rb
63
72
  - lib/zmachine/connection.rb
64
73
  - lib/zmachine/deferrable.rb
74
+ - lib/zmachine/hashed_wheel.rb
65
75
  - lib/zmachine/jeromq-0.3.0-SNAPSHOT.jar
66
76
  - lib/zmachine/reactor.rb
67
77
  - lib/zmachine/tcp_channel.rb
68
78
  - lib/zmachine/timers.rb
69
79
  - lib/zmachine/zmq_channel.rb
80
+ - spec/hashed_wheel_spec.rb
81
+ - spec/spec_helper.rb
70
82
  - zmachine.gemspec
71
83
  homepage: https://github.com/madvertise/zmachine
72
84
  licenses:
73
85
  - MIT
74
- post_install_message:
86
+ metadata: {}
87
+ post_install_message:
75
88
  rdoc_options: []
76
89
  require_paths:
77
90
  - lib
78
91
  required_ruby_version: !ruby/object:Gem::Requirement
79
92
  requirements:
80
- - - '>='
93
+ - - ! '>='
81
94
  - !ruby/object:Gem::Version
82
95
  version: '0'
83
- none: false
84
96
  required_rubygems_version: !ruby/object:Gem::Requirement
85
97
  requirements:
86
- - - '>='
98
+ - - ! '>='
87
99
  - !ruby/object:Gem::Version
88
100
  version: '0'
89
- none: false
90
101
  requirements: []
91
- rubyforge_project:
92
- rubygems_version: 1.8.24
93
- signing_key:
94
- specification_version: 3
95
- summary: pure JRuby multi-threaded EventMachine compatible event loop
96
- test_files: []
97
- has_rdoc:
102
+ rubyforge_project:
103
+ rubygems_version: 2.0.6
104
+ signing_key:
105
+ specification_version: 4
106
+ summary: pure JRuby multi-threaded mostly EventMachine compatible event loop
107
+ test_files:
108
+ - spec/hashed_wheel_spec.rb
109
+ - spec/spec_helper.rb
110
+ has_rdoc: