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,18 +1,27 @@
1
+ java_import java.lang.System
2
+
1
3
  module ZMachine
2
4
 
3
5
  class HashedWheelTimeout
4
- attr_accessor :round
5
6
  attr_reader :deadline
6
7
  attr_reader :callback
7
- def initialize(round, deadline, &block)
8
- @round = round
8
+
9
+ def initialize(deadline, &block)
9
10
  @deadline = deadline
10
11
  @callback = block
12
+ @canceled = false
13
+ end
14
+
15
+ def cancel
16
+ @canceled = true
17
+ end
18
+
19
+ def canceled?
20
+ @canceled
11
21
  end
12
22
  end
13
23
 
14
24
  class HashedWheel
15
-
16
25
  attr_reader :slots
17
26
  attr_accessor :last
18
27
 
@@ -21,26 +30,15 @@ module ZMachine
21
30
  @tick_length = tick_length * 1_000_000
22
31
  @last = start_time
23
32
  @current_tick = 0
24
-
25
- @next = nil
26
- end
27
-
28
- def next_deadline
29
- return Float::INFINITY if !@next
30
- @next.deadline
31
33
  end
32
34
 
33
35
  def add(timeout, &block)
34
36
  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
37
+ ticks = timeout / @tick_length
38
+ slot = (@current_tick + ticks) % @slots.length
39
+ HashedWheelTimeout.new(System.nano_time + timeout, &block).tap do |hwt|
40
+ @slots[slot] << hwt
42
41
  end
43
- hwt
44
42
  end
45
43
 
46
44
  def reset(time = System.nano_time)
@@ -51,27 +49,21 @@ module ZMachine
51
49
 
52
50
  # returns all timeouts
53
51
  def advance(now = System.nano_time)
54
- # how many tickts have passed?
55
52
  passed_ticks = (now - @last) / @tick_length
56
- round = 0
57
53
  result = []
58
54
  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
55
+ @current_tick %= @slots.length
56
+ @slots[@current_tick].delete_if do |timeout|
57
+ result << timeout if timeout.deadline < now
65
58
  end
66
59
  @current_tick += 1
67
60
  passed_ticks -= 1
68
61
  end while passed_ticks > 0
69
62
  @last = now
70
- result
63
+ result.reject do |timeout|
64
+ timeout.canceled?
65
+ end
71
66
  end
72
67
 
73
68
  end
74
69
  end
75
-
76
-
77
-
@@ -1,32 +1,14 @@
1
1
  java_import java.lang.System
2
- java_import java.io.FileDescriptor
3
- java_import java.io.IOException
4
- java_import java.net.InetSocketAddress
5
- java_import java.nio.ByteBuffer
6
- java_import java.nio.channels.ClosedChannelException
7
- java_import java.nio.channels.SelectionKey
8
2
  java_import java.nio.channels.Selector
9
- java_import java.nio.channels.ServerSocketChannel
10
- java_import java.nio.channels.SocketChannel
11
- java_import java.util.TreeMap
12
- java_import java.util.concurrent.atomic.AtomicBoolean
13
3
  java_import java.util.concurrent.ConcurrentLinkedQueue
14
4
 
15
- require 'zmachine/jeromq-0.3.0-SNAPSHOT.jar'
16
- java_import org.zeromq.ZContext
17
-
18
- require 'zmachine/acceptor'
19
- require 'zmachine/tcp_channel'
20
- require 'zmachine/zmq_channel'
21
5
  require 'zmachine/hashed_wheel'
6
+ require 'zmachine/connection_manager'
22
7
 
23
8
  module ZMachine
24
9
 
25
- class NotReactorOwner < Exception
26
- end
27
-
28
- class NoReactorError < Exception
29
- end
10
+ class NotReactorOwner < Exception; end
11
+ class NoReactorError < Exception; end
30
12
 
31
13
  class Reactor
32
14
 
@@ -40,7 +22,6 @@ module ZMachine
40
22
  end
41
23
 
42
24
  def self.terminate_all_reactors
43
- # should have a lock ....
44
25
  @mutex.synchronize do
