ffi-rzmq 0.5.1 → 0.6.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.
@@ -15,10 +15,12 @@ module ZMQ
15
15
  # calls #copy_in_string.
16
16
  #
17
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.
18
+ # to Socket#send. That method calls #close on your behalf.
20
19
  #
21
- # (This class is not really zero-copy. Ruby makes this near impossible.)
20
+ # (This class is not really zero-copy. Ruby makes this near impossible
21
+ # since Ruby objects can be relocated in memory by the GC at any
22
+ # time. There is no way to peg them to native memory or have them
23
+ # use non-movable native memory as backing store.)
22
24
  #
23
25
  # Message represents ruby equivalent of the +zmq_msg_t+ C struct.
24
26
  # Access the underlying memory buffer and the buffer size using the
@@ -32,41 +34,54 @@ module ZMQ
32
34
  # necessary, so only decode (copy from the 0mq buffer to Ruby) that
33
35
  # which is necessary.
34
36
  #
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.
37
+ # When you are done using a *received* message object, call #close to
38
+ # release the associated buffers.
42
39
  #
43
40
  # As noted above, for sent objects the underlying library will call close
44
41
  # for you.
45
42
  #
46
43
  # class MyMessage
44
+ # class Layout < FFI::Struct
45
+ # layout :value1, :uint8,
46
+ # :value2, :uint64,
47
+ # :value3, :uint32,
48
+ # :value4, [:char, 30]
49
+ # end
50
+ #
47
51
  # def initialize msg_struct = nil
48
- # @msg_t = msg_struct ? msg_struct : ZMQ::Message.new
52
+ # if msg_struct
53
+ # @msg_t = msg_struct
54
+ # @data = Layout.new(@msg_t.data)
55
+ # else
56
+ # @pointer = FFI::MemoryPointer.new :byte, Layout.size, true
57
+ # @data = Layout.new @pointer
58
+ # end
49
59
  # end
50
60
  #
51
61
  # def size() @size = @msg_t.size; end
52
62
  #
53
- # def decode
54
- # @decoded_data = JSON.parse(@msg_t.copy_out_string)
63
+ # def value1
64
+ # @data[:value1]
55
65
  # end
56
66
  #
57
- # def field1
58
- # @field1 ||= decode[:field1]
67
+ # def value4
68
+ # @data[:value4].to_ptr.read_string
59
69
  # end
60
70
  #
61
- # def field2
62
- # @field2 ||= decode[:field2]
71
+ # def value1=(val)
72
+ # @data[:value1] = val
63
73
  # end
64
- # ---
74
+ #
75
+ # def create_sendable_message
76
+ # msg = Message.new
77
+ # msg.copy_in_bytes @pointer, Layout.size
78
+ # end
79
+ #
65
80
  #
66
81
  # message = Message.new
67
82
  # successful_read = socket.recv message
68
83
  # message = MyMessage.new message if successful_read
69
- # puts "field1 is #{message.field1}"
84
+ # puts "value1 is #{message.value1}"
70
85
  #
71
86
  class Message
72
87
  include ZMQ::Util
@@ -84,9 +99,8 @@ module ZMQ
84
99
  copy_in_string message
85
100
  else
86
101
  # initialize an empty message structure to receive a message
87
- result_code = LibZMQ.zmq_msg_init @struct
102
+ result_code = LibZMQ.zmq_msg_init @pointer
88
103
  error_check ZMQ_MSG_INIT_STR, result_code
89
- @state = :initialized
90
104
  end
91
105
  end
92
106
 
@@ -94,6 +108,11 @@ module ZMQ
94
108
  # that libzmq can send it. The underlying library will handle
95
109
  # deallocation of the native memory buffer.
96
110
  #
111
+ # Can only be initialized via #copy_in_string or #copy_in_bytes once.
112
+ #
113
+ # Can raise a MessageError when #copy_in_string or #copy_in_bytes is
114
+ # called multiple times on the same instance.
115
+ #
97
116
  def copy_in_string string
98
117
  copy_in_bytes string, string.size if string
99
118
  end
@@ -101,25 +120,23 @@ module ZMQ
101
120
  # Makes a copy of +len+ bytes from the ruby string +bytes+. Library
102
121
  # handles deallocation of the native memory buffer.
103
122
  #
