ffi-rzmq 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 +92 -0
- data/README.rdoc +162 -0
- data/Rakefile +19 -0
- data/examples/local_lat.rb +43 -0
- data/examples/local_lat_zerocopy.rb +24 -0
- data/examples/publish_subscribe.rb +52 -0
- data/examples/remote_lat.rb +53 -0
- data/examples/remote_lat_zerocopy.rb +35 -0
- data/examples/reqrep_poll.rb +49 -0
- data/examples/request_response.rb +23 -0
- data/ffi-rzmq.gemspec +43 -0
- data/lib/ffi-rzmq.rb +71 -0
- data/lib/ffi-rzmq/context.rb +99 -0
- data/lib/ffi-rzmq/exceptions.rb +145 -0
- data/lib/ffi-rzmq/message.rb +210 -0
- data/lib/ffi-rzmq/poll.rb +186 -0
- data/lib/ffi-rzmq/poll_items.rb +98 -0
- data/lib/ffi-rzmq/socket.rb +344 -0
- data/lib/ffi-rzmq/wrapper.rb +120 -0
- data/lib/ffi-rzmq/zmq.rb +147 -0
- data/spec/context_spec.rb +96 -0
- data/spec/reqrep_spec.rb +56 -0
- data/spec/socket_spec.rb +111 -0
- data/spec/spec_helper.rb +38 -0
- data/version.txt +1 -0
- metadata +113 -0
@@ -0,0 +1,210 @@
|
|
1
|
+
|
2
|
+
module ZMQ
|
3
|
+
|
4
|
+
ZMQ_MSG_INIT_SIZE_STR = 'zmq_msg_init_size'.freeze
|
5
|
+
ZMQ_MSG_INIT_DATA_STR = 'zmq_msg_init_data'.freeze
|
6
|
+
ZMQ_MSG_INIT_STR = 'zmq_msg_init'.freeze
|
7
|
+
ZMQ_MSG_CLOSE_STR = 'zmq_msg_close'.freeze
|
8
|
+
ZMQ_MSG_COPY_STR = 'zmq_msg_copy'.freeze
|
9
|
+
ZMQ_MSG_MOVE_STR = 'zmq_msg_move'.freeze
|
10
|
+
ZMQ_MSG_SIZE_STR = 'zmq_msg_size'.freeze
|
11
|
+
|
12
|
+
# The constructor optionally takes a string as an argument. It will
|
13
|
+
# copy this string to native memory in preparation for transmission.
|
14
|
+
# So, don't pass a string unless you intend to send it. Internally it
|
15
|
+
# calls #copy_in_string.
|
16
|
+
#
|
17
|
+
# Call #close to release buffers when you have *not* passed this on
|
18
|
+
# to Socket#send or Socket#recv. Those methods call #close on your
|
19
|
+
# behalf.
|
20
|
+
#
|
21
|
+
# (This class is not really zero-copy. Ruby makes this near impossible.)
|
22
|
+
#
|
23
|
+
# Message represents ruby equivalent of the +zmq_msg_t+ C struct.
|
24
|
+
# Access the underlying memory buffer and the buffer size using the
|
25
|
+
# #data and #size methods respectively.
|
26
|
+
#
|
27
|
+
# It is recommended that this class be composed inside another class for
|
28
|
+
# access to the underlying buffer. The outer wrapper class can provide
|
29
|
+
# nice accessors for the information in the data buffer; a clever
|
30
|
+
# implementation can probably lazily encode/decode the data buffer
|
31
|
+
# on demand. Lots of protocols send more information than is strictly
|
32
|
+
# necessary, so only decode (copy from the 0mq buffer to Ruby) that
|
33
|
+
# which is necessary.
|
34
|
+
#
|
35
|
+
# When you are done using a *received* message object, just let it go out of
|
36
|
+
# scope to release the memory. During the next garbage collection run
|
37
|
+
# it will call the equivalent of #LibZMQ.zmq_msg_close to release
|
38
|
+
# all buffers. Obviously, this automatic collection of message objects
|
39
|
+
# comes at the price of a larger memory footprint (for the
|
40
|
+
# finalizer proc object) and lower performance. If you wanted blistering
|
41
|
+
# performance, Ruby isn't there just yet.
|
42
|
+
#
|
43
|
+
# As noted above, for sent objects the underlying library will call close
|
44
|
+
# for you.
|
45
|
+
#
|
46
|
+
# class MyMessage
|
47
|
+
# def initialize msg_struct = nil
|
48
|
+
# @msg_t = msg_struct ? msg_struct : ZMQ::Message.new
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# def size() @size = @msg_t.size; end
|
52
|
+
#
|
53
|
+
# def decode
|
54
|
+
# @decoded_data = JSON.parse(@msg_t.copy_out_string)
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# def field1
|
58
|
+
# @field1 ||= decode[:field1]
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# def field2
|
62
|
+
# @field2 ||= decode[:field2]
|
63
|
+
# end
|
64
|
+
# ---
|
65
|
+
#
|
66
|
+
# message = Message.new
|
67
|
+
# successful_read = socket.recv message
|
68
|
+
# message = MyMessage.new message if successful_read
|
69
|
+
# puts "field1 is #{message.field1}"
|
70
|
+
#
|
71
|
+
class Message
|
72
|
+
include ZMQ::Util
|
73
|
+
|
74
|
+
def initialize message = nil
|
75
|
+
@state = :uninitialized
|
76
|
+
|
77
|
+
# allocate our own pointer so that we can tell it to *not* zero out
|
78
|
+
# the memory; it's pointless work since the library is going to
|
79
|
+
# overwrite it anyway.
|
80
|
+
@pointer = FFI::MemoryPointer.new LibZMQ::Msg.size, 1, false
|
81
|
+
@struct = LibZMQ::Msg.new @pointer
|
82
|
+
|
83
|
+
if message
|
84
|
+
copy_in_string message
|
85
|
+
else
|
86
|
+
# initialize an empty message structure to receive a message
|
87
|
+
result_code = LibZMQ.zmq_msg_init @struct
|
88
|
+
error_check ZMQ_MSG_INIT_STR, result_code
|
89
|
+
@state = :initialized
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Makes a copy of the ruby +string+ into a native memory buffer so
|
94
|
+
# that libzmq can send it. The underlying library will handle
|
95
|
+
# deallocation of the native memory buffer.
|
96
|
+
#
|
97
|
+
def copy_in_string string
|
98
|
+
copy_in_bytes string, string.size
|
99
|
+
end
|
100
|
+
|
101
|
+
# Makes a copy of +len+ bytes from the ruby string +bytes+. Library
|
102
|
+
# handles deallocation of the native memory buffer.
|
103
|
+
#
|
104
|
+
def copy_in_bytes bytes, len
|
105
|
+
# release any associated buffers if this Message object is being
|
106
|
+
# reused
|
107
|
+
close unless uninitialized?
|
108
|
+
|
109
|
+
data_buffer = LibC.malloc len
|
110
|
+
# writes the exact number of bytes, no null byte to terminate string
|
111
|
+
data_buffer.write_string bytes, len
|
112
|
+
|
113
|
+
# make sure we have a way to deallocate this memory if the object goes
|
114
|
+
# out of scope
|
115
|
+
define_finalizer
|
116
|
+
|
117
|
+
unless RBX
|
118
|
+
result_code = LibZMQ.zmq_msg_init_data @struct.pointer, data_buffer, len, LibZMQ::MessageDeallocator, nil
|
119
|
+
else
|
120
|
+
# no callback for freeing up memory; memory leak!
|
121
|
+
result_code = LibZMQ.zmq_msg_init_data @struct.pointer, data_buffer, len, nil, nil
|
122
|
+
end
|
123
|
+
error_check ZMQ_MSG_INIT_DATA_STR, result_code
|
124
|
+
@state = :initialized
|
125
|
+
end
|
126
|
+
|
127
|
+
# Provides the memory address of the +zmq_msg_t+ struct. Used mostly for
|
128
|
+
# passing to other methods accessing the underlying library that
|
129
|
+
# require a real data address.
|
130
|
+
#
|
131
|
+
def address
|
132
|
+
@struct.pointer
|
133
|
+
end
|
134
|
+
alias :pointer :address
|
135
|
+
|
136
|
+
def copy source
|
137
|
+
result_code = LibZMQ.zmq_msg_copy @struct.pointer, source.address
|
138
|
+
error_check ZMQ_MSG_COPY_STR, result_code
|
139
|
+
@state = :initialized
|
140
|
+
end
|
141
|
+
|
142
|
+
def move source
|
143
|
+
result_code = LibZMQ.zmq_msg_copy @struct.pointer, source.address
|
144
|
+
error_check ZMQ_MSG_MOVE_STR, result_code
|
145
|
+
@state = :initialized
|
146
|
+
end
|
147
|
+
|
148
|
+
# Provides the size of the data buffer for this +zmq_msg_t+ C struct.
|
149
|
+
#
|
150
|
+
def size
|
151
|
+
LibZMQ.zmq_msg_size @struct.pointer
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns a pointer to the data buffer.
|
155
|
+
# This pointer should *never* be freed. It will automatically be freed
|
156
|
+
# when the +message+ object goes out of scope and gets garbage
|
157
|
+
# collected.
|
158
|
+
#
|
159
|
+
def data
|
160
|
+
LibZMQ.zmq_msg_data @struct.pointer
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns the data buffer as a string.
|
164
|
+
#
|
165
|
+
# Note: If this is binary data, it won't print very prettily.
|
166
|
+
#
|
167
|
+
def copy_out_string
|
168
|
+
data.read_string(size)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Manually release the message struct and its associated data
|
172
|
+
# buffer.
|
173
|
+
#
|
174
|
+
# The Message object is still valid after this call and can be used
|
175
|
+
# again for sending or receiving.
|
176
|
+
#
|
177
|
+
def close
|
178
|
+
LibZMQ.zmq_msg_close @struct.pointer
|
179
|
+
remove_finalizer
|
180
|
+
@state = :uninitialized
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def uninitialized?(); :uninitialized == @state; end
|
187
|
+
|
188
|
+
def define_finalizer
|
189
|
+
ObjectSpace.define_finalizer(self, self.class.close(@struct))
|
190
|
+
end
|
191
|
+
|
192
|
+
def remove_finalizer
|
193
|
+
ObjectSpace.undefine_finalizer self
|
194
|
+
end
|
195
|
+
|
196
|
+
# Message finalizer
|
197
|
+
# Note that there is no error checking for the call to #zmq_msg_close.
|
198
|
+
# This is intentional. Since this code runs as a finalizer, there is no
|
199
|
+
# way to catch a raised exception anywhere near where the error actually
|
200
|
+
# occurred in the code, so we just ignore deallocation failures here.
|
201
|
+
def self.close struct
|
202
|
+
Proc.new do
|
203
|
+
# release the data buffer
|
204
|
+
LibZMQ.zmq_msg_close struct.pointer
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
end # class Message
|
209
|
+
|
210
|
+
end # module ZMQ
|
@@ -0,0 +1,186 @@
|
|
1
|
+
|
2
|
+
module ZMQ
|
3
|
+
|
4
|
+
ZMQ_POLL_STR = 'zmq_poll'.freeze
|
5
|
+
|
6
|
+
class Poller
|
7
|
+
include ZMQ::Util
|
8
|
+
|
9
|
+
attr_reader :readables, :writables
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@items = ZMQ::PollItems.new
|
13
|
+
@raw_to_socket = {}
|
14
|
+
@sockets = []
|
15
|
+
@readables = []
|
16
|
+
@writables = []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Checks each poll item for selectability based on the poll items'
|
20
|
+
# registered +events+. Will block for up to +timeout+ milliseconds
|
21
|
+
# A millisecond is 1/1000 of a second, so to block for 1 second
|
22
|
+
# pass the value "1000" to #poll.
|
23
|
+
#
|
24
|
+
# Pass "-1" or +:blocking+ for +timeout+ for this call to block
|
25
|
+
# indefinitely.
|
26
|
+
#
|
27
|
+
# May raise a ZMQ::PollError exception. This occurs when one of the
|
28
|
+
# registered sockets belongs to an application thread in another
|
29
|
+
# Context.
|
30
|
+
#
|
31
|
+
def poll timeout = :blocking
|
32
|
+
unless @items.empty?
|
33
|
+
timeout = adjust timeout
|
34
|
+
items_triggered = LibZMQ.zmq_poll @items.address, @items.size, timeout
|
35
|
+
error_check ZMQ_POLL_STR, items_triggered >= 0 ? 0 : items_triggered
|
36
|
+
update_selectables
|
37
|
+
items_hash
|
38
|
+
else
|
39
|
+
{}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# The non-blocking version of #poll. See the #poll description for
|
44
|
+
# potential exceptions.
|
45
|
+
#
|
46
|
+
# May raise a ZMQ::PollError exception. This occurs when one of the
|
47
|
+
# registered sockets belongs to an application thread in another
|
48
|
+
# Context.
|
49
|
+
#
|
50
|
+
def poll_nonblock
|
51
|
+
poll 0
|
52
|
+
end
|
53
|
+
|
54
|
+
# Register the +sock+ for +events+. This method is idempotent meaning
|
55
|
+
# it can be called multiple times with the same data and the socket
|
56
|
+
# will only get registered at most once. Calling multiple times with
|
57
|
+
# different values for +events+ will OR the event information together.
|
58
|
+
#
|
59
|
+
# Does not raise any exceptions.
|
60
|
+
#
|
61
|
+
def register sock, events = ZMQ::POLLIN | ZMQ::POLLOUT, fd = 0
|
62
|
+
return unless sock || !fd.zero? || !events.zero?
|
63
|
+
|
64
|
+
@poll_items_dirty = true
|
65
|
+
item = @items.get(@sockets.index(sock))
|
66
|
+
|
67
|
+
unless item
|
68
|
+
@sockets << sock
|
69
|
+
item = LibZMQ::PollItem.new
|
70
|
+
@raw_to_socket[item.socket] = sock
|
71
|
+
|
72
|
+
case sock
|
73
|
+
when ZMQ::Socket, Socket
|
74
|
+
item[:socket] = sock.socket
|
75
|
+
item[:fd] = 0
|
76
|
+
else
|
77
|
+
item[:socket] = 0
|
78
|
+
item[:fd] = fd
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
item[:events] |= events
|
83
|
+
|
84
|
+
@items << item
|
85
|
+
end
|
86
|
+
|
87
|
+
# Deregister the +sock+ for +events+.
|
88
|
+
#
|
89
|
+
# Does not raise any exceptions.
|
90
|
+
#
|
91
|
+
def deregister sock, events, fd = 0
|
92
|
+
return unless sock || !fd.zero?
|
93
|
+
|
94
|
+
item = @items.get(@sockets.index(sock))
|
95
|
+
|
96
|
+
if item
|
97
|
+
# change the value in place
|
98
|
+
item[:events] ^= events
|
99
|
+
|
100
|
+
delete sock if items[:events].zero?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# A helper method to register a +sock+ as readable events only.
|
105
|
+
#
|
106
|
+
def register_readable sock
|
107
|
+
register sock, ZMQ::POLLIN, 0
|
108
|
+
end
|
109
|
+
|
110
|
+
# A helper method to register a +sock+ for writable events only.
|
111
|
+
#
|
112
|
+
def register_writable sock
|
113
|
+
register sock, ZMQ::POLLOUT, 0
|
114
|
+
end
|
115
|
+
|
116
|
+
# A helper method to deregister a +sock+ for readable events.
|
117
|
+
#
|
118
|
+
def deregister_readable sock
|
119
|
+
deregister sock, ZMQ::POLLIN, 0
|
120
|
+
end
|
121
|
+
|
122
|
+
# A helper method to deregister a +sock+ for writable events.
|
123
|
+
#
|
124
|
+
def deregister_writable sock
|
125
|
+
deregister sock, ZMQ::POLLOUT, 0
|
126
|
+
end
|
127
|
+
|
128
|
+
# Deletes the +sock+ for all subscribed events.
|
129
|
+
#
|
130
|
+
def delete sock
|
131
|
+
if index = @sockets.index(sock)
|
132
|
+
@items.delete_at index
|
133
|
+
@sockets.delete sock
|
134
|
+
@raw_to_socket.delete sock.socket
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def size(); @items.size; end
|
139
|
+
|
140
|
+
def inspect
|
141
|
+
@items.inspect
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_s(); inspect; end
|
145
|
+
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def items_hash
|
150
|
+
hsh = {}
|
151
|
+
|
152
|
+
@items.each do |poll_item|
|
153
|
+
hsh[@raw_to_socket[poll_item.socket]] = poll_item
|
154
|
+
end
|
155
|
+
|
156
|
+
hsh
|
157
|
+
end
|
158
|
+
|
159
|
+
def update_selectables
|
160
|
+
@readables.clear
|
161
|
+
@writables.clear
|
162
|
+
|
163
|
+
@items.each do |poll_item|
|
164
|
+
@readables << @raw_to_socket[poll_item.socket] if poll_item.readable?
|
165
|
+
@writables << @raw_to_socket[poll_item.socket] if poll_item.writable?
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Convert the timeout value to something usable by
|
170
|
+
# the library.
|
171
|
+
#
|
172
|
+
# -1 or :blocking should be converted to -1.
|
173
|
+
#
|
174
|
+
# Users will pass in values measured as
|
175
|
+
# milliseconds, so we need to convert that value to
|
176
|
+
# microseconds for the library.
|
177
|
+
def adjust timeout
|
178
|
+
if :blocking == timeout || -1 == timeout
|
179
|
+
-1
|
180
|
+
else
|
181
|
+
timeout *= 1000
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
end # module ZMQ
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
module ZMQ
|
3
|
+
class PollItems
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@element_size = LibZMQ::PollItem.size
|
8
|
+
@store = nil
|
9
|
+
@items = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def size; @items.size; end
|
13
|
+
|
14
|
+
def empty?; @items.empty?; end
|
15
|
+
|
16
|
+
def address
|
17
|
+
clean
|
18
|
+
@store
|
19
|
+
end
|
20
|
+
|
21
|
+
def get index
|
22
|
+
unless @items.empty? || index.nil?
|
23
|
+
clean
|
24
|
+
|
25
|
+
# pointer arithmetic in ruby! whee!
|
26
|
+
pointer = @store + (@element_size * index)
|
27
|
+
|
28
|
+
# cast the memory to a PollItem
|
29
|
+
LibZMQ::PollItem.new pointer
|
30
|
+
end
|
31
|
+
end
|
32
|
+
alias :[] :get
|
33
|
+
|
34
|
+
def <<(obj)
|
35
|
+
@dirty = true
|
36
|
+
@items << obj
|
37
|
+
end
|
38
|
+
alias :push :<<
|
39
|
+
|
40
|
+
def delete_at index
|
41
|
+
unless @items.empty?
|
42
|
+
@items.delete_at index
|
43
|
+
@dirty = true
|
44
|
+
clean
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def each &blk
|
49
|
+
clean
|
50
|
+
index = 0
|
51
|
+
until index >= @items.size do
|
52
|
+
struct = get index
|
53
|
+
yield struct
|
54
|
+
index += 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def each_with_index &blk
|
59
|
+
clean
|
60
|
+
index = 0
|
61
|
+
until index >= @items.size do
|
62
|
+
struct = get index
|
63
|
+
yield struct, index
|
64
|
+
index += 1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def inspect
|
69
|
+
clean
|
70
|
+
str = ""
|
71
|
+
each { |item| str << "ptr [#{item[:socket]}], events [#{item[:events]}], revents [#{item[:revents]}], " }
|
72
|
+
str.chop.chop
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_s(); inspect; end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# Allocate a contiguous chunk of memory and copy over the PollItem structs
|
80
|
+
# to this block. Note that the old +@store+ value goes out of scope so when
|
81
|
+
# it is garbage collected that native memory should be automatically freed.
|
82
|
+
def clean
|
83
|
+
if @dirty
|
84
|
+
@store = FFI::MemoryPointer.new @element_size, @items.size, false
|
85
|
+
|
86
|
+
# copy over
|
87
|
+
offset = 0
|
88
|
+
@items.each do |item|
|
89
|
+
LibC.memcpy(@store + offset, item, @element_size)
|
90
|
+
offset += @element_size
|
91
|
+
end
|
92
|
+
|
93
|
+
@dirty = false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end # class PollItems
|
98
|
+
end # module ZMQ
|