45
26
  @reactors.each(&:stop_event_loop)
46
27
  @reactors.clear
@@ -54,186 +35,121 @@ module ZMachine
54
35
  end
55
36
 
56
37
  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
61
- @timers = TreeMap.new
62
- @timer_callbacks = {}
63
- @channels = []
64
- @new_channels = []
65
- @unbound_channels = []
66
- @next_signature = 0
67
- @shutdown_hooks = []
38
+ # a 10 ms tick wheel with 512 slots => ~5s for a round
39
+ @heartbeat_interval = 0.5 # coarse grained by default
68
40
  @next_tick_queue = ConcurrentLinkedQueue.new
69
41
  @running = false
70
-
71
- # don't use a direct buffer. Ruby doesn't seem to like them.
72
- @read_buffer = ByteBuffer.allocate(32*1024)
42
+ @shutdown_hooks = []
43
+ @wheel = HashedWheel.new(512, 10)
73
44
  end
74
45
 
75
46
  def add_shutdown_hook(&block)
76
47
  @shutdown_hooks << block
77
48
  end
78
49
 
79
- def xadd_timer(*args, &block)
50
+ def add_timer(*args, &block)
80
51
  check_reactor_thread
81
52
  interval = args.shift
82
53
  callback = args.shift || block
54
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}", interval: interval, callback: callback) if ZMachine.debug
83
55
  return unless callback
84
56
  @wheel.add((interval.to_f * 1000).to_i, &callback)
85
57
  end
86
58
 
87
-
88
- def add_timer(*args, &block)
59
+ def bind(server, port_or_type=nil, handler=nil, *args, &block)
60
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}", server: server, port_or_type: port_or_type) if ZMachine.debug
89
61
  check_reactor_thread
90
- interval = args.shift
91
- callback = args.shift || block
92
- return unless callback
93
-
94
- signature = next_signature
95
- deadline = System.nano_time + (interval.to_f * 1000_000_000).to_i
96
-
97
- if @timers.contains_key(deadline)
98
- @timers.get(deadline) << signature
99
- else
100
- @timers.put(deadline, [signature])
101
- end
102
-
103
- ZMachine.logger.debug("zmachine:#{__method__}", signature: signature) if ZMachine.debug
104
- @timer_callbacks[signature] = callback
105
- signature
106
- end
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
62
+ @connection_manager.bind(server, port_or_type, handler, *args, &block)
112
63
  end
113
64
 
114
- def cancel_timer(timer_or_sig)
115
- if timer_or_sig.respond_to?(:cancel)
116
- timer_or_sig.cancel
117
- else
118
- ZMachine.logger.debug("zmachine:#{__method__}", signature: timer_or_sig) if ZMachine.debug
119
- @timer_callbacks[timer_or_sig] = false if @timer_callbacks.has_key?(timer_or_sig)
120
- end
65
+ def close_connection(connection)
66
+ @connection_manager.close_connection(connection)
121
67
  end
122
68
 
123
69
  def connect(server, port_or_type=nil, handler=nil, *args, &block)
124
- ZMachine.logger.debug("zmachine:#{__method__}", server: server, port_or_type: port_or_type) if ZMachine.debug
70
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}", server: server, port_or_type: port_or_type) if ZMachine.debug
125
71
  check_reactor_thread