123
+ # Can only be initialized via #copy_in_string or #copy_in_bytes once.
124
+ #
125
+ # Can raise a MessageError when #copy_in_string or #copy_in_bytes is
126
+ # called multiple times on the same instance.
127
+ #
104
128
  def copy_in_bytes bytes, len
105
- # release any associated buffers if this Message object is being
106
- # reused
107
- close unless uninitialized? # FIXME: this is a bug waiting to happen
129
+ raise MessageError.new "#{self}", -1, -1, "This object cannot be reused; allocate a new one!" if initialized?
108
130
 
109
131
  data_buffer = LibC.malloc len
110
132
  # writes the exact number of bytes, no null byte to terminate string
111
133
  data_buffer.write_string bytes, len
112
134
 
113
- # make sure we have a way to deallocate this memory if the object goes
114
- # out of scope
115
- define_finalizer
135
+ # use libC to call free on the data buffer; earlier versions used an
136
+ # FFI::Function here that called back into Ruby, but Rubinius won't
137
+ # support that and there are issues with the other runtimes too
138
+ result_code = LibZMQ.zmq_msg_init_data @pointer, data_buffer, len, LibC::Free, nil
116
139
 
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
140
  error_check ZMQ_MSG_INIT_DATA_STR, result_code
124
141
  @state = :initialized
125
142
  end
@@ -129,18 +146,18 @@ module ZMQ
129
146
  # require a real data address.
130
147
  #
131
148
  def address
132
- @struct.pointer
149
+ @pointer
133
150
  end
134
151
  alias :pointer :address
135
152
 
136
153
  def copy source
137
- result_code = LibZMQ.zmq_msg_copy @struct.pointer, source.address
154
+ result_code = LibZMQ.zmq_msg_copy @pointer, source.address
138
155
  error_check ZMQ_MSG_COPY_STR, result_code
139
156
  @state = :initialized
140
157
  end
141
158
 
142
159
  def move source
143
- result_code = LibZMQ.zmq_msg_copy @struct.pointer, source.address
160
+ result_code = LibZMQ.zmq_msg_copy @pointer, source.address
144
161
  error_check ZMQ_MSG_MOVE_STR, result_code
145
162
  @state = :initialized
146
163
  end
@@ -148,7 +165,7 @@ module ZMQ
148
165
  # Provides the size of the data buffer for this +zmq_msg_t+ C struct.
149
166
  #
150
167
  def size
151
- LibZMQ.zmq_msg_size @struct.pointer
168
+ LibZMQ.zmq_msg_size @pointer
152
169
  end
153
170
 
154
171
  # Returns a pointer to the data buffer.
@@ -157,7 +174,7 @@ module ZMQ
157
174
  # collected.
158
175
  #
159
176
  def data
160
- LibZMQ.zmq_msg_data @struct.pointer
177
+ LibZMQ.zmq_msg_data @pointer
161
178
  end
162
179
 
163
180
  # Returns the data buffer as a string.
@@ -171,22 +188,69 @@ module ZMQ
171
188
  # Manually release the message struct and its associated data
172
189
  # buffer.
173
190
  #
174
- # The Message object is still valid after this call and can be used
175
- # again for sending or receiving.
191
+ def close
192
+ LibZMQ.zmq_msg_close @pointer
193
+ end
194
+
195
+
196
+ private
197
+
198
+ def initialized?(); :initialized == @state; end
199
+
200
+ end # class Message
201
+
202
+
203
+
204
+ # A subclass of #Message that includes finalizers for deallocating
205
+ # native memory when this object is garbage collected. Note that on
206
+ # certain Ruby runtimes the use of finalizers can add 10s of
207
+ # microseconds of overhead for each message. The convenience comes
208
+ # at a price.
209
+ #
210
+ # The constructor optionally takes a string as an argument. It will
211
+ # copy this string to native memory in preparation for transmission.
212
+ # So, don't pass a string unless you intend to send it. Internally it
213
+ # calls #copy_in_string.
214
+ #
215
+ # Call #close to release buffers when you have *not* passed this on
216
+ # to Socket#send. That method calls #close on your behalf.
217
+ #
218
+ # When you are done using a *received* message object, just let it go out of
219
+ # scope to release the memory. During the next garbage collection run
220
+ # it will call the equivalent of #LibZMQ.zmq_msg_close to release
221
+ # all buffers. Obviously, this automatic collection of message objects
222
+ # comes at the price of a larger memory footprint (for the
223
+ # finalizer proc object) and lower performance. If you wanted blistering
224
+ # performance, Ruby isn't there just yet.
225
+ #
226
+ # As noted above, for sent objects the underlying library will call close
227
+ # for you.
228
+ #
229
+ class ManagedMessage < Message
230
+ # Makes a copy of +len+ bytes from the ruby string +bytes+. Library
231
+ # handles deallocation of the native memory buffer.
232
+ #
233
+ def copy_in_bytes bytes, len
234
+ super
235
+
236
+ # make sure we have a way to deallocate this memory if the object goes
237
+ # out of scope
238
+ define_finalizer
239
+ end
240
+
241
+ # Manually release the message struct and its associated data
242
+ # buffer.
176
243
  #
