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 +48 -0
- data/lib/zm/address.rb +14 -9
- data/lib/zm/devices/forwarder.rb +20 -8
- data/lib/zm/devices/queue.rb +15 -6
- data/lib/zm/log_client.rb +135 -0
- data/lib/zm/reactor.rb +68 -63
- data/lib/zm/sockets/base.rb +22 -6
- data/lib/zm/timers.rb +38 -13
- data/lib/zmqmachine.rb +1 -2
- data/spec/reactor_spec.rb +25 -0
- data/spec/spec_helper.rb +15 -10
- data/version.txt +1 -1
- data/zmqmachine.gemspec +2 -2
- metadata +11 -5
- data/spec/zmqmachine_spec.rb +0 -6
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
|
-
|
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 :
|
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 :
|
86
|
+
raise UnknownAddressError, "Unknown address transport type [#{type}]; must be :tcp, :pgm, or :inproc"
|
82
87
|
end
|
83
88
|
end
|
84
89
|
|
data/lib/zm/devices/forwarder.rb
CHANGED
@@ -65,17 +65,20 @@ module ZMQMachine
|
|
65
65
|
class Handler
|
66
66
|
attr_accessor :socket_out
|
67
67
|
|
68
|
-
def initialize reactor, address,
|
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|
|
90
|
+
messages.each { |msg| @reactor.log(:device, "[fwd] [#{msg.copy_out_string}]") } if @verbose
|
88
91
|
|
89
|
-
|
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,
|
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,
|
105
|
-
@handler_out = Handler.new reactor, outgoing,
|
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
|
data/lib/zm/devices/queue.rb
CHANGED
@@ -67,15 +67,17 @@ module ZMQMachine
|
|
67
67
|
class Handler
|
68
68
|
attr_accessor :socket_out
|
69
69
|
|
70
|
-
def initialize reactor, address,
|
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|
|
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,
|
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,
|
109
|
-
@handler_out = Handler.new reactor, outgoing,
|
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
|
-
|
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
|
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
|
-
|
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
|
414
|
-
@sockets.delete
|
415
|
-
@raw_to_socket.delete
|
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
|
data/lib/zm/sockets/base.rb
CHANGED
@@ -102,7 +102,11 @@ module ZMQMachine
|
|
102
102
|
# May raise a ZMQ::SocketError.
|
103
103
|
#
|
104
104
|
def send_message message, multipart = false
|
105
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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.
|
146
|
+
@timers.each do |timer|
|
135
147
|
break unless timer.expired?(now)
|
136
148
|
runnables << timer
|
137
149
|
periodicals << timer if timer.periodical?
|
138
|
-
|
150
|
+
expired_count += 1
|
139
151
|
end
|
140
152
|
|
141
|
-
remove
|
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
|
208
|
-
#
|
209
|
-
#
|
210
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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/
|
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
|
-
|
4
|
+
hash: 11
|
5
|
+
prerelease:
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
-
|
8
|
+
- 5
|
8
9
|
- 0
|
9
|
-
version: 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/
|
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.
|
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.
|