zmqmachine 0.4.0 → 0.5.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.
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
-