126
- if server.nil? or server =~ %r{\w+://}
127
- _connect_zmq(server, port_or_type, handler, *args, &block)
128
- else
129
- _connect_tcp(server, port_or_type, handler, *args, &block)
130
- end
131
- rescue java.nio.channels.UnresolvedAddressException
132
- raise ZMachine::ConnectionError.new('unable to resolve server address')
72
+ @connection_manager.connect(server, port_or_type, handler, *args, &block)
133
73
  end
134
74
 
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
75
+ def connections
76
+ @connection_manager.connections
140
77
  end
141
78
 
142
- def connection_count
143
- @channels.size
79
+ def heartbeat_interval
80
+ @heartbeat_interval
144
81
  end
145
82
 
146
- def error_handler(callback = nil, &block)
147
- @error_handler = callback || block
83
+ def heartbeat_interval=(value)
84
+ value = 0.01 if value < 0.01
85
+ @heartbeat_interval = value
148
86
  end
149
87
 
150
88
  def next_tick(callback=nil, &block)
151
89
  @next_tick_queue << (callback || block)
152
- signal_loopbreak if reactor_running?
90
+ wakeup if running?
153
91
  end
154
92
 
155
- def run(callback=nil, shutdown_hook=nil, &block)
156
- ZMachine.logger.debug("zmachine:#{__method__}") if ZMachine.debug
157
-
158
- @callback = callback || block
93
+ def reconnect(server, port_or_type, handler)
94
+ return if handler && handler.channel.is_a?(ZMQChannel)
95
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}", server: server, port_or_type: port_or_type) if ZMachine.debug
96
+ @connection_manager.connect(server, port_or_type, handler)
97
+ end
159
98
 
99
+ def run(callback=nil, shutdown_hook=nil, &block)
100
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}") if ZMachine.debug
160
101
  add_shutdown_hook(shutdown_hook) if shutdown_hook
161
-
162
102
  begin
163
- # list of active reactors
164
103
  Reactor.register_reactor(self)
165
-
166
104
  @running = true
167
-
168
- if @callback
169
- add_timer(0) do
170
- @callback.call(self)
171
- end
105
+ if callback = (callback || block)
106
+ add_timer(0) { callback.call(self) }
172
107
  end
173
-
174
108
  @selector = Selector.open
109
+ @connection_manager = ConnectionManager.new(@selector)
175
110
  @run_reactor = true
176
-
177
- while @run_reactor
178
- ZMachine.logger.debug("zmachine:#{__method__}", run_reactor: true) if ZMachine.debug
179
- run_deferred_callbacks
180
- break unless @run_reactor
181
- run_timers
182
- break unless @run_reactor
183
- # advance_timer_wheel
184
- # break unless @run_reactor
185
- remove_timed_out_channels
186
- remove_unbound_channels
187
- check_io
188
- add_new_channels
189
- process_io
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
111
+ run_reactor while @run_reactor
197
112
  ensure
198
- ZMachine.logger.debug("zmachine:#{__method__}", stop: :selector) if ZMachine.debug
113
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}", stop: :selector) if ZMachine.debug
199
114
  @selector.close rescue nil
200
115
  @selector = nil
201
- ZMachine.logger.debug("zmachine:#{__method__}", stop: :channels) if ZMachine.debug
202
- @unbound_channels += @channels
203
- remove_unbound_channels
204
- ZMachine.logger.debug("zmachine:#{__method__}", stop: :shutdown_hooks) if ZMachine.debug
116
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}", stop: :connections) if ZMachine.debug
117
+ @connection_manager.shutdown
118
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}", stop: :shutdown_hooks) if ZMachine.debug
205
119
  @shutdown_hooks.pop.call until @shutdown_hooks.empty?
206
120
  @next_tick_queue = ConcurrentLinkedQueue.new
207
121
  @running = false
208
122
  Reactor.unregister_reactor(self)
209
- ZMachine.logger.debug("zmachine:#{__method__}", stop: :zcontext) if ZMachine.debug
123
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}", stop: :zcontext) if ZMachine.debug
210
124
  ZMachine.context.destroy
211
125
  ZMachine.clear_current_reactor
212
126
  end
213
127
  end
214
128
 
215
- def advance_timer_wheel
216
- @wheel.advance.each do |timeout|
217
- timeout.callback.call
129
+ def run_reactor
130
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}") if ZMachine.debug
131
+ run_deferred_callbacks
132
+ break unless @run_reactor
133
+ run_timers
134
+ break unless @run_reactor
135
+ @connection_manager.cleanup
136
+ if @connection_manager.idle?
137
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}", select: @heartbeat_interval) if ZMachine.debug
138
+ @selector.select(@heartbeat_interval * 1000)
139
+ else
140
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}", select: :now) if ZMachine.debug
141
+ @selector.select_now
218
142
  end
