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,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