zmachine 0.2.1 → 0.3.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.
@@ -19,7 +19,7 @@ module ZMachine
19
19
 
20
20
  def idle?
21
21
  @new_connections.size == 0 and
22
- @zmq_connections.none? {|c| c.channel.has_more? } # see comment in #process
22
+ @zmq_connections.none? {|c| c.channel.can_recv? } # see comment in #process
23
23
  end
24
24
 
25
25
  def shutdown
@@ -30,18 +30,17 @@ module ZMachine
30
30
 
31
31
  def bind(address, port_or_type, handler, *args, &block)
32
32
  ZMachine.logger.debug("zmachine:connection_manager:#{__method__}", address: address, port_or_type: port_or_type) if ZMachine.debug
33
- connection = build_connection(handler, *args, &block)
34
- connection.bind(address, port_or_type)
33
+ connection = build_connection(handler, *args)
34
+ connection.bind(address, port_or_type, &block)
35
35
  @new_connections << connection
36
36
  connection
37
37
  end
38
38
 
39
39
  def connect(address, port_or_type, handler, *args, &block)
40
40
  ZMachine.logger.debug("zmachine:connection_manager:#{__method__}", address: address, port_or_type: port_or_type) if ZMachine.debug
41
- connection = build_connection(handler, *args, &block)
42
- connection.connect(address, port_or_type)
41
+ connection = build_connection(handler, *args)
42
+ connection.connect(address, port_or_type, &block)
43
43
  @new_connections << connection
44
- yield connection if block_given?
45
44
  connection
46
45
  rescue java.nio.channels.UnresolvedAddressException
47
46
  raise ZMachine::ConnectionError.new('unable to resolve server address')
@@ -55,26 +54,30 @@ module ZMachine
55
54
  process_connection(it.next.attachment)
56
55
  it.remove
57
56
  end
58
- # super ugly, but ZMQ only triggers the FD if and only if you have read
59
- # every message from the socket. under load however there will always be
60
- # new messages in the mailbox between last recv and next select, which
61
- # causes the FD never to be triggered again.
62
- # the only mitigation strategy i came up with is iterating over all channels :(
57
+ # super ugly, but ZMQ only triggers the FD if and only if you
58
+ # have read every message from the socket. under load however
59
+ # there will always be new messages in the mailbox between last
60
+ # recv and next select, which causes the FD never to be
61
+ # triggered again.
62
+ # the only mitigation strategy i came up with is iterating over all
63
+ # channels. performance impact shouldn't be too huge, since ZMQ takes
64
+ # care of all the multiplexing and we only have a small amount of ZMQ
65
+ # connections in the reactor
63
66
  @zmq_connections.each do |connection|
64
- connection.readable! if connection.channel.has_more?
67
+ connection.readable! if connection.channel.can_recv?
65
68
  end
66
69
  end
67
70
 
68
71
  def process_connection(connection)
69
72
  new_connection = connection.process_events
70
73
  @new_connections << new_connection if new_connection
71
- rescue IOException
72
- close_connection(connection)
74
+ rescue IOException => e
75
+ close_connection(connection, e)
73
76
  end
74
77
 
75
- def close_connection(connection)
76
- ZMachine.logger.debug("zmachine:connection_manager:#{__method__}", connection: connection) if ZMachine.debug
77
- @unbound_connections << connection
78
+ def close_connection(connection, reason = nil)
79
+ ZMachine.logger.debug("zmachine:connection_manager:#{__method__}", connection: connection, reason: reason.inspect) if ZMachine.debug
80
+ @unbound_connections << [connection, reason]
78
81
  end
79
82
 
80
83
  def add_new_connections
@@ -83,10 +86,12 @@ module ZMachine
83
86
  begin
84
87
  connection.register(@selector)
85
88
  @connections << connection
86
- @zmq_connections << connection if connection.channel.is_a?(ZMQChannel)
89
+ if connection.channel.is_a?(ZMQChannel)
90
+ @zmq_connections << connection
91
+ connection.connection_completed
92
+ end
87
93
  rescue ClosedChannelException => e
88
- ZMachine.logger.exception(e, "failed to add connection")
89
- @unbound_connections << connection
94
+ @unbound_connections << [connection, e]
90
95
  end
91
96
  end
92
97
  @new_connections.clear
@@ -101,7 +106,11 @@ module ZMachine
101
106
  begin
102
107
  @connections.delete(connection)
103
108
  @zmq_connections.delete(connection)
104
- connection.unbind
109
+ if connection.method(:unbind).arity != 0
110
+ connection.unbind(reason)
111
+ else
112
+ connection.unbind
113
+ end
105
114
  connection.close
106
115
  rescue Exception => e
107
116
  ZMachine.logger.exception(e, "failed to unbind connection") if ZMachine.debug
@@ -112,16 +121,16 @@ module ZMachine
112
121
 
113
122
  private
114
123
 
115
- def build_connection(handler, *args, &block)
124
+ def build_connection(handler, *args)
116
125
  if handler and handler.is_a?(Class)
117
- handler.new(*args, &block)
126
+ handler.new(*args)
118
127
  elsif handler and handler.is_a?(Connection)
119
128
  # already initialized connection on reconnect
120
129
  handler
121
130
  elsif handler
122
- connection_from_module(handler).new(*args, &block)
131
+ connection_from_module(handler).new(*args)
123
132
  else
124
- Connection.new(*args, &block)
133
+ Connection.new(*args)
125
134
  end
126
135
  end
127
136
 
@@ -27,13 +27,13 @@ module ZMachine
27
27
 
28
28
  def initialize(number_of_slots, tick_length, start_time = System.nano_time)
29
29
  @slots = Array.new(number_of_slots) { [] }
30
- @tick_length = tick_length * 1_000_000
30
+ @tick_length = tick_length * 1_000_000_000
31
31
  @last = start_time
32
32
  @current_tick = 0
33
33
  end
34
34
 
35
35
  def add(timeout, &block)
36
- timeout *= 1_000_000 # ms to ns
36
+ timeout *= 1_000_000_000 # s to ns
37
37
  ticks = timeout / @tick_length
38
38
  slot = (@current_tick + ticks) % @slots.length
39
39
  HashedWheelTimeout.new(System.nano_time + timeout, &block).tap do |hwt|
@@ -41,14 +41,15 @@ module ZMachine
41
41
  end
42
42
  end
43
43
 
44
- def reset(time = System.nano_time)
44
+ def reset(time = nil)
45
45
  @slots = Array.new(@slots.length) { [] }
46
46
  @current_tick = 0
47
- @last = time
47
+ @last = time || System.nano_time
48
48
  end
49
49
 
50
50
  # returns all timeouts
51
- def advance(now = System.nano_time)
51
+ def advance(now = nil)
52
+ now ||= System.nano_time
52
53
  passed_ticks = (now - @last) / @tick_length
53
54
  result = []
54
55
  begin
@@ -40,7 +40,7 @@ module ZMachine
40
40
  @next_tick_queue = ConcurrentLinkedQueue.new
41
41
  @running = false
42
42
  @shutdown_hooks = []
43
- @wheel = HashedWheel.new(512, 10)
43
+ @wheel = HashedWheel.new(512, 0.01)
44
44
  end
45
45
 
46
46
  def add_shutdown_hook(&block)
@@ -53,7 +53,7 @@ module ZMachine
53
53
  callback = args.shift || block
54
54
  ZMachine.logger.debug("zmachine:reactor:#{__method__}", interval: interval, callback: callback) if ZMachine.debug
55
55
  return unless callback
56
- @wheel.add((interval.to_f * 1000).to_i, &callback)
56
+ @wheel.add(interval, &callback)
57
57
  end
58
58
 
59
59
  def bind(server, port_or_type=nil, handler=nil, *args, &block)
@@ -62,8 +62,9 @@ module ZMachine
62
62
  @connection_manager.bind(server, port_or_type, handler, *args, &block)
63
63
  end
64
64
 
65
- def close_connection(connection)
66
- @connection_manager.close_connection(connection)
65
+ def close_connection(connection, reason = nil)
66
+ return true unless @connection_manager
67
+ @connection_manager.close_connection(connection, reason)
67
68
  end
68
69
 
69
70
  def connect(server, port_or_type=nil, handler=nil, *args, &block)
@@ -129,9 +130,9 @@ module ZMachine
129
130
  def run_reactor
130
131
  ZMachine.logger.debug("zmachine:reactor:#{__method__}") if ZMachine.debug
131
132
  run_deferred_callbacks
132
- break unless @run_reactor
133
+ return unless @run_reactor
133
134
  run_timers
134
- break unless @run_reactor
135
+ return unless @run_reactor
135
136
  @connection_manager.cleanup
136
137
  if @connection_manager.idle?
137
138
  ZMachine.logger.debug("zmachine:reactor:#{__method__}", select: @heartbeat_interval) if ZMachine.debug
@@ -149,6 +150,7 @@ module ZMachine
149
150
 
150
151
  def stop_event_loop
151
152
  @run_reactor = false
153
+ @connection_manager.shutdown
152
154
  wakeup
153
155
  end
154
156
 
@@ -173,7 +175,7 @@ module ZMachine
173
175
  def run_timers
174
176
  ZMachine.logger.debug("zmachine:reactor:#{__method__}") if ZMachine.debug
175
177
  @wheel.advance.each do |timeout|
176
- ZMachine.logger.debug("zmachine:reactor:#{__method__}", callback: timeout.callback) if ZMachine.debug
178
+ ZMachine.logger.info("zmachine:reactor:#{__method__}", callback: timeout.callback) if ZMachine.debug
177
179
  timeout.callback.call
178
180
  end
179
181
  end
@@ -13,6 +13,7 @@ module ZMachine
13
13
  end
14
14
 
15
15
  def bind(address, port)
16
+ ZMachine.logger.debug("zmachine:tcp_channel:#{__method__}", channel: self) if ZMachine.debug
16
17
  address = InetSocketAddress.new(address, port)
17
18
  @socket = ServerSocketChannel.open
18
19
  @socket.configure_blocking(false)
@@ -20,10 +21,11 @@ module ZMachine
20
21
  end
21
22
 
22
23
  def bound?
23
- @socket.is_a?(ServerSocketChannel) and @socket.bound?
24
+ @socket.is_a?(ServerSocketChannel) && @socket.bound?
24
25
  end
25
26
 
26
27
  def accept
28
+ ZMachine.logger.debug("zmachine:tcp_channel:#{__method__}", channel: self) if ZMachine.debug
27
29
  client_socket = @socket.accept
28
30
  return unless client_socket
29
31
  client_socket.configure_blocking(false)
@@ -33,6 +35,7 @@ module ZMachine
33
35
  end
34
36
 
35
37
  def connect(address, port)
38
+ ZMachine.logger.debug("zmachine:tcp_channel:#{__method__}", channel: self) if ZMachine.debug
36
39
  address = InetSocketAddress.new(address, port)
37
40
  @socket = SocketChannel.open
38
41
  @socket.configure_blocking(false)
@@ -51,9 +54,9 @@ module ZMachine
51
54
  end
52
55
 
53
56
  def finish_connecting
57
+ ZMachine.logger.debug("zmachine:tcp_channel:#{__method__}", channel: self) if ZMachine.debug
54
58
  return unless connection_pending?
55
- @socket.finish_connect # XXX: finish_connect might return false
56
- return true
59
+ @socket.finish_connect
57
60
  end
58
61
 
59
62
  def connected?
@@ -61,50 +64,20 @@ module ZMachine
61
64
  end
62
65
 
63
66
  def read_inbound_data
67
+ ZMachine.logger.debug("zmachine:tcp_channel:#{__method__}", channel: self) if ZMachine.debug
64
68
  buffer = @inbound_buffer
65
69
  buffer.clear
66
70
  raise IOException.new("EOF") if @socket.read(buffer) == -1
67
71
  buffer.flip
68
72
  return if buffer.limit == 0
69
- String.from_java_bytes(buffer.array[buffer.position...buffer.limit])
73
+ data = buffer.array[buffer.position...buffer.limit]
74
+ data = String.from_java_bytes(data) unless @raw
75
+ data
70
76
  end
71
77
 
72
- def send_data(data)
73
- raise RuntimeError.new("send_data called after close") if @close_scheduled
74
- return unless data
75
- data = data.to_java_bytes if data.is_a?(String) # EM compat
76
- buffer = ByteBuffer.wrap(data)
77
- if buffer.has_remaining
78
- @outbound_queue << buffer
79
- end
80
- end
81
-
82
- def write_outbound_data
83
- while can_send?
84
- buffer = @outbound_queue.first
85
- @socket.write(buffer) if buffer.has_remaining
86
- # Did we consume the whole outbound buffer? If yes,
87
- # pop it off and keep looping. If no, the outbound network
88
- # buffers are full, so break out of here.
89
- if buffer.remaining == 0
90
- @outbound_queue.shift
91
- else
92
- break
93
- end
94
- end
95
-
96
- if can_send?
97
- # network buffers are full
98
- return false
99
- end
100
-
101
- close if @close_scheduled
102
- return true
103
- end
104
-
105
- def close(after_writing = false)
106
- super
107
- @socket.close unless can_send?
78
+ def close!
79
+ ZMachine.logger.debug("zmachine:tcp_channel:#{__method__}", channel: self) if ZMachine.debug
80
+ @socket.close
108
81
  end
109
82
 
110
83
  def closed?
@@ -1,61 +1,33 @@
1
1
  module ZMachine
2
- # Creates a one-time timer
3
- #
4
- # timer = ZMachine::Timer.new(5) do
5
- # # this will never fire because we cancel it
6
- # end
7
- # timer.cancel
8
- #
9
2
  class Timer
10
- # Create a new timer that fires after a given number of seconds
11
- def initialize(interval, callback=nil, &block)
12
- @signature = ZMachine.add_timer(interval, callback || block)
13
- end
14
3
 
15
- # Cancel the timer
16
- def cancel
17
- ZMachine.cancel_timer(@signature)
18
- end
19
- end
4
+ attr_accessor :interval
20
5
 
21
- # Creates a periodic timer
22
- #
23
- # @example
24
- # n = 0
25
- # timer = ZMachine::PeriodicTimer.new(5) do
26
- # puts "the time is #{Time.now}"
27
- # timer.cancel if (n+=1) > 5
28
- # end
29
- #
30
- class PeriodicTimer
31
- # Create a new periodic timer that executes every interval seconds
32
6
  def initialize(interval, callback=nil, &block)
33
7
  @interval = interval
34
- @code = callback || block
35
- @cancelled = false
36
- @work = method(:fire)
8
+ @callback = callback || block
37
9
  schedule
38
10
  end
39
11
 
40
- # Cancel the periodic timer
41
- def cancel
42
- @cancelled = true
12
+ def schedule
13
+ @timer = ZMachine.add_timer(@interval, method(:fire))
43
14
  end
44
15
 
45
- # Fire the timer every interval seconds
46
- attr_accessor :interval
16
+ def fire
17
+ @callback.call
18
+ end
47
19
 
48
- # @private
49
- def schedule
50
- ZMachine.add_timer(@interval, @work)
20
+ def cancel
21
+ @timer.cancel
51
22
  end
23
+ end
24
+
25
+ class PeriodicTimer < Timer
52
26
 
53
- # @private
54
27
  def fire
55
- unless @cancelled
56
- @code.call
57
- schedule
58
- end
28
+ super
29
+ schedule
59
30
  end
31
+
60
32
  end
61
33
  end
@@ -1,4 +1,5 @@
1
- require 'zmachine/jeromq-0.3.0-SNAPSHOT.jar'
1
+ require 'zmachine/jeromq-0.3.2-SNAPSHOT.jar'
2
+ java_import org.zeromq.ZMsg
2
3
  java_import org.zeromq.ZMQ
3
4
  java_import org.zeromq.ZMQException
4
5
 
@@ -9,35 +10,39 @@ class ZMQ
9
10
  class Socket
10
11
  # for performance reason we alias the method here (otherwise it uses reflections all the time!)
11
12
  # super ugly, since we need to dynamically infer the java class of byte[]
13
+ java_alias :send_byte_buffer, :sendByteBuffer, [Java::JavaNio::ByteBuffer.java_class, Java::int]
12
14
  java_alias :send_byte_array, :send, [[].to_java(:byte).java_class, Java::int]
13
15
  java_alias :recv_byte_array, :recv, [Java::int]
16
+
17
+ def write(buffer)
18
+ bytes = send_byte_buffer(buffer, 0)
19
+ buffer.position(buffer.position + bytes)
20
+ end
14
21
  end
15
22
  end
16
23
 
17
24
  module ZMachine
18
25
  class ZMQChannel < Channel
19
26
 
20
- def initialize(type)
21
- super()
22
- @socket = ZMachine.context.create_socket(type)
23
- @bound = false
24
- @connected = false
25
- @closed = false
26
- end
27
+ extend Forwardable
27
28
 
28
- def identity=(v)
29
- @socket.identity = v if @socket
30
- end
31
- def identity
32
- @socket ? @socket.identity : nil
29
+ def_delegator :@socket, :identity
30
+ def_delegator :@socket, :identity=
31
+
32
+ def initialize
33
+ super
34
+ @raw = true
33
35
  end
34
36
 
35
37
  def selectable_fd
36
38
  @socket.fd
37
39
  end
38
40
 
39
- def bind(address, port = nil)
41
+ def bind(address, type)
42
+ ZMachine.logger.debug("zmachine:zmq_channel:#{__method__}", channel: self) if ZMachine.debug
40
43
  @bound = true
44
+ @connected = true
45
+ @socket = ZMachine.context.create_socket(type)
41
46
  @socket.bind(address)
42
47
  end
43
48
 
@@ -45,13 +50,26 @@ module ZMachine
45
50
  @bound
46
51
  end
47
52
 
48
- def connect(address)
49
- @connected = true
53
+ def accept
54
+ ZMachine.logger.debug("zmachine:zmq_channel:#{__method__}", channel: self) if ZMachine.debug
55
+ self
56
+ end
57
+
58
+ def connect(address, type)
59
+ ZMachine.logger.debug("zmachine:zmq_channel:#{__method__}", channel: self) if ZMachine.debug
60
+ @connection_pending = true
61
+ @socket = ZMachine.context.create_socket(type)
50
62
  @socket.connect(address)
51
63
  end
52
64
 
53
65
  def connection_pending?
54
- false
66
+ @connection_pending
67
+ end
68
+
69
+ def finish_connecting
70
+ ZMachine.logger.debug("zmachine:zmq_channel:#{__method__}", channel: self) if ZMachine.debug
71
+ return unless connection_pending?
72
+ @connected = true
55
73
  end
56
74
 
57
75
  def connected?
@@ -59,71 +77,40 @@ module ZMachine
59
77
  end
60
78
 
61
79
  def read_inbound_data
62
- data = [@socket.recv_byte_array(0)]
63
- while @socket.hasReceiveMore
64
- data << @socket.recv_byte_array(0)
65
- end
80
+ ZMachine.logger.debug("zmachine:zmq_channel:#{__method__}", channel: self) if ZMachine.debug
81
+ data = ZMsg.recv_msg(@socket)
82
+ data = String.from_java_bytes(data.first.data) unless @raw
66
83
  data
67
84
  end
68
85
 
69
- def send_data(data)
70
- parts, last = data[0..-2], data.last
71
- parts.each do |part|
72
- @socket.send_byte_array(part, ZMQ::SNDMORE | ZMQ::DONTWAIT)
73
- end
74
- @socket.send_byte_array(last, ZMQ::DONTWAIT)
75
- rescue ZMQException
76
- @outbound_queue << data
77
- end
78
-
79
- # to get around iterating over an array in #send_data we pass message parts
80
- # as arguments
81
86
  def send1(a)
82
87
  @socket.send_byte_array(a, ZMQ::DONTWAIT)
83
88
  end
84
89
 
85
90
  def send2(a, b)
86
- @socket.send_byte_array(a, ZMQ::SNDMORE | ZMQ::DONTWAIT)
91
+ @socket.send_byte_array(a, ZMQ::SNDMORE | ZMQ::DONTWAIT) and
87
92
  @socket.send_byte_array(b, ZMQ::DONTWAIT)
88
93
  end
89
94
 
90
95
  def send3(a, b, c)
91
- @socket.send_byte_array(a, ZMQ::SNDMORE | ZMQ::DONTWAIT)
92
- @socket.send_byte_array(b, ZMQ::SNDMORE | ZMQ::DONTWAIT)
96
+ @socket.send_byte_array(a, ZMQ::SNDMORE | ZMQ::DONTWAIT) and
97
+ @socket.send_byte_array(b, ZMQ::SNDMORE | ZMQ::DONTWAIT) and
93
98
  @socket.send_byte_array(c, ZMQ::DONTWAIT)
94
99
  end
95
100
 
96
101
  def send4(a, b, c, d)
97
- @socket.send_byte_array(a, ZMQ::SNDMORE | ZMQ::DONTWAIT)
98
- @socket.send_byte_array(b, ZMQ::SNDMORE | ZMQ::DONTWAIT)
99
- @socket.send_byte_array(c, ZMQ::SNDMORE | ZMQ::DONTWAIT)
102
+ @socket.send_byte_array(a, ZMQ::SNDMORE | ZMQ::DONTWAIT) and
103
+ @socket.send_byte_array(b, ZMQ::SNDMORE | ZMQ::DONTWAIT) and
104
+ @socket.send_byte_array(c, ZMQ::SNDMORE | ZMQ::DONTWAIT) and
100
105
  @socket.send_byte_array(d, ZMQ::DONTWAIT)
101
106
  end
102
107
 
103
- def has_more?
104
- @socket.events & ZMQ::Poller::POLLIN == ZMQ::Poller::POLLIN
105
- end
106
-
107
- def can_send?
108
- super and (@socket.events & ZMQ::Poller::POLLOUT == ZMQ::Poller::POLLOUT)
109
- end
110
-
111
- def write_outbound_data
112
- while can_send?
113
- data = @outbound_queue.shift
114
- send_data(data)
115
- end
116
-
117
- close if @close_scheduled
118
- return true
119
- end
120
-
121
- def close(after_writing = false)
122
- super
108
+ def close!
109
+ ZMachine.logger.debug("zmachine:zmq_channel:#{__method__}", channel: self) if ZMachine.debug
123
110
  @closed = true
124
111
  @connected = false
125
112
  @bound = false
126
- ZMachine.context.destroySocket(@socket) unless can_send?
113
+ ZMachine.context.destroySocket(@socket)
127
114
  end
128
115
 
129
116
  def closed?
@@ -134,5 +121,10 @@ module ZMachine
134
121
  raise RuntimeError.new("ZMQChannel has no peer")
135
122
  end
136
123
 
124
+ # see comment in ConnectionManager#process
125
+ def can_recv?
126
+ @socket.events & ZMQ::Poller::POLLIN == ZMQ::Poller::POLLIN
127
+ end
128
+
137
129
  end
138
130
  end