rbzmq 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE.txt +165 -0
- data/README.md +70 -0
- data/doc/file.README.html +125 -0
- data/lib/rbzmq.rb +11 -0
- data/lib/rbzmq/context.rb +40 -0
- data/lib/rbzmq/errors.rb +45 -0
- data/lib/rbzmq/message.rb +41 -0
- data/lib/rbzmq/poller.rb +198 -0
- data/lib/rbzmq/socket.rb +287 -0
- data/lib/rbzmq/version.rb +14 -0
- data/rbzmq.gemspec +24 -0
- data/spec/functional/rbzmq/poller_spec.rb +95 -0
- data/spec/functional/rbzmq/socket_spec.rb +40 -0
- data/spec/rbzmq/socket_spec.rb +255 -0
- data/spec/scripts/test.rb +5 -0
- data/spec/spec_helper.rb +27 -0
- metadata +94 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
module RbZMQ
|
2
|
+
#
|
3
|
+
# = RbZMQ::Message
|
4
|
+
#
|
5
|
+
class Message
|
6
|
+
#
|
7
|
+
attr_reader :data
|
8
|
+
|
9
|
+
def initialize(str = '')
|
10
|
+
if str.is_a?(ZMQ::Message)
|
11
|
+
@data = str.copy_out_string
|
12
|
+
str.close
|
13
|
+
else
|
14
|
+
@data = str.to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
data
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_zmq
|
23
|
+
ZMQ::Message.new(data)
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
#
|
28
|
+
# Create new {RbZMQ::Message}.
|
29
|
+
#
|
30
|
+
# If first argument is a {RbZMQ::Message} object it will
|
31
|
+
# be returned instead of a new one.
|
32
|
+
#
|
33
|
+
# @return [RbZMQ::Message] Newly created message.
|
34
|
+
#
|
35
|
+
def new(*args)
|
36
|
+
return args[0] if args[0].is_a?(self)
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/rbzmq/poller.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
module RbZMQ
|
2
|
+
#
|
3
|
+
# = RbZMQ::Poller
|
4
|
+
#
|
5
|
+
# The {Poller} allows to poll on one or more ZMQ sockets
|
6
|
+
# or file descriptors simultaneously.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# poller = RbZMQ::Poller.new
|
10
|
+
# poller.register(socket, ZMQ::POLLIN)
|
11
|
+
# poller.poll(10_000) do |socket|
|
12
|
+
# # Do something with socket
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
class Poller
|
16
|
+
#
|
17
|
+
# Create a new poller.
|
18
|
+
#
|
19
|
+
def initialize
|
20
|
+
@poll_items = ZMQ::PollItems.new
|
21
|
+
@mutex = Mutex.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Poll on all registered objects.
|
25
|
+
#
|
26
|
+
# If a block is given it will be invoked for each ready
|
27
|
+
# pollable object. Without a block an enumerator of
|
28
|
+
# ready pollables will be returned.
|
29
|
+
#
|
30
|
+
# If not selectable is registered {#poll} will return
|
31
|
+
# without blocking.
|
32
|
+
#
|
33
|
+
# @example Poll with block
|
34
|
+
# poller.poll(10_000) do |io|
|
35
|
+
# io.readable? || io.writable? #=> true
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# @param timeout [Integer, Symbol] A timeout in milliseconds.
|
39
|
+
# The values `-1`, `:blocking` and `:infinity` will
|
40
|
+
# block indefinitely.
|
41
|
+
#
|
42
|
+
# @yield [pollable] Yield each ready object.
|
43
|
+
# @yieldparam pollable [RbZMQ::Socket, IO, Object] Registered
|
44
|
+
# pollable object.
|
45
|
+
#
|
46
|
+
# @return [Enumerator, Boolean, Nil] The return value is
|
47
|
+
# determined by the following rules:
|
48
|
+
# 1. Nil is returned when no objects are registered.
|
49
|
+
# 2. An Enumerator will be returned when no block
|
50
|
+
# is given. The enumerator will have no elements if
|
51
|
+
# call timed out.
|
52
|
+
# 3. If a block is given true will be returned when
|
53
|
+
# objects were ready, false if times out.
|
54
|
+
#
|
55
|
+
def poll(timeout, &block)
|
56
|
+
mutex.synchronize do
|
57
|
+
if @poll_items.any?
|
58
|
+
ready_items = do_poll(convert_timeout(timeout))
|
59
|
+
|
60
|
+
if block_given?
|
61
|
+
ready_items > 0 ? each_ready_item(&block) : false
|
62
|
+
else
|
63
|
+
if ready_items > 0
|
64
|
+
to_enum(:each_ready_item)
|
65
|
+
else
|
66
|
+
Array.new.to_enum(:each)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
else
|
70
|
+
nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Return number of registered pollables.
|
76
|
+
#
|
77
|
+
# @return [Integer] Number of registered objects.
|
78
|
+
#
|
79
|
+
def size
|
80
|
+
mutex.synchronize { @poll_items.size }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Register given socket or IO to be watched on given
|
84
|
+
# event list.
|
85
|
+
#
|
86
|
+
# This method is idempotent.
|
87
|
+
#
|
88
|
+
# @example Watch socket to read
|
89
|
+
# socket = RbZMQ::Socket.new(ZMQ::DEALER)
|
90
|
+
# poller.register(socket, ZMQ::POLLIN)
|
91
|
+
#
|
92
|
+
# @example Watch IO to write
|
93
|
+
# reader, writer = IO.pipe
|
94
|
+
# poller.register(writer, ZMQ::POLLOUT)
|
95
|
+
#
|
96
|
+
# @param pollable [RbZMQ::Socket, IO] Watchable socket or
|
97
|
+
# IO object.
|
98
|
+
#
|
99
|
+
# @param events [Integer] ZMQ events. Calling multiple
|
100
|
+
# times with different events will OR the events together.
|
101
|
+
# Allowed values are ZMQ::POLLIN and ZMQ::POLLOUT.
|
102
|
+
#
|
103
|
+
# @return [Integer] Registered events for pollable.
|
104
|
+
#
|
105
|
+
def register(pollable, events = ZMQ::POLLIN)
|
106
|
+
return if pollable.nil? || events.zero?
|
107
|
+
|
108
|
+
mutex.synchronize do
|
109
|
+
item = @poll_items[pollable]
|
110
|
+
unless item
|
111
|
+
item = ::ZMQ::PollItem.from_pollable(pollable)
|
112
|
+
@poll_items << item
|
113
|
+
end
|
114
|
+
|
115
|
+
item.events |= events
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Deregister events from pollable.
|
120
|
+
#
|
121
|
+
# When no events are left or socket or IO object has been
|
122
|
+
# closed it will also be remove from watched objects.
|
123
|
+
#
|
124
|
+
# @param pollable [RbZMQ::Socket, IO] Watchable socket
|
125
|
+
# or IO object.
|
126
|
+
#
|
127
|
+
# @param events [Integer] ZMQ events.
|
128
|
+
# Allowed values are ZMQ::POLLIN and ZMQ::POLLOUT.
|
129
|
+
#
|
130
|
+
# @return [Boolean] False if pollable was removed
|
131
|
+
# because all events where removed or it was closed,
|
132
|
+
# nil if pollable was not registered or an Integer
|
133
|
+
# with the leaving events.
|
134
|
+
#
|
135
|
+
def deregister(pollable, events = ZMQ::POLLIN | ZMQ::POLLOUT)
|
136
|
+
return unless pollable
|
137
|
+
|
138
|
+
mutex.synchronize do
|
139
|
+
item = @poll_items[pollable]
|
140
|
+
if item && (item.events & events) > 0
|
141
|
+
item.events ^= events
|
142
|
+
|
143
|
+
if item.events.zero? || item.closed?
|
144
|
+
@poll_items.delete pollable
|
145
|
+
false
|
146
|
+
else
|
147
|
+
item.events
|
148
|
+
end
|
149
|
+
else
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Remove socket or IO object from poller.
|
156
|
+
#
|
157
|
+
# @param pollable [RbZMQ::Socket, IO] Watched object to remove.
|
158
|
+
#
|
159
|
+
# @return [Boolean] True if pollable was successfully
|
160
|
+
# removed, false otherwise.
|
161
|
+
#
|
162
|
+
def delete(pollable)
|
163
|
+
mutex.synchronize do
|
164
|
+
return false if @poll_items.empty?
|
165
|
+
|
166
|
+
@poll_items.delete pollable
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
attr_reader :mutex
|
173
|
+
|
174
|
+
def do_poll(timeout)
|
175
|
+
rc = LibZMQ.zmq_poll @poll_items.address,
|
176
|
+
@poll_items.size,
|
177
|
+
timeout
|
178
|
+
RbZMQ::ZMQError.error! rc
|
179
|
+
end
|
180
|
+
|
181
|
+
def each_ready_item(&block)
|
182
|
+
@poll_items.each do |item|
|
183
|
+
yield item.pollable if item.readable? || item.writable?
|
184
|
+
end
|
185
|
+
|
186
|
+
true
|
187
|
+
end
|
188
|
+
|
189
|
+
def convert_timeout(timeout)
|
190
|
+
case timeout
|
191
|
+
when :blocking, :infinity, -1
|
192
|
+
-1
|
193
|
+
else
|
194
|
+
Integer timeout
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
data/lib/rbzmq/socket.rb
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
module RbZMQ
|
2
|
+
#
|
3
|
+
# = RbZMQ::Socket
|
4
|
+
#
|
5
|
+
class Socket
|
6
|
+
#
|
7
|
+
# Default timeout.
|
8
|
+
#
|
9
|
+
DEFAULT_TIMEOUT = 5_000
|
10
|
+
|
11
|
+
# @!visibility private
|
12
|
+
#
|
13
|
+
# Internal ZMQ::Context reference.
|
14
|
+
#
|
15
|
+
attr_reader :zmq_ctx
|
16
|
+
|
17
|
+
# @!visibility private
|
18
|
+
#
|
19
|
+
# Internal ZMQ::Socket reference.
|
20
|
+
#
|
21
|
+
attr_reader :zmq_socket
|
22
|
+
|
23
|
+
# Allocates a socket of given type for sending and receiving data.
|
24
|
+
#
|
25
|
+
# @param type [Integer] ZMQ socket type, on if ZMQ::REQ, ZMQ::REP,
|
26
|
+
# ZMQ::PUB, ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH,
|
27
|
+
# ZMQ::XREQ, ZMQ::REP, ZMQ::DEALER or ZMQ::ROUTER.
|
28
|
+
#
|
29
|
+
# @param opts [Hash] Option hash. :ctx will be removed, all other
|
30
|
+
# options will be passed to ZMQ::Socket.new.
|
31
|
+
#
|
32
|
+
# @option opts [Context] :ctx ZMQ context used to initialize socket.
|
33
|
+
# By default {Context.global} is used. Must be {RbZMQ::Context},
|
34
|
+
# ZMQ::Context or an FFI::Pointer.
|
35
|
+
#
|
36
|
+
# @raise [ZMQError] On error.
|
37
|
+
#
|
38
|
+
# @return [Socket] Created socket object.
|
39
|
+
#
|
40
|
+
def initialize(type, opts = {})
|
41
|
+
ctx = opts.fetch(:ctx) { RbZMQ::Context.global }
|
42
|
+
ctx = ctx.pointer if ctx.respond_to? :pointer
|
43
|
+
|
44
|
+
unless ctx.is_a?(FFI::Pointer)
|
45
|
+
raise ArgumentError.new "Context must be ZMQ::Context or " \
|
46
|
+
"RbZMQ::Context (respond to #pointer) or must be a FFI::Pointer, "\
|
47
|
+
"but #{ctx.inspect} given."
|
48
|
+
end
|
49
|
+
|
50
|
+
@zmq_ctx = ctx
|
51
|
+
@zmq_socket = ZMQ::Socket.new ctx, type
|
52
|
+
rescue ZMQ::ZeroMQError => err
|
53
|
+
raise ZMQError.new err
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return ZMQ socket pointer. Required interface for ZMQ::Poller.
|
57
|
+
#
|
58
|
+
def socket
|
59
|
+
@zmq_socket.socket
|
60
|
+
end
|
61
|
+
|
62
|
+
# Bind this socket to given address.
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# socket = RbZMQ::Socket.new ZMQ::PUB
|
66
|
+
# socket.bind "tcp://127.0.0.1:5555"
|
67
|
+
#
|
68
|
+
# @param address [String] Address to bind. Must be a supported protocol.
|
69
|
+
#
|
70
|
+
# @raise [ZMQError] On error.
|
71
|
+
#
|
72
|
+
# @return [RbZMQ::Socket] Self.
|
73
|
+
#
|
74
|
+
def bind(address)
|
75
|
+
ZMQError.error! zmq_socket.bind address
|
76
|
+
self
|
77
|
+
end
|
78
|
+
|
79
|
+
# Connect to given address.
|
80
|
+
#
|
81
|
+
# @example Bind to single remote address
|
82
|
+
# socket = RbZMQ::Socket.new ZMQ::PUSH
|
83
|
+
# socket.connect "tcp://127.0.0.1:5555"
|
84
|
+
#
|
85
|
+
# @example Bind to multiple endpoints
|
86
|
+
# socket = RbZMQ::Socket.new ZMQ::ROUTER
|
87
|
+
# socket.connect "tcp://127.0.0.1:5555"
|
88
|
+
# socket.connect "tcp://127.0.0.1:6666"
|
89
|
+
#
|
90
|
+
# @raise [ZMQError] On error.
|
91
|
+
#
|
92
|
+
# @return [RbZMQ::Socket] Self.
|
93
|
+
#
|
94
|
+
def connect(address)
|
95
|
+
ZMQError.error! zmq_socket.connect address
|
96
|
+
self
|
97
|
+
end
|
98
|
+
|
99
|
+
# Closes the socket. Any unprocessed messages in queue are sent or dropped
|
100
|
+
# depending upon the value of the socket option ZMQ::LINGER.
|
101
|
+
#
|
102
|
+
# @example
|
103
|
+
# socket = RbZMQ::Socket.new ZMQ::PULL
|
104
|
+
# socket.close
|
105
|
+
#
|
106
|
+
# @return [Boolean] Return true upon success *or* when the socket has
|
107
|
+
# already been closed, false otherwise. Use {#close!} to raise an error
|
108
|
+
# on failure.
|
109
|
+
#
|
110
|
+
def close
|
111
|
+
ZMQError.ok? zmq_socket.close
|
112
|
+
end
|
113
|
+
|
114
|
+
# Closes the socket. Any unprocessed messages in queue are sent or dropped
|
115
|
+
# depending upon the value of the socket option ZMQ::LINGER.
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
# socket = RbZMQ::Socket.new ZMQ::PULL
|
119
|
+
# socket.close!
|
120
|
+
#
|
121
|
+
# @raise [ZMQError] Error raised on failure.
|
122
|
+
#
|
123
|
+
# @return [Boolean] True.
|
124
|
+
#
|
125
|
+
def close!
|
126
|
+
ZMQError.error! zmq_socket.close
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
# Set a ZMQ socket object.
|
131
|
+
#
|
132
|
+
# @return [Boolean] True if success, false otherwise.
|
133
|
+
#
|
134
|
+
# @see zmq_setsockopt
|
135
|
+
#
|
136
|
+
def setsockopt(opt, val)
|
137
|
+
ZMQError.ok? zmq_socket.setsockopt(opt, val)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Queues one or more messages for transmission.
|
141
|
+
#
|
142
|
+
# @example Send single message or string
|
143
|
+
# begin
|
144
|
+
# message = RbZMQ::Message.new
|
145
|
+
# socket.send message
|
146
|
+
# rescue RbZMQ::ZMQError => err
|
147
|
+
# puts 'Send failed.'
|
148
|
+
# end
|
149
|
+
#
|
150
|
+
# @example Send multiple messages
|
151
|
+
# socket.send ["A", "B", "C 2"]
|
152
|
+
#
|
153
|
+
# @param messages [RbZMQ::Message, String, #each] A {RbZMQ::Message} or
|
154
|
+
# string message to send, or a list of messages responding to `#each`.
|
155
|
+
#
|
156
|
+
# @param flags [Integer] May contains of the following flags:
|
157
|
+
# * 0 (default) - blocking operation
|
158
|
+
# * ZMQ::DONTWAIT - non-blocking operation
|
159
|
+
# * ZMQ::SNDMORE - this message or all messages
|
160
|
+
# are part of a multi-part message
|
161
|
+
#
|
162
|
+
# @param opts [Hash] Options.
|
163
|
+
#
|
164
|
+
# @option opts [Boolean] :block If method call should block. Will set
|
165
|
+
# ZMQ::DONTWAIT flag if false. Defaults to true.
|
166
|
+
#
|
167
|
+
# @option opts [Boolean] :more If this message or all messages
|
168
|
+
# are part of a multi-part message
|
169
|
+
#
|
170
|
+
# @raise [ZMQError] Raises an error under two conditions:
|
171
|
+
# 1. The message(s) could not be enqueued
|
172
|
+
# 2. When flags is set with ZMQ::DONTWAIT and the socket
|
173
|
+
# returned EAGAIN.
|
174
|
+
#
|
175
|
+
# @return [RbZMQ::Socket] Self.
|
176
|
+
#
|
177
|
+
def send(messages, flags = 0, opts = {})
|
178
|
+
opts, flags = flags, 0 if flags.is_a?(Hash)
|
179
|
+
flags = convert_flags(opts, flags)
|
180
|
+
|
181
|
+
if messages.respond_to?(:each)
|
182
|
+
send_multiple(messages, flags)
|
183
|
+
else
|
184
|
+
send_single(messages, flags)
|
185
|
+
end
|
186
|
+
|
187
|
+
self
|
188
|
+
end
|
189
|
+
|
190
|
+
# Dequeues a message from the underlying queue.
|
191
|
+
#
|
192
|
+
# By default, this is a blocking operation.
|
193
|
+
#
|
194
|
+
# @example
|
195
|
+
# message = socket.recv
|
196
|
+
#
|
197
|
+
# @param flags [Integer] Can be ZMQ::DONTWAIT.
|
198
|
+
#
|
199
|
+
# @param opts [Hash] Options.
|
200
|
+
#
|
201
|
+
# @option opts [Boolean] :block If false operation will be non-blocking.
|
202
|
+
# Defaults to true.
|
203
|
+
#
|
204
|
+
# @option opts [Integer] :timeout Raise a EAGAIN error if nothing was
|
205
|
+
# received within given amount of milliseconds. Defaults
|
206
|
+
# to {DEFAULT_TIMEOUT}. The values `:blocking`, `:infinity`
|
207
|
+
# or `-1` will wait forever.
|
208
|
+
#
|
209
|
+
# @raise [ZMQError] Raise error under two conditions.
|
210
|
+
# 1. The message could not be dequeued
|
211
|
+
# 2. When mode is non-blocking and the socket returned EAGAIN.
|
212
|
+
#
|
213
|
+
# @raise [Errno::EAGAIN] When timeout was reached without receiving
|
214
|
+
# a message.
|
215
|
+
#
|
216
|
+
# @return [RbZMQ::Message] Received message.
|
217
|
+
#
|
218
|
+
def recv(flags = 0, opts = {})
|
219
|
+
opts, flags = flags, 0 if flags.is_a?(Hash)
|
220
|
+
|
221
|
+
with_recv_timeout(opts) do
|
222
|
+
rc = zmq_socket.recvmsg((message = ZMQ::Message.new),
|
223
|
+
convert_flags(opts, flags, [:block]))
|
224
|
+
ZMQError.error! rc
|
225
|
+
RbZMQ::Message.new(message)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
def send_multiple(messages, flags)
|
232
|
+
flgs = flags | ZMQ::SNDMORE
|
233
|
+
last = messages.to_enum(:each).reduce(nil) do |memo, msg|
|
234
|
+
send_single(memo, flgs) if memo
|
235
|
+
RbZMQ::Message.new(msg)
|
236
|
+
end
|
237
|
+
|
238
|
+
send_single(last, flags) if last
|
239
|
+
end
|
240
|
+
|
241
|
+
def send_single(message, flags)
|
242
|
+
zmqmsg = RbZMQ::Message.new(message).to_zmq
|
243
|
+
ZMQError.error! zmq_socket.sendmsg(zmqmsg, flags)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Convert option hash to ZMQ flag list
|
247
|
+
# * :block (! DONTWAIT) defaults to true
|
248
|
+
# * :more (SNDMORE) defaults to false
|
249
|
+
def convert_flags(opts, flags = 0, allowed = [:block, :more])
|
250
|
+
if !opts.fetch(:block, true) && allowed.include?(:block)
|
251
|
+
flags |= ZMQ::DONTWAIT
|
252
|
+
end
|
253
|
+
if opts.fetch(:more, false) && allowed.include?(:more)
|
254
|
+
flags |= ZMQ::SNDMORE
|
255
|
+
end
|
256
|
+
|
257
|
+
flags
|
258
|
+
end
|
259
|
+
|
260
|
+
def poll
|
261
|
+
@poll ||= RbZMQ::Poller.new.tap do |poll|
|
262
|
+
poll.register @zmq_socket, ZMQ::POLLIN
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# RECV timeout using ZMQ::POLLER
|
267
|
+
def with_recv_timeout(opts)
|
268
|
+
timeout = parse_timeout opts[:timeout]
|
269
|
+
|
270
|
+
unless poll.poll(timeout){ return yield }
|
271
|
+
raise Errno::EAGAIN.new "ZMQ socket did not receive anything " \
|
272
|
+
"within #{timeout}ms."
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def parse_timeout(timeout)
|
277
|
+
case timeout
|
278
|
+
when :blocking, :infinity
|
279
|
+
-1
|
280
|
+
when nil
|
281
|
+
DEFAULT_TIMEOUT
|
282
|
+
else
|
283
|
+
Integer(timeout)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|