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 +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.
|