143
+ @connection_manager.process
219
144
  end
220
145
 
221
- def reactor_running?
222
- @running || false
223
- end
224
-
225
- def start_server(server, port_or_type=nil, handler=nil, *args, &block)
226
- ZMachine.logger.debug("zmachine:#{__method__}", server: server, port_or_type: port_or_type) if ZMachine.debug
227
- if server =~ %r{\w+://}
228
- _bind_zmq(server, port_or_type, handler, *args, &block)
229
- else
230
- _bind_tcp(server, port_or_type, handler, *args, &block)
231
- end
146
+ def running?
147
+ @running
232
148
  end
233
149
 
234
150
  def stop_event_loop
235
151
  @run_reactor = false
236
- signal_loopbreak
152
+ wakeup
237
153
  end
238
154
 
239
155
  def stop_server(channel)
@@ -247,268 +163,25 @@ module ZMachine
247
163
  raise NotReactorOwner if Thread.current[:reactor] != self
248
164
  end
249
165
 
250
-
251
- def _bind_tcp(address, port, handler, *args, &block)
252
- klass = _klass_from_handler(Connection, handler)
253
- channel = TCPChannel.new(@selector)
254
- channel.bind(address, port)
255
- add_channel(channel, Acceptor, klass, *(args+[block]) )
256
- channel.handler
257
- end
258
-
259
- def _bind_zmq(address, type, handler, *args, &block)
260
- klass = _klass_from_handler(Connection, handler)
261
- channel = ZMQChannel.new(type, @selector)
262
- channel.bind(address)
263
- add_channel(channel, klass, *args, &block)
264
- channel.handler
265
- end
266
-
267
- def _connect_tcp(address, port, handler=nil, *args, &block)
268
- klass = _klass_from_handler(Connection, handler)
269
- channel = TCPChannel.new(@selector)
270
- channel.connect(address, port)
271
- channel.connect_pending = true
272
- add_channel(channel, klass, *args, &block)
273
- channel.mark_active!
274
- channel.handler
275
- end
276
-
277
- def _connect_zmq(address, type, handler=nil, *args, &block)
278
- klass = _klass_from_handler(Connection, handler)
279
- channel = ZMQChannel.new(type, @selector)
280
- add_channel(channel, klass, *args, &block)
281
- channel.connect(address)
282
- channel.handler.connection_completed
283
- channel.handler
284
- end
285
-
286
- def check_io
287
-
288
- now = System.nano_time
289
-
290
- if @new_channels.size > 0
291
- timeout = -1
292
- elsif !@timers.empty?
293
- timer_key = @timers.first_key
294
- timeout = (timer_key - now)
295
- timeout = -1 if timeout <= 0
296
- else
297
- # puts "hb = #{@heartbeat_interval}"
298
- #timeout = (@heartbeat_interval - (now - @last)) / 1_000_000
299
- timeout = 0
300
- end
301
-
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
315
- end
316
-
317
- ZMachine.logger.debug("zmachine:#{__method__}", timeout: timeout) if ZMachine.debug
318
-
319
- if timeout < 0
320
- @selector.select_now
321
- else
322
- @selector.select(timeout)
323
- end
324
- end
325
-
326
- def process_io
327
- it = @selector.selected_keys.iterator
328
-
329
- while it.has_next
330
- selected_key = it.next
331
- it.remove
332
- if selected_key.connectable?
333
- is_connectable(selected_key.attachment)
334
- elsif selected_key.acceptable?
335
- is_acceptable(selected_key.attachment)
336
- else
337
- is_writable(selected_key.attachment) if selected_key.writable?
338
- is_readable(selected_key.attachment) if selected_key.readable?
339
- end
340
- end
341
-
342
- @channels.each do |channel|
343
- is_readable(channel) if channel.has_more?
344
- is_writable(channel) if channel.can_send?
345
- end
346
- end
347
-
348
- def is_acceptable(channel)
349
- ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
350
- client_channel = channel.accept
351
- acceptor = channel.handler
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
357
- channel.close
358
- end
359
-
360
- def is_readable(channel)
361
- ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
362
- channel.mark_active!
363
- data = channel.read_inbound_data(@read_buffer)
364
- channel.handler.receive_data(data) if data
365
- rescue IOException => e
366
- ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, e:e.message) if ZMachine.debug
367
- @unbound_channels << channel
368
- end
369
-
370
- def is_writable(channel)
371
- ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
372
- channel.mark_active!
373
- @unbound_channels << channel unless channel.write_outbound_data
374
- rescue IOException => e
375
- ZMachine.logger.debug("zmachine:#{__method__} unbind", channel: channel, e:e.message) if ZMachine.debug
376
- @unbound_channels << channel
377
- end
378
-
379
- def is_connectable(channel)
380
- channel.mark_active!
381
- ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
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
391
- @unbound_channels << channel
392
- end
393
-
394
- def add_channel(channel, klass_or_instance, *args, &block)
395
- ZMachine.logger.debug("zmachine:#{__method__}", channel: channel) if ZMachine.debug
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
404
- @new_channels << channel
405
- block.call(channel.handler) if block
406
- channel
407
- rescue => e
408
- puts "ERROR adding channel #{e.message}"
409
- puts e.backtrace
410
- end
411
-
412
- def add_new_channels
413
- @new_channels.each do |channel|
414
- begin
415
- channel.register
416
- @channels << channel
417
- rescue ClosedChannelException => e
418
- puts "UNBIND on add_channel #{e.message}"
419
- @unbound_channels << channel
420
- end
421
- end
422
- @new_channels.clear
423
- end
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
-
438
- def remove_unbound_channels
439
- return if @unbound_channels.empty?
440
- @unbound_channels.each do |channel|
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
451
- end
452
- @unbound_channels.clear
453
- end
454
-
455
166
  def run_deferred_callbacks
456
- # max is current size
457
- size = @next_tick_queue.size
458
- while size > 0 && callback = @next_tick_queue.poll
459
- begin
460
- size -= 1
461
- callback.call
462
- # ensure
463
- # ZMachine.next_tick {} if $!
464
- end
167
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}") if ZMachine.debug
168
+ while callback = @next_tick_queue.poll
169
+ callback.call
465
170
  end
466
171
  end
467
172
 
468
- # TODO : we should definitly optimize periodic timers ... right now they are wasting a hell of cycle es they are recreated on every invocation
469
173
  def run_timers
470
- now = System.nano_time
471
- until @timers.empty?
472
- timer_key = @timers.first_key
473
- break if timer_key > now
474
- signatures = @timers.get(timer_key)
475
- @timers.remove(timer_key)
476
- # Fire all timers at this timestamp
477
- signatures.each do |signature|
478
- callback = @timer_callbacks.delete(signature)
479
- return if callback == false # callback cancelled
480
- callback or raise UnknownTimerFired, "callback data: #{signature}"
481
- callback.call
482
- end
174
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}") if ZMachine.debug
175
+ @wheel.advance.each do |timeout|
176
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}", callback: timeout.callback) if ZMachine.debug
177
+ timeout.callback.call
483
178
  end
484
179
  end
485
180
 
486
- def signal_loopbreak
181
+ def wakeup
182
+ ZMachine.logger.debug("zmachine:reactor:#{__method__}") if ZMachine.debug
487
183
  @selector.wakeup if @selector
488
184
  end
489
185
 
490
- def next_signature
491
- @next_signature += 1
492
- end
493
-
494
- def _klass_from_handler(klass = Connection, handler = nil)
495
- if handler and handler.is_a?(Class)
496
- handler
497
- elsif handler and handler.is_a?(Connection)
498
- # can happen on reconnect
499
- handler
500
- elsif handler
501
- _handler_from_module(klass, handler)
502
- else
503
- klass
504
- end
505
- end
506
-
507
- def _handler_from_module(klass, handler)
508
- handler::CONNECTION_CLASS
509
- rescue NameError
510
- handler::const_set(:CONNECTION_CLASS, Class.new(klass) { include handler })
511
- end
512
-
513
186
  end
514
187
  end