rbzmq 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|