zmachine 0.2.1 → 0.3.0

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