zmachine 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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: