zmqmachine 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,51 @@
1
+ == 0.5.0 / 2011-03-3
2
+ * Changed the constructor for the ZM::Reactor class. It now takes
3
+ an optional third hash argument. One of the keys that it uses
4
+ is :zeromq_context. This allows a user to create a master 0mq
5
+ context and share it with multiple reactors. The point of that is
6
+ to allow the use of the :inproc transport. That transport is
7
+ somewhat misnamed since it only allows "inprocess" communication
8
+ between sockets created by the same context. I needed :inproc to
9
+ work around a 0mq memory leak (still unfixed) and discovered this
10
+ issue.
11
+ A second key is :log_transport which is used by the logging
12
+ facility.
13
+ In the future, the first two arguments to ZM::Reactor will likely
14
+ be rolled into the hash argument. That will be a backwards-
15
+ incompatible change, so I'm hoping folks read these notes and
16
+ start preparing.
17
+ * Many fixes to ZM::Timers. Cancellation is more intelligent about
18
+ matching timers. Inspection of timers also provides better detail
19
+ about their contents. Lastly, expired timer removal is much clearer
20
+ in how it functions *and* it is faster by an order of magnitude.
21
+ (Note: Array#shift is *way faster* than Array#delete_at(0)).
22
+ * Timer expiration has changed. Originally, timers fired only when
23
+ the current time was *greater than* its firing time. Now timers
24
+ fire when the current time is *greater than or equal to* its firing
25
+ time.
26
+ * Modified how arguments are passed to the Forwarder and Queue
27
+ devices.
28
+ * Devices now default to a ZMQ::LINGER of 0 (drop messages
29
+ immediately upon socket close) and a ZMQ::HWM of 1 (only allow
30
+ 1 outstanding message between senders and receivers). These
31
+ parameters can be modified using the new argument hash.
32
+ * ZM::Address now supports the :inproc transport.
33
+ * Fixed a memory leak where a message was not released when an
34
+ internal recv operation returned EAGAIN. The code was leaking an
35
+ empty ZMQ::Message every time.
36
+ * Added a facility for logging. When a Reactor is instantiated a
37
+ user may pass in a transport string that indicates where the
38
+ reactor may *connect* and publish log messages. Part of the
39
+ contract is that this transport already has a listener bound
40
+ to it before the connection is attempted (e.g. for inproc
41
+ transports). If inproc is used, then the user also needs to
42
+ pass in the shared context via :zeromq_context.
43
+ * Fixed a bug in Socket#send_messages where EAGAIN was not always
44
+ properly detected and bubbled up to its caller.
45
+ * Refactored socket deletion. Hopefully it is easier to understand.
46
+
47
+ == 0.4.1 / Unreleased
48
+
1
49
  == 0.4.0 / 2010-12-16
2
50
  * Replaced SortedSet in Timers with an Array and a custom routine
3
51
  for finding the in-order index for an insert. The routine
data/lib/zm/address.rb CHANGED
@@ -50,7 +50,12 @@ module ZMQMachine
50
50
  end
51
51
 
52
52
  def to_s
53
- "#{@transport}://#{@host}:#{@port}"
53
+ case @transport
54
+ when :tcp
55
+ "#{@transport}://#{@host}:#{@port}"
56
+ else
57
+ "#{@transport}://#{@host}"
58
+ end
54
59
  end
55
60
 
56
61
 
@@ -63,22 +68,22 @@ module ZMQMachine
63
68
  # should also return nil or some other error indication when parsing fails
64
69
  split = string.split(':')
65
70
  type = split[0]
66
- port = split[2]
67
- host = split[1].sub('//', '')
71
+ port = split[2] # nil for ipc/inproc and non-empty for tcp
72
+ host = split[1].slice(2, split[1].length) #sub('//', '')
68
73
 
69
- Address.new host, port, type.to_sym
74
+ Address.new host, port, type.downcase.to_sym
70
75
  end
71
76
 
72
77
  private
73
78
 
74
79
  def determine_type type
75
- case type
76
- when :inprocess
80
+ case type.to_sym
81
+ when :inproc
77
82
  :inproc
78
- when :tcp, :pgm
79
- type
83
+ when :tcp, :pgm, :ipc
84
+ type.to_sym
80
85
  else
81
- raise UnknownAddressError, "Unknown address transport type [#{type}]; must be :tcp, :pgm, or :inprocess"
86
+ raise UnknownAddressError, "Unknown address transport type [#{type}]; must be :tcp, :pgm, or :inproc"
82
87
  end
83
88
  end
84
89
 
@@ -65,17 +65,20 @@ module ZMQMachine
65
65
  class Handler
66
66
  attr_accessor :socket_out
67
67
 
68
- def initialize reactor, address, verbose = false
68
+ def initialize reactor, address, opts = {}
69
69
  @reactor = reactor
70
70
  @address = address
71
- @verbose = verbose
71
+ @verbose = opts[:verbose] || false
72
+ @opts = opts
73
+
74
+ @messages = []
72
75
  end
73
76
 
74
77
  def on_attach socket
75
78
  socket.identity = "forwarder.#{Kernel.rand(999_999_999)}"
79
+ set_options socket
76
80
  rc = socket.bind @address
77
81
  #FIXME: error handling!
78
-
79
82
  socket.subscribe_all if :sub == socket.kind
80
83
  end
81
84
 
@@ -84,10 +87,19 @@ module ZMQMachine
84
87
  end
85
88
 
86
89
  def on_readable socket, messages
87
- messages.each { |msg| puts "[fwd] [#{msg.copy_out_string}]" } if @verbose
90
+ messages.each { |msg| @reactor.log(:device, "[fwd] [#{msg.copy_out_string}]") } if @verbose
88
91
 
89
- socket_out.send_messages messages if @socket_out
92
+ if @socket_out
93
+ rc = socket_out.send_messages messages
94
+ messages.each { |message| message.close }
95
+ end
96
+ end
97
+
98
+ def set_options socket
99
+ socket.raw_socket.setsockopt ZMQ::HWM, (@opts[:hwm] || 1)
100
+ socket.raw_socket.setsockopt ZMQ::LINGER, (@opts[:linger] || 0)
90
101
  end
102
+
91
103
  end # class Handler
92
104
 
93
105
 
@@ -96,13 +108,13 @@ module ZMQMachine
96
108
  #
97
109
  # Forwards all messages received by the +incoming+ address to the +outgoing+ address.
98
110
  #
99
- def initialize reactor, incoming, outgoing, verbose = false
111
+ def initialize reactor, incoming, outgoing, opts = {:verbose => false}
100
112
  incoming = Address.from_string incoming if incoming.kind_of? String
101
113
  outgoing = Address.from_string outgoing if outgoing.kind_of? String
102
114
 
103
115
  # setup the handlers for processing messages
104
- @handler_in = Handler.new reactor, incoming, verbose
105
- @handler_out = Handler.new reactor, outgoing, verbose
116
+ @handler_in = Handler.new reactor, incoming, opts
117
+ @handler_out = Handler.new reactor, outgoing, opts
106
118
 
107
119
  # create each socket and pass in the appropriate handler
108
120
  @incoming = reactor.sub_socket @handler_in
@@ -67,15 +67,17 @@ module ZMQMachine
67
67
  class Handler
68
68
  attr_accessor :socket_out
69
69
 
70
- def initialize reactor, address, verbose = false, dir = 0
70
+ def initialize reactor, address, dir, opts = {}
71
71
  @reactor = reactor
72
72
  @address = address
73
- @verbose = verbose
73
+ @verbose = opts[:verbose] || false
74
+ @opts = opts
74
75
  @dir = dir
75
76
  end
76
77
 
77
78
  def on_attach socket
78
79
  socket.identity = "queue.#{Kernel.rand(999_999_999)}"
80
+ set_options socket
79
81
  rc = socket.bind @address
80
82
  #FIXME: error handling!
81
83
  end
@@ -85,13 +87,20 @@ module ZMQMachine
85
87
  end
86
88
 
87
89
  def on_readable socket, messages
88
- messages.each { |msg| puts "[Q#{@dir}] [#{msg.copy_out_string}]" } if @verbose
90
+ messages.each { |msg| @reactor.log(:device, "[Q#{@dir}] [#{msg.copy_out_string}]") } if @verbose
89
91
 
90
92
  if @socket_out
91
93
  # FIXME: need to be able to handle EAGAIN/failed send
92
94
  rc = socket_out.send_messages messages
95
+ messages.each { |message| message.close }
93
96
  end
94
97
  end
98
+
99
+ def set_options socket
100
+ socket.raw_socket.setsockopt ZMQ::HWM, (@opts[:hwm] || 1)
101
+ socket.raw_socket.setsockopt ZMQ::LINGER, (@opts[:linger] || 0)
102
+ end
103
+
95
104
  end # class Handler
96
105
 
97
106
 
@@ -100,13 +109,13 @@ module ZMQMachine
100
109
  #
101
110
  # Routes all messages received by either address to the other address.
102
111
  #
103
- def initialize reactor, incoming, outgoing, verbose = false
112
+ def initialize reactor, incoming, outgoing, opts = {}
104
113
  incoming = Address.from_string incoming if incoming.kind_of? String
105
114
  outgoing = Address.from_string outgoing if outgoing.kind_of? String
106
115
 
107
116
  # setup the handlers for processing messages
108
- @handler_in = Handler.new reactor, incoming, verbose, :in
109
- @handler_out = Handler.new reactor, outgoing, verbose, :out
117
+ @handler_in = Handler.new reactor, incoming, :in, opts
118
+ @handler_out = Handler.new reactor, outgoing, :out, opts
110
119
 
111
120
  # create each socket and pass in the appropriate handler
112
121
  @incoming = reactor.xrep_socket @handler_in
@@ -0,0 +1,135 @@
1
+ #--
2
+ #
3
+ # Author:: Chuck Remes
4
+ # Homepage:: http://github.com/chuckremes/zmqmachine
5
+ # Date:: 20110306
6
+ #
7
+ #----------------------------------------------------------------------------
8
+ #
9
+ # Copyright (C) 2011 by Chuck Remes. All Rights Reserved.
10
+ # Email: cremes at mac dot com
11
+ #
12
+ # (The MIT License)
13
+ #
14
+ # Permission is hereby granted, free of charge, to any person obtaining
15
+ # a copy of this software and associated documentation files (the
16
+ # 'Software'), to deal in the Software without restriction, including
17
+ # without limitation the rights to use, copy, modify, merge, publish,
18
+ # distribute, sublicense, and/or sell copies of the Software, and to
19
+ # permit persons to whom the Software is furnished to do so, subject to
20
+ # the following conditions:
21
+ #
22
+ # The above copyright notice and this permission notice shall be
23
+ # included in all copies or substantial portions of the Software.
24
+ #
25
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
26
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
28
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
29
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
30
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
31
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32
+ #
33
+ #---------------------------------------------------------------------------
34
+ #
35
+ #
36
+
37
+ module ZMQMachine
38
+
39
+ class LogClient
40
+ def initialize reactor, transport
41
+ @reactor = reactor
42
+ @transport = transport
43
+ allocate_socket
44
+ @message_queue = []
45
+ @timer = nil
46
+ end
47
+
48
+ def shutdown
49
+ @timer.cancel if @timer
50
+ @reactor.close_socket @socket
51
+ end
52
+
53
+ def on_attach socket
54
+ socket.identity = "#{Kernel.rand(999_999_999)}.#{socket.kind}.log_client"
55
+
56
+ # socket options *must* be set before we bind/connect otherwise they are ignored
57
+ set_options socket
58
+
59
+ #FIXME error check!
60
+ rc = socket.connect @transport
61
+
62
+ raise "#{self.class}#on_attach, failed to connect to transport endpoint [#{@transport}]" unless rc
63
+
64
+ register_for_events socket
65
+ end
66
+
67
+ # Takes an array of ZM::Message instances and writes them out to the socket. If any
68
+ # socket write fails, the message is saved. We will attempt to write it again in
69
+ # 10 milliseconds or when another message array is sent, whichever comes first.
70
+ #
71
+ # All messages passed here are guaranteed to be written in the *order they were
72
+ # received*.
73
+ #
74
+ def write messages
75
+ @message_queue << messages
76
+ write_queue_to_socket
77
+ end
78
+
79
+ # Prints each message when global debugging is enabled.
80
+ #
81
+ # Forwards +messages+ on to the :on_read callback given in the constructor.
82
+ #
83
+ def on_readable socket, messages
84
+ @on_read.call messages, socket
85
+ end
86
+
87
+ # Just deregisters from receiving any further write *events*
88
+ #
89
+ def on_writable socket
90
+ @reactor.deregister_writable socket
91
+ end
92
+
93
+
94
+ private
95
+
96
+ def allocate_socket
97
+ @socket = @reactor.pub_socket self
98
+ end
99
+
100
+ def register_for_events socket
101
+ @reactor.register_readable socket
102
+ @reactor.deregister_writable socket
103
+ end
104
+
105
+ def set_options socket
106
+ socket.raw_socket.setsockopt ZMQ::HWM, 0
107
+ socket.raw_socket.setsockopt ZMQ::LINGER, 0
108
+ end
109
+
110
+ def write_queue_to_socket
111
+ until @message_queue.empty?
112
+ rc = @socket.send_messages @message_queue.at(0)
113
+
114
+ if rc # succeeded, so remove the message from the queue
115
+ messages = @message_queue.shift
116
+ messages.each { |message| message.close }
117
+
118
+ if @timer
119
+ @timer.cancel
120
+ @timer = nil
121
+ end
122
+
123
+ else
124
+
125
+ # schedule another write attempt in 10 ms; break out of the loop
126
+ # only set the timer *once*
127
+ @timer = @reactor.oneshot_timer 10, method(:write_queue_to_socket) unless @timer
128
+ break
129
+ end
130
+ end
131
+ end
132
+
133
+ end # class LogClient
134
+
135
+ end # module ZMQMachine
data/lib/zm/reactor.rb CHANGED
@@ -39,10 +39,30 @@ module ZMQMachine
39
39
  class Reactor
40
40
  attr_reader :name
41
41
 
42
+ # +name+ provides a name for this reactor instance. It's unused
43
+ # at present but may be used in the future for allowing multiple
44
+ # reactors to communicate amongst each other.
45
+ #
42
46
  # +poll_interval+ is the number of milliseconds to block while
43
47
  # waiting for new 0mq socket events; default is 10
44
48
  #
45
- def initialize name, poll_interval = 10
49
+ # +opts+ may contain a key +:zeromq_context+. When this
50
+ # hash is provided, the value for :zeromq_context should be a
51
+ # 0mq context as created by ZMQ::Context.new. The purpose of
52
+ # providing a context to the reactor is so that multiple
53
+ # reactors can share a single context. Doing so allows for sockets
54
+ # within each reactor to communicate with each other via an
55
+ # :inproc transport (:inproc is misnamed, it should be :incontext).
56
+ # By not supplying this hash, the reactor will create and use
57
+ # its own 0mq context.
58
+ #
59
+ # +opts+ may also include a +:log_transport+ key. This should be
60
+ # a transport string for an endpoint that a logger client may connect
61
+ # to for publishing log messages. when this key is defined, the
62
+ # client is automatically created and connected to the indicated
63
+ # endpoint.
64
+ #
65
+ def initialize name, poll_interval = 10, opts = {}
46
66
  @name = name
47
67
  @running = false
48
68
  @thread = nil
@@ -53,11 +73,16 @@ module ZMQMachine
53
73
  @proc_queue_mutex = Mutex.new
54
74
 
55
75
  # could raise if it fails
56
- @context = ZMQ::Context.new 1
76
+ @context = opts[:zeromq_context] || ZMQ::Context.new
57
77
  @poller = ZMQ::Poller.new
58
78
  @sockets = []
59
79
  @raw_to_socket = {}
60
80
  Thread.abort_on_exception = true
81
+
82
+ if opts[:log_transport]
83
+ @logger = LogClient.new self, opts[:log_transport]
84
+ @logging_enabled = true
85
+ end
61
86
  end
62
87
 
63
88
  # Returns true when the reactor is running OR while it is in the
@@ -153,9 +178,15 @@ module ZMQMachine
153
178
  # Removes the given +sock+ socket from the reactor context. It is deregistered
154
179
  # for new events and closed. Any queued messages are silently dropped.
155
180
  #
181
+ # Returns +true+ for a succesful close, +false+ otherwise.
182
+ #
156
183
  def close_socket sock
157
- delete_socket sock
184
+ return false unless sock
185
+
186
+ removed = delete_socket sock
158
187
  sock.raw_socket.close
188
+
189
+ removed
159
190
  end
160
191
 
161
192
  # Creates a REQ socket and attaches +handler_instance+ to the
@@ -351,6 +382,35 @@ module ZMQMachine
351
382
  end
352
383
  end
353
384
 
385
+ def open_socket_count kind = :all
386
+ @sockets.inject(0) do |sum, socket|
387
+ if :all == kind || (socket.kind == kind)
388
+ sum + 1
389
+ else
390
+ sum
391
+ end
392
+ end
393
+ end
394
+
395
+ # Publishes log messages to an existing transport passed in to the Reactor
396
+ # constructor using the :log_transport key.
397
+ #
398
+ # Reactor.new :log_transport => 'inproc://reactor_log'
399
+ #
400
+ # +level+ parameter refers to a key to indicate severity level, e.g. :warn,
401
+ # :debug, level0, level9, etc.
402
+ #
403
+ # +message+ is a plain string that will be written out in its entirety.
404
+ #
405
+ # When no :log_transport was defined when creating the Reactor, all calls
406
+ # just discard the messages.
407
+ #
408
+ def log level, message
409
+ if @logging_enabled
410
+ @logger.write [ZMQ::Message.new(level.to_s), ZMQ::Message.new(message.to_s)]
411
+ end
412
+ end
413
+
354
414
 
355
415
  private
356
416
 
@@ -409,10 +469,12 @@ module ZMQMachine
409
469
  @raw_to_socket[sock.raw_socket] = sock
410
470
  end
411
471
 
472
+ # Returns true when all steps succeed, false otherwise
473
+ #
412
474
  def delete_socket sock
413
- @poller.delete sock.raw_socket
414
- @sockets.delete sock
415
- @raw_to_socket.delete sock.raw_socket
475
+ @poller.delete(sock.raw_socket) and
476
+ @sockets.delete(sock) and
477
+ @raw_to_socket.delete(sock.raw_socket)
416
478
  end
417
479
 
418
480
 
@@ -427,61 +489,4 @@ module ZMQMachine
427
489
  end # class Reactor
428
490
 
429
491
 
430
- # Not implemented. Just a (broken) thought experiment at the moment.
431
- #
432
- class SyncReactor
433
-
434
- def initialize name
435
- @klass_path = ZMQMachine::Sync
436
- @poll_interval = 10
437
- @active = true
438
- super name
439
- end
440
-
441
- def run blk = nil, &block
442
- blk ||= block
443
- @running = true
444
-
445
- @thread = Thread.new do
446
- @fiber = Fiber.new { |context| blk.call context }
447
- func = Proc.new { |context| @fiber.resume context }
448
-
449
- run_once func
450
- while @running do
451
- run_once
452
- end
453
-
454
- cleanup
455
- end
456
-
457
- self
458
- end
459
-
460
- def deactivate() @active = false; end
461
-
462
- def active?() @active; end
463
-
464
- def while_active? &blk
465
- begin
466
- while @active do
467
- yield
468
- end
469
- rescue => e
470
- p e
471
- end
472
-
473
- @running = false
474
- end
475
-
476
-
477
- private
478
-
479
- def poll
480
- @poller.poll @poll_interval
481
-
482
- @poller.writables.each { |sock| sock.resume }
483
- @poller.readables.each { |sock| sock.resume }
484
- end
485
- end # class SyncReactor
486
-
487
492
  end # module ZMQMachine
@@ -102,7 +102,11 @@ module ZMQMachine
102
102
  # May raise a ZMQ::SocketError.
103
103
  #
104
104
  def send_message message, multipart = false
105
- queued = @raw_socket.send message, ZMQ::NOBLOCK | (multipart ? ZMQ::SNDMORE : 0)
105
+ begin
106
+ queued = @raw_socket.send message, ZMQ::NOBLOCK | (multipart ? ZMQ::SNDMORE : 0)
107
+ rescue ZMQ::ZeroMQError => e
108
+ queued = false
109
+ end
106
110
  queued
107
111
  end
108
112
 
@@ -125,12 +129,12 @@ module ZMQMachine
125
129
  # May raise a ZMQ::SocketError.
126
130
  #
127
131
  def send_messages messages
128
- rc = false
132
+ rc = true
129
133
  i = 0
130
134
  size = messages.size
131
135
 
132
136
  # loop through all messages but the last
133
- while size > 1 && i < size - 1 do
137
+ while rc && size > 1 && i < size - 1 do
134
138
  rc = send_message messages.at(i), true
135
139
  i += 1
136
140
  end
@@ -140,7 +144,7 @@ module ZMQMachine
140
144
 
141
145
  # send the last message without the multipart arg to flush
142
146
  # the message to the 0mq queue
143
- rc = send_message messages.last if size > 0
147
+ rc = send_message messages.last if rc && size > 0
144
148
  rc
145
149
  end
146
150
 
@@ -204,11 +208,22 @@ module ZMQMachine
204
208
  message = ZMQ::Message.new
205
209
  begin
206
210
  rc = @raw_socket.recv message, ZMQ::NOBLOCK
207
- rc = 0 if rc
211
+
212
+ if rc
213
+ rc = 0 # callers expect 0 for success, not true
214
+ messages << message
215
+ else
216
+ # got EAGAIN most likely
217
+ message.close
218
+ message = nil
219
+ rc = false
220
+ end
221
+
208
222
  rescue ZMQ::ZeroMQError => e
223
+ message.close if message
209
224
  rc = e
210
225
  end
211
- messages << message
226
+
212
227
  rc
213
228
  end
214
229
 
@@ -218,6 +233,7 @@ module ZMQMachine
218
233
  @state = :ready
219
234
  @handler.on_readable self, messages
220
235
  else
236
+ # this branch is never called
221
237
  @handler.on_readable_error self, rc
222
238
  end
223
239
  end
data/lib/zm/timers.rb CHANGED
@@ -96,10 +96,22 @@ module ZMQMachine
96
96
  def cancel timer
97
97
  i = index timer
98
98
 
99
- if timer == @timers.at(i)
99
+ # when #index doesn't find a match, it returns an index 1 past
100
+ # the end, so check for that
101
+ if i < @timers.size && timer == @timers.at(i)
100
102
  @timers.delete_at(i) ? true : false
101
103
  else
102
- false
104
+ # slow branch; necessary since the #index operation works
105
+ # solely from the timer.fire_time attribute. There
106
+ # could be multiple timers scheduled to fire at the
107
+ # same time so the equivalence test above could fail
108
+ # on the first index returned, so fallback to this
109
+ # slower method
110
+ size = @timers.size
111
+ @timers.delete_if { |t| t == timer }
112
+
113
+ # true when the array has shrunk, false otherwise
114
+ @timers.size != size
103
115
  end
104
116
  end
105
117
 
@@ -128,17 +140,17 @@ module ZMQMachine
128
140
  def fire_expired
129
141
  # all time is expected as milliseconds
130
142
  now = Timers.now
131
- runnables, periodicals, expired = [], [], []
143
+ runnables, periodicals, expired_count = [], [], 0
132
144
 
133
145
  # defer firing the timer until after this loop so we can clean it up first
134
- @timers.each_with_index do |timer, index|
146
+ @timers.each do |timer|
135
147
  break unless timer.expired?(now)
136
148
  runnables << timer
137
149
  periodicals << timer if timer.periodical?
138
- expired << index
150
+ expired_count += 1
139
151
  end
140
152
 
141
- remove expired
153
+ remove expired_count
142
154
  runnables.each { |timer| timer.fire }
143
155
  renew periodicals
144
156
  end
@@ -204,10 +216,11 @@ module ZMQMachine
204
216
  l
205
217
  end
206
218
 
207
- def remove expired
208
- # need to reverse so we are deleting the highest indexes first,
209
- # otherwise everything shifts down and we delete the wrong timers
210
- expired.sort.reverse.each { |index| @timers.delete_at index }
219
+ def remove expired_count
220
+ # the timers are ordered, so we can just shift them off the front
221
+ # of the array; this is *orders of magnitude* faster than #delete_at
222
+ # with a (reversed) array of indexes
223
+ expired_count.times { @timers.shift }
211
224
  end
212
225
 
213
226
  def renew timers
@@ -263,14 +276,22 @@ module ZMQMachine
263
276
  end
264
277
 
265
278
  def <=>(other)
266
- self.fire_time <=> other.fire_time
279
+ @fire_time <=> other.fire_time
280
+ end
281
+
282
+ def ==(other)
283
+ # need a more specific equivalence test since multiple timers could be
284
+ # scheduled to go off at exactly the same time
285
+ @fire_time == other.fire_time &&
286
+ @timer_proc == other.timer_proc &&
287
+ periodical? == other.periodical?
267
288
  end
268
289
 
269
290
  # True when the timer should be fired; false otherwise.
270
291
  #
271
292
  def expired? time
272
293
  time ||= Timers.now
273
- time > @fire_time
294
+ time >= @fire_time
274
295
  end
275
296
 
276
297
  # True when this is a periodical timer; false otherwise.
@@ -284,7 +305,11 @@ module ZMQMachine
284
305
  end
285
306
 
286
307
  def to_s
287
- "[delay [#{@delay}], periodical? [#{@periodical}], fire_time [#{Time.at(@fire_time/1000)}] fire_delay_s [#{(@fire_time - Timers.now)/1000}]]"
308
+ ftime = Time.at(@fire_time / 1000)
309
+ fdelay = @fire_time - Timers.now
310
+ name = @timer_proc.respond_to?(:name) ? @timer_proc.name : @timer_proc.to_s
311
+
312
+ "[delay [#{@delay}], periodical? [#{@periodical}], fire_time [#{ftime}] fire_delay_ms [#{fdelay}]] proc [#{name}]"
288
313
  end
289
314
 
290
315
  def inspect; to_s; end
data/lib/zmqmachine.rb CHANGED
@@ -62,11 +62,10 @@ module ZMQMachine
62
62
  end # module ZMQMachine
63
63
 
64
64
  require 'singleton'
65
- require 'set'
66
65
  require 'ffi-rzmq'
67
66
 
68
67
  # the order of files is important
69
- %w(address exceptions timers deferrable reactor message sockets devices).each do |file|
68
+ %w(address exceptions timers deferrable log_client reactor message sockets devices).each do |file|
70
69
  require ZMQMachine.libpath(['zm', file])
71
70
  end
72
71
 
@@ -0,0 +1,25 @@
1
+ $: << "." # added for ruby 1.9.2 compatibilty; it doesn't include the current directory on the load path anymore
2
+
3
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
4
+
5
+ module ZM
6
+
7
+ describe Reactor do
8
+
9
+
10
+ context "when closing sockets" do
11
+ let(:reactor) { Reactor.new(:test) }
12
+
13
+ it "should return false when the given socket is nil" do
14
+ reactor.close_socket(nil).should be_false
15
+ end
16
+
17
+ it "should return true when the given socket is successfully closed and deleted" do
18
+ sock = reactor.req_socket(FakeHandler.new)
19
+
20
+ reactor.close_socket(sock).should be_true
21
+ end
22
+ end
23
+ end # describe Reactor
24
+
25
+ end # module ZM
data/spec/spec_helper.rb CHANGED
@@ -2,14 +2,19 @@
2
2
  require File.expand_path(
3
3
  File.join(File.dirname(__FILE__), %w[.. lib zmqmachine]))
4
4
 
5
- Spec::Runner.configure do |config|
6
- # == Mock Framework
7
- #
8
- # RSpec uses it's own mocking framework by default. If you prefer to
9
- # use mocha, flexmock or RR, uncomment the appropriate line:
10
- #
11
- # config.mock_with :mocha
12
- # config.mock_with :flexmock
13
- # config.mock_with :rr
14
- end
5
+ #Rspec.configure do |config|
6
+ # # == Mock Framework
7
+ # #
8
+ # # RSpec uses it's own mocking framework by default. If you prefer to
9
+ # # use mocha, flexmock or RR, uncomment the appropriate line:
10
+ # #
11
+ # # config.mock_with :mocha
12
+ # # config.mock_with :flexmock
13
+ # # config.mock_with :rr
14
+ #end
15
15
 
16
+ class FakeHandler
17
+ def on_attach socket
18
+
19
+ end
20
+ end
data/version.txt CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.5.0
data/zmqmachine.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{zmqmachine}
5
- s.version = "0.4.0"
5
+ s.version = "0.5.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Chuck Remes"]
@@ -22,7 +22,7 @@ It is possible to extend the 0mq library to "poll" normal file
22
22
  descriptors. This isn't on my roadmap but patches are accepted.}
23
23
  s.email = %q{cremes@mac.com}
24
24
  s.extra_rdoc_files = ["History.txt", "README.rdoc", "version.txt"]
25
- s.files = [".bnsignore", "History.txt", "README.rdoc", "Rakefile", "examples/fake_ftp.rb", "examples/one_handed_ping_pong.rb", "examples/ping_pong.rb", "examples/pub_sub.rb", "examples/pubsub_forwarder.rb", "examples/throttled_ping_pong.rb", "lib/zm/address.rb", "lib/zm/deferrable.rb", "lib/zm/devices.rb", "lib/zm/devices/forwarder.rb", "lib/zm/devices/queue.rb", "lib/zm/exceptions.rb", "lib/zm/message.rb", "lib/zm/reactor.rb", "lib/zm/sockets.rb", "lib/zm/sockets/base.rb", "lib/zm/sockets/pair.rb", "lib/zm/sockets/pub.rb", "lib/zm/sockets/rep.rb", "lib/zm/sockets/req.rb", "lib/zm/sockets/sub.rb", "lib/zm/sockets/xrep.rb", "lib/zm/sockets/xreq.rb", "lib/zm/timers.rb", "lib/zmqmachine.rb", "spec/spec_helper.rb", "spec/zmqmachine_spec.rb", "version.txt", "zmqmachine.gemspec"]
25
+ s.files = [".bnsignore", "History.txt", "README.rdoc", "Rakefile", "examples/fake_ftp.rb", "examples/one_handed_ping_pong.rb", "examples/ping_pong.rb", "examples/pub_sub.rb", "examples/pubsub_forwarder.rb", "examples/throttled_ping_pong.rb", "lib/zm/address.rb", "lib/zm/deferrable.rb", "lib/zm/devices.rb", "lib/zm/devices/forwarder.rb", "lib/zm/devices/queue.rb", "lib/zm/exceptions.rb", "lib/zm/log_client.rb", "lib/zm/message.rb", "lib/zm/reactor.rb", "lib/zm/sockets.rb", "lib/zm/sockets/base.rb", "lib/zm/sockets/pair.rb", "lib/zm/sockets/pub.rb", "lib/zm/sockets/rep.rb", "lib/zm/sockets/req.rb", "lib/zm/sockets/sub.rb", "lib/zm/sockets/xrep.rb", "lib/zm/sockets/xreq.rb", "lib/zm/timers.rb", "lib/zmqmachine.rb", "spec/spec_helper.rb", "spec/reactor_spec.rb", "version.txt", "zmqmachine.gemspec"]
26
26
  s.homepage = %q{http://github.com/chuckremes/zmqmachine}
27
27
  s.rdoc_options = ["--main", "README.rdoc"]
28
28
  s.require_paths = ["lib"]
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zmqmachine
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ hash: 11
5
+ prerelease:
5
6
  segments:
6
7
  - 0
7
- - 4
8
+ - 5
8
9
  - 0
9
- version: 0.4.0
10
+ version: 0.5.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Chuck Remes
@@ -25,6 +26,7 @@ dependencies:
25
26
  requirements:
26
27
  - - ">="
27
28
  - !ruby/object:Gem::Version
29
+ hash: 3
28
30
  segments:
29
31
  - 0
30
32
  - 7
@@ -40,6 +42,7 @@ dependencies:
40
42
  requirements:
41
43
  - - ">="
42
44
  - !ruby/object:Gem::Version
45
+ hash: 27
43
46
  segments:
44
47
  - 3
45
48
  - 5
@@ -87,6 +90,7 @@ files:
87
90
  - lib/zm/devices/forwarder.rb
88
91
  - lib/zm/devices/queue.rb
89
92
  - lib/zm/exceptions.rb
93
+ - lib/zm/log_client.rb
90
94
  - lib/zm/message.rb
91
95
  - lib/zm/reactor.rb
92
96
  - lib/zm/sockets.rb
@@ -101,7 +105,7 @@ files:
101
105
  - lib/zm/timers.rb
102
106
  - lib/zmqmachine.rb
103
107
  - spec/spec_helper.rb
104
- - spec/zmqmachine_spec.rb
108
+ - spec/reactor_spec.rb
105
109
  - version.txt
106
110
  - zmqmachine.gemspec
107
111
  has_rdoc: true
@@ -119,6 +123,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
119
123
  requirements:
120
124
  - - ">="
121
125
  - !ruby/object:Gem::Version
126
+ hash: 3
122
127
  segments:
123
128
  - 0
124
129
  version: "0"
@@ -127,13 +132,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
132
  requirements:
128
133
  - - ">="
129
134
  - !ruby/object:Gem::Version
135
+ hash: 3
130
136
  segments:
131
137
  - 0
132
138
  version: "0"
133
139
  requirements: []
134
140
 
135
141
  rubyforge_project: zmqmachine
136
- rubygems_version: 1.3.7
142
+ rubygems_version: 1.5.2
137
143
  signing_key:
138
144
  specification_version: 3
139
145
  summary: ZMQMachine is another Ruby implementation of the reactor pattern but this time using 0mq sockets rather than POSIX sockets.
@@ -1,6 +0,0 @@
1
-
2
- require File.join(File.dirname(__FILE__), %w[spec_helper])
3
-
4
- describe ZMQMachine do
5
- end
6
-