177
244
  def close
178
- LibZMQ.zmq_msg_close @struct.pointer
245
+ super
179
246
  remove_finalizer
180
- @state = :uninitialized
181
247
  end
182
248
 
183
249
 
184
250
  private
185
251
 
186
- def uninitialized?(); :uninitialized == @state; end
187
-
188
252
  def define_finalizer
189
- ObjectSpace.define_finalizer(self, self.class.close(@struct))
253
+ ObjectSpace.define_finalizer(self, self.class.close(@pointer))
190
254
  end
191
255
 
192
256
  def remove_finalizer
@@ -198,13 +262,13 @@ module ZMQ
198
262
  # This is intentional. Since this code runs as a finalizer, there is no
199
263
  # way to catch a raised exception anywhere near where the error actually
200
264
  # occurred in the code, so we just ignore deallocation failures here.
201
- def self.close struct
265
+ def self.close ptr
202
266
  Proc.new do
203
267
  # release the data buffer
204
- LibZMQ.zmq_msg_close struct.pointer
268
+ LibZMQ.zmq_msg_close ptr
205
269
  end
206
270
  end
207
271
 
208
- end # class Message
272
+ end # class ManagedMessage
209
273
 
210
274
  end # module ZMQ
@@ -86,7 +86,7 @@ module ZMQ
86
86
  # copy over
87
87
  offset = 0
88
88
  @items.each do |item|
89
- LibC.memcpy(@store + offset, item, @element_size)
89
+ LibC.memcpy(@store + offset, item.pointer, @element_size)
90
90
  offset += @element_size
91
91
  end
92
92
 
@@ -13,29 +13,38 @@ module ZMQ
13
13
  class Socket
14
14
  include ZMQ::Util
15
15
 
16
- attr_reader :socket
16
+ attr_reader :socket, :name
17
17
 
18
- # By default, this class uses ZMQ::Message for regular Ruby
19
- # memory management.
18
+ # Allocates a socket of type +type+ for sending and receiving data.
20
19
  #
21
20
  # +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB,
22
21
  # ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH,
23
22
  # ZMQ::XREQ or ZMQ::XREP.
24
23
  #
24
+ # By default, this class uses ZMQ::Message for manual
25
+ # memory management. For automatic garbage collection of received messages,
26
+ # it is possible to override the :receiver_class to use ZMQ::ManagedMessage.
27
+ #
28
+ # sock = Socket.new(Context.new, ZMQ::REQ, :receiver_class => ZMQ::ManagedMessage)
29
+ #
30
+ # Advanced users may want to replace the receiver class with their
31
+ # own custom class. The custom class must conform to the same public API
32
+ # as ZMQ::Message.
33
+ #
25
34
  # Can raise two kinds of exceptions depending on the error.
26
35
  # ContextError:: Raised when a socket operation is attempted on a terminated
27
36
  # #Context. See #ContextError.
28
37
  # SocketError:: See all of the possibilities in the docs for #SocketError.
29
38
  #
30
- def initialize context_ptr, type
31
- # maybe at some point we'll want to allow users to override this with their
32
- # own classes? Or is this a YAGNI mistake?
33
- @sender_klass = ZMQ::Message
34
- @receiver_klass = ZMQ::Message
39
+ def initialize context_ptr, type, opts = {:receiver_class => ZMQ::Message}
40
+ # users may override the classes used for receiving; class must conform to the
41
+ # same public API as ZMQ::Message
42
+ @receiver_klass = opts[:receiver_class]
35
43
 
36
44
  unless context_ptr.null?
37
45
  @socket = LibZMQ.zmq_socket context_ptr, type
38
46
  error_check ZMQ_SOCKET_STR, @socket.null? ? 1 : 0
47
+ @name = SocketTypeNameMap[type]
39
48
  else
40
49
  raise ContextError.new ZMQ_SOCKET_STR, 0, ETERM, "Context pointer was null"
41
50
  end
@@ -64,15 +73,18 @@ module ZMQ
64
73
  # SocketError:: See all of the possibilities in the docs for #SocketError.
65
74
  #
66
75
  def setsockopt option_name, option_value, option_len = nil
76
+ option_value = sanitize_value option_name, option_value
77
+ option_len ||= option_value.size
78
+
67
79
  begin
68
80
  case option_name
69
- when HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, MCAST_LOOP
70
- option_value_ptr = LibC.malloc option_value.size
81
+ when HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, MCAST_LOOP, SNDBUF, RCVBUF
82
+ option_value_ptr = LibC.malloc option_len
71
83
  option_value_ptr.write_long option_value
72
84
 
73
85
  when IDENTITY, SUBSCRIBE, UNSUBSCRIBE
74
86
  # note: not checking errno for failed memory allocations :(
75
- option_value_ptr = LibC.malloc option_value.size
87
+ option_value_ptr = LibC.malloc option_len
76
88
  option_value_ptr.write_string option_value
77
89
 
78
90
  else
@@ -81,7 +93,7 @@ module ZMQ
81
93
  error_check ZMQ_SETSOCKOPT_STR, EINVAL
82
94
  end
83
95
 
84
- result_code = LibZMQ.zmq_setsockopt @socket, option_name, option_value_ptr, option_len || option_value.size
96
+ result_code = LibZMQ.zmq_setsockopt @socket, option_name, option_value_ptr, option_len
85
97
  error_check ZMQ_SETSOCKOPT_STR, result_code
86
98
  ensure
87
99
  LibC.free option_value_ptr unless option_value_ptr.nil? || option_value_ptr.null?
@@ -188,8 +200,8 @@ module ZMQ
188
200
  error_check ZMQ_CLOSE_STR, result_code
189
201
  end
190
202
 
191
- # Queues the message for transmission. Message is assumed to be an instance or
192
- # subclass of #Message.
203
+ # Queues the message for transmission. Message is assumed to conform to the
204
+ # same public API as #Message.
193
205
  #
194
206
  # +flags+ may take two values:
195
207
  # * 0 (default) - blocking operation
@@ -197,13 +209,14 @@ module ZMQ
197
209
  # * ZMQ::SNDMORE - this message is part of a multi-part message
198
210
  #
199
211
  # Returns true when the message was successfully enqueued.
200
- # Returns false when the message could not be enqueued *and* +flags+ is set
201
- # with ZMQ::NOBLOCK.
212
+ # Returns false under two conditions.
213
+ # 1. The message could not be enqueued
214
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
202
215
  #
203
216
  # The application code is *not* responsible for handling the +message+ object
204
- # lifecycle when #send return ZMQ::NOBLOCK or it raises an exception. The
217
+ # lifecycle when #send returns successfully or it raises an exception. The
205
218
  # #send method takes ownership of the +message+ and its associated buffers.
206
- # A failed call will release the +message+ data buffer.
219
+ # Both successful and failed calls will release the +message+ data buffer.
207
220
  #
208
221
  # Again, once a +message+ object has been passed to this method,
209
222
  # do not try to access its #data buffer anymore. The 0mq library now owns it.
@@ -239,7 +252,7 @@ module ZMQ
239
252
  # SocketError:: See all of the possibilities in the docs for #SocketError.
240
253
  #
241
254
  def send_string message_string, flags = 0
242
- message = @sender_klass.new
255
+ message = Message.new
243
256
  message.copy_in_string message_string
244
257
  result = send message, flags
245
258
  result
@@ -327,6 +340,17 @@ module ZMQ
327
340
  [FFI::MemoryPointer.new(255), length]
328
341
  end
329
342
  end
343
+
344
+ def sanitize_value option_name, option_value
345
+ case option_name
346
+ when HWM, AFFINITY, SNDBUF, RCVBUF
347
+ option_value.abs
348
+ when MCAST_LOOP
349
+ option_value ? 1 : 0
350
+ else
351
+ option_value
352
+ end
353
+ end
330
354
 
331
355
  def define_finalizer
332
356
  ObjectSpace.define_finalizer(self, self.class.close(@socket))
@@ -3,7 +3,7 @@ require 'ffi' unless RBX # external gem
3
3
  module LibC
4
4
  extend FFI::Library
5
5
  # figures out the correct libc for each platform including Windows
6
- ffi_lib FFI::Library::LIBC unless RBX
6
+ library = ffi_lib(FFI::Library::LIBC).first
7
7
 
8
8
  # memory allocators
9
9
  attach_function :malloc, [:size_t], :pointer
@@ -11,6 +11,9 @@ module LibC
11
11
  attach_function :valloc, [:size_t], :pointer
12
12
  attach_function :realloc, [:pointer, :size_t], :pointer
13
13
  attach_function :free, [:pointer], :void
14
+
15
+ # get a pointer to the free function; used for ZMQ::Message deallocation
16
+ Free = library.find_symbol('free')
14
17
 
15
18
  # memory movers
16
19
  attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
@@ -46,12 +49,6 @@ module LibZMQ
46
49
  attach_function :zmq_msg_copy, [:pointer, :pointer], :int
47
50
  attach_function :zmq_msg_move, [:pointer, :pointer], :int
48
51
 
49
- unless RBX
50
- MessageDeallocator = FFI::Function.new(:void, [:pointer, :pointer]) do |data_ptr, hint_ptr|
51
- LibC.free data_ptr
52
- end
53
- MessageDeallocator.autorelease = false
54
- end
55
52
 
56
53
  # Used for casting pointers back to the struct
57
54
  class Msg < FFI::Struct
data/lib/ffi-rzmq/zmq.rb CHANGED
@@ -11,6 +11,18 @@ module ZMQ
11
11
  XREP = 6
12
12
  PULL = UPSTREAM = 7
13
13
  PUSH = DOWNSTREAM = 8
14
+
15
+ SocketTypeNameMap = {
16
+ PAIR => "PAIR",
17
+ PUB => "PUB",
18
+ SUB => "SUB",
19
+ REQ => "REQ",
20
+ REP => "REP",
21
+ XREQ => "XREQ",
22
+ XREP => "XREP",
23
+ PULL => "PULL",
24
+ PUSH => "PUSH"
25
+ }
14
26
 
15
27
  # Socket options
16
28
  HWM = 1
@@ -120,18 +132,19 @@ module ZMQ
120
132
  # send/recv. True only when #errno is EGAIN and +result_code+ is non-zero.
121
133
  #
122
134
  def error_check_nonblock result_code
123
- queue_operation = eagain? && !result_code.zero? ? false : true
124
- queue_operation
135
+ !result_code.zero? && eagain? ? false : true
125
136
  end
126
137
 
127
138
  def raise_error source, result_code
128
139
  case source
129
- when ZMQ_SOCKET_STR, ZMQ_SETSOCKOPT_STR, ZMQ_GETSOCKOPT_STR, ZMQ_BIND_STR, ZMQ_CONNECT_STR, ZMQ_SEND_STR, ZMQ_RECV_STR
140
+ when ZMQ_SEND_STR, ZMQ_RECV_STR, ZMQ_SOCKET_STR, ZMQ_SETSOCKOPT_STR, ZMQ_GETSOCKOPT_STR, ZMQ_BIND_STR, ZMQ_CONNECT_STR
130
141
  raise SocketError.new source, result_code, errno, error_string
131
142
  when ZMQ_INIT_STR, ZMQ_TERM_STR
132
143
  raise ContextError.new source, result_code, errno, error_string
133
144
  when ZMQ_POLL_STR
134
145
  raise PollError.new source, result_code, errno, error_string
146
+ when ZMQ_MSG_INIT_STR, ZMQ_MSG_INIT_DATA_STR, ZMQ_MSG_COPY_STR, ZMQ_MSG_MOVE_STR
147
+ raise MessageError.new source, result_code, errno, error_string
135
148
  else
136
149
  raise ZeroMQError.new source, result_code, -1,
137
150
  "Source [#{source}] does not match any zmq_* strings, rc [#{result_code}], errno [#{errno}], error_string [#{error_string}]"