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.
@@ -0,0 +1,344 @@
1
+
2
+ module ZMQ
3
+
4
+ ZMQ_SOCKET_STR = 'zmq_socket'.freeze unless defined? ZMQ_SOCKET_STR
5
+ ZMQ_SETSOCKOPT_STR = 'zmq_setsockopt'.freeze
6
+ ZMQ_GETSOCKOPT_STR = 'zmq_getsockopt'.freeze
7
+ ZMQ_BIND_STR = 'zmq_bind'.freeze
8
+ ZMQ_CONNECT_STR = 'zmq_connect'.freeze
9
+ ZMQ_CLOSE_STR = 'zmq_close'.freeze
10
+ ZMQ_SEND_STR = 'zmq_send'.freeze
11
+ ZMQ_RECV_STR = 'zmq_recv'.freeze
12
+
13
+ class Socket
14
+ include ZMQ::Util
15
+
16
+ attr_reader :socket
17
+
18
+ # By default, this class uses ZMQ::Message for regular Ruby
19
+ # memory management.
20
+ #
21
+ # +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB,
22
+ # ZMQ::SUB, ZMQ::PAIR, ZMQ::UPSTREAM, ZMQ::DOWNSTREAM,
23
+ # ZMQ::XREQ or ZMQ::XREP.
24
+ #
25
+ # Can raise two kinds of exceptions depending on the error.
26
+ # ContextError:: Raised when a socket operation is attempted on a terminated
27
+ # #Context. See #ContextError.
28
+ # SocketError:: See all of the possibilities in the docs for #SocketError.
29
+ #
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
35
+
36
+ unless context_ptr.null?
37
+ @socket = LibZMQ.zmq_socket context_ptr, type
38
+ error_check ZMQ_SOCKET_STR, @socket.null? ? 1 : 0
39
+ else
40
+ raise ContextError.new ZMQ_SOCKET_STR, 0, ETERM, "Context pointer was null"
41
+ end
42
+
43
+ define_finalizer
44
+ end
45
+
46
+ # Set the queue options on this socket.
47
+ #
48
+ # Valid +option_name+ values that take a numeric +option_value+ are:
49
+ # ZMQ::HWM
50
+ # ZMQ::SWAP
51
+ # ZMQ::AFFINITY
52
+ # ZMQ::RATE
53
+ # ZMQ::RECOVERY_IVL
54
+ # ZMQ::MCAST_LOOP
55
+ #
56
+ # Valid +option_name+ values that take a string +option_value+ are:
57
+ # ZMQ::IDENTITY
58
+ # ZMQ::SUBSCRIBE
59
+ # ZMQ::UNSUBSCRIBE
60
+ #
61
+ # Can raise two kinds of exceptions depending on the error.
62
+ # ContextError:: Raised when a socket operation is attempted on a terminated
63
+ # #Context. See #ContextError.
64
+ # SocketError:: See all of the possibilities in the docs for #SocketError.
65
+ #
66
+ def setsockopt option_name, option_value, option_len = nil
67
+ begin
68
+ case option_name
69
+ when HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, MCAST_LOOP
70
+ option_value_ptr = LibC.malloc option_value.size
71
+ option_value_ptr.write_long option_value
72
+
73
+ when IDENTITY, SUBSCRIBE, UNSUBSCRIBE
74
+ # note: not checking errno for failed memory allocations :(
75
+ option_value_ptr = LibC.malloc option_value.size
76
+ option_value_ptr.write_string option_value
77
+
78
+ else
79
+ # we didn't understand the passed option argument
80
+ # will force a raise due to EINVAL being non-zero
81
+ error_check ZMQ_SETSOCKOPT_STR, EINVAL
82
+ end
83
+
84
+ result_code = LibZMQ.zmq_setsockopt @socket, option_name, option_value_ptr, option_len || option_value.size
85
+ error_check ZMQ_SETSOCKOPT_STR, result_code
86
+ ensure
87
+ LibC.free option_value_ptr unless option_value_ptr.nil? || option_value_ptr.null?
88
+ end
89
+ end
90
+
91
+ # Get the options set on this socket. Returns a value dependent upon
92
+ # the +option_name+ requested.
93
+ #
94
+ # Valid +option_name+ values and their return types:
95
+ # ZMQ::RCVMORE - boolean
96
+ # ZMQ::HWM - integer
97
+ # ZMQ::SWAP - integer
98
+ # ZMQ::AFFINITY - bitmap in an integer
99
+ # ZMQ::IDENTITY - string
100
+ # ZMQ::RATE - integer
101
+ # ZMQ::RECOVERY_IVL - integer
102
+ # ZMQ::MCAST_LOOP - boolean
103
+ # ZMQ::SNDBUF - integer
104
+ # ZMQ::RCVBUF - integer
105
+ #
106
+ # Can raise two kinds of exceptions depending on the error.
107
+ # ContextError:: Raised when a socket operation is attempted on a terminated
108
+ # #Context. See #ContextError.
109
+ # SocketError:: See all of the possibilities in the docs for #SocketError.
110
+ #
111
+ def getsockopt option_name
112
+ begin
113
+ option_value = FFI::MemoryPointer.new :pointer
114
+ option_length = FFI::MemoryPointer.new :size_t
115
+
116
+ unless [RCVMORE, HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, MCAST_LOOP,
117
+ IDENTITY, SNDBUF, RCVBUF].include? option_name
118
+ # we didn't understand the passed option argument
119
+ # will force a raise
120
+ error_check ZMQ_SETSOCKOPT_STR, -1
121
+ end
122
+
123
+ option_value, option_length = alloc_temp_sockopt_buffers option_name
124
+
125
+ result_code = LibZMQ.zmq_getsockopt @socket, option_name, option_value, option_length
126
+ error_check ZMQ_GETSOCKOPT_STR, result_code
127
+ ret = 0
128
+
129
+ case option_name
130
+ when RCVMORE, MCAST_LOOP
131
+ # boolean return
132
+ ret = option_value.read_long_long != 0
133
+ when HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, SNDBUF, RCVBUF
134
+ ret = option_value.read_long_long
135
+ when IDENTITY
136
+ ret = option_value.read_string(option_length.read_long_long)
137
+ end
138
+
139
+ ret
140
+ end
141
+ end
142
+
143
+ # Convenience method for checking on additional message parts.
144
+ #
145
+ # Equivalent to Socket#getsockopt ZMQ::RCVMORE
146
+ #
147
+ def more_parts?
148
+ getsockopt ZMQ::RCVMORE
149
+ end
150
+
151
+ # Convenience method for getting the value of the socket IDENTITY.
152
+ #
153
+ def identity
154
+ getsockopt ZMQ::IDENTITY
155
+ end
156
+
157
+ # Convenience method for setting the value of the socket IDENTITY.
158
+ #
159
+ def identity= value
160
+ setsockopt ZMQ::IDENTITY, value.to_s
161
+ end
162
+
163
+ # Can raise two kinds of exceptions depending on the error.
164
+ # ContextError:: Raised when a socket operation is attempted on a terminated
165
+ # #Context. See #ContextError.
166
+ # SocketError:: See all of the possibilities in the docs for #SocketError.
167
+ #
168
+ def bind address
169
+ result_code = LibZMQ.zmq_bind @socket, address
170
+ error_check ZMQ_BIND_STR, result_code
171
+ end
172
+
173
+ # Can raise two kinds of exceptions depending on the error.
174
+ # ContextError:: Raised when a socket operation is attempted on a terminated
175
+ # #Context. See #ContextError.
176
+ # SocketError:: See all of the possibilities in the docs for #SocketError.
177
+ #
178
+ def connect address
179
+ result_code = LibZMQ.zmq_connect @socket, address
180
+ error_check ZMQ_CONNECT_STR, result_code
181
+ end
182
+
183
+ # Closes the socket. Any unprocessed messages in queue are dropped.
184
+ #
185
+ def close
186
+ remove_finalizer
187
+ result_code = LibZMQ.zmq_close @socket
188
+ error_check ZMQ_CLOSE_STR, result_code
189
+ end
190
+
191
+ # Queues the message for transmission. Message is assumed to be an instance or
192
+ # subclass of #Message.
193
+ #
194
+ # +flags+ may take two values:
195
+ # * 0 (default) - blocking operation
196
+ # * ZMQ::NOBLOCK - non-blocking operation
197
+ # * ZMQ::SNDMORE - this message is part of a multi-part message
198
+ #
199
+ # 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.
202
+ #
203
+ # The application code is *not* responsible for handling the +message+ object
204
+ # lifecycle when #send return ZMQ::NOBLOCK or it raises an exception. The
205
+ # #send method takes ownership of the +message+ and its associated buffers.
206
+ # A failed call will release the +message+ data buffer.
207
+ #
208
+ # Again, once a +message+ object has been passed to this method,
209
+ # do not try to access its #data buffer anymore. The 0mq library now owns it.
210
+ #
211
+ # Can raise two kinds of exceptions depending on the error.
212
+ # ContextError:: Raised when a socket operation is attempted on a terminated
213
+ # #Context. See #ContextError.
214
+ # SocketError:: See all of the possibilities in the docs for #SocketError.
215
+ #
216
+ def send message, flags = 0
217
+ begin
218
+ result_code = LibZMQ.zmq_send @socket, message.address, flags
219
+
220
+ # when the flag isn't set, do a normal error check
221
+ # when set, check to see if the message was successfully queued
222
+ queued = flags != NOBLOCK ? error_check(ZMQ_SEND_STR, result_code) : error_check_nonblock(result_code)
223
+ ensure
224
+ message.close
225
+ end
226
+
227
+ # true if sent, false if failed/EAGAIN
228
+ queued
229
+ end
230
+
231
+ # Helper method to make a new #Message instance out of the +message_string+ passed
232
+ # in for transmission.
233
+ #
234
+ # +flags+ may be ZMQ::NOBLOCK.
235
+ #
236
+ # Can raise two kinds of exceptions depending on the error.
237
+ # ContextError:: Raised when a socket operation is attempted on a terminated
238
+ # #Context. See #ContextError.
239
+ # SocketError:: See all of the possibilities in the docs for #SocketError.
240
+ #
241
+ def send_string message_string, flags = 0
242
+ message = @sender_klass.new
243
+ message.copy_in_string message_string
244
+ result = send message, flags
245
+ result
246
+ end
247
+
248
+ # Dequeues a message from the underlying queue. By default, this is a blocking operation.
249
+ #
250
+ # +flags+ may take two values:
251
+ # 0 (default) - blocking operation
252
+ # ZMQ::NOBLOCK - non-blocking operation
253
+ #
254
+ # Returns a true when it successfully dequeues one from the queue. Also, the +message+
255
+ # object is populated by the library with a data buffer containing the received
256
+ # data.
257
+ #
258
+ # Returns nil when a message could not be dequeued *and* +flags+ is set
259
+ # with ZMQ::NOBLOCK. The +message+ object is not modified in this situation.
260
+ #
261
+ # The application code is *not* responsible for handling the +message+ object lifecycle
262
+ # when #recv raises an exception. The #recv method takes ownership of the
263
+ # +message+ and its associated buffers. A failed call will
264
+ # release the data buffers assigned to the +message+.
265
+ #
266
+ # Can raise two kinds of exceptions depending on the error.
267
+ # ContextError:: Raised when a socket operation is attempted on a terminated
268
+ # #Context. See #ContextError.
269
+ # SocketError:: See all of the possibilities in the docs for #SocketError.
270
+ #
271
+ def recv message, flags = 0
272
+ begin
273
+ dequeued = _recv message, flags
274
+ rescue ZeroMQError
275
+ message.close
276
+ raise
277
+ end
278
+
279
+ dequeued ? true : nil
280
+ end
281
+
282
+ # Helper method to make a new #Message instance and convert its payload
283
+ # to a string.
284
+ #
285
+ # +flags+ may be ZMQ::NOBLOCK.
286
+ #
287
+ # Can raise two kinds of exceptions depending on the error.
288
+ # ContextError:: Raised when a socket operation is attempted on a terminated
289
+ # #Context. See #ContextError.
290
+ # SocketError:: See all of the possibilities in the docs for #SocketError.
291
+ #
292
+ def recv_string flags = 0
293
+ message = @receiver_klass.new
294
+
295
+ begin
296
+ dequeued = _recv message, flags
297
+
298
+ if dequeued
299
+ message.copy_out_string
300
+ else
301
+ nil
302
+ end
303
+ ensure
304
+ message.close
305
+ end
306
+ end
307
+
308
+ private
309
+
310
+ def _recv message, flags = 0
311
+ result_code = LibZMQ.zmq_recv @socket, message.address, flags
312
+
313
+ flags != NOBLOCK ? error_check(ZMQ_RECV_STR, result_code) : error_check_nonblock(result_code)
314
+ end
315
+
316
+ def alloc_temp_sockopt_buffers option_name
317
+ length = FFI::MemoryPointer.new :int64
318
+
319
+ case option_name
320
+ when RCVMORE, MCAST_LOOP, HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, SNDBUF, RCVBUF
321
+ # int64_t
322
+ length.write_long_long 8
323
+ [FFI::MemoryPointer.new(:int64), length]
324
+ when IDENTITY
325
+ # could be a string of up to 255 bytes
326
+ length.write_long_long 255
327
+ [FFI::MemoryPointer.new(255), length]
328
+ end
329
+ end
330
+
331
+ def define_finalizer
332
+ ObjectSpace.define_finalizer(self, self.class.close(@socket))
333
+ end
334
+
335
+ def remove_finalizer
336
+ ObjectSpace.undefine_finalizer self
337
+ end
338
+
339
+ def self.close socket
340
+ Proc.new { LibZMQ.zmq_close socket }
341
+ end
342
+ end # class Socket
343
+
344
+ end # module ZMQ
@@ -0,0 +1,120 @@
1
+ require 'ffi' unless RBX # external gem
2
+
3
+ module LibC
4
+ extend FFI::Library
5
+ # figures out the correct libc for each platform including Windows
6
+ ffi_lib FFI::Library::LIBC unless RBX
7
+
8
+ # memory allocators
9
+ attach_function :malloc, [:size_t], :pointer
10
+ attach_function :calloc, [:size_t], :pointer
11
+ attach_function :valloc, [:size_t], :pointer
12
+ attach_function :realloc, [:pointer, :size_t], :pointer
13
+ attach_function :free, [:pointer], :void
14
+
15
+ # memory movers
16
+ attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
17
+ attach_function :bcopy, [:pointer, :pointer, :size_t], :void
18
+
19
+ end # module LibC
20
+
21
+ module LibZMQ
22
+ extend FFI::Library
23
+ LINUX = ["libzmq", "/usr/local/lib/libzmq", "/opt/local/lib/libzmq"]
24
+ OSX = ["libzmq", "/usr/local/lib/libzmq", "/opt/local/lib/libzmq"]
25
+ WINDOWS = []
26
+ RBX ? ffi_lib(*(LINUX + OSX + WINDOWS)) : ffi_lib(LINUX + OSX + WINDOWS)
27
+
28
+ # Misc
29
+ attach_function :zmq_version, [:pointer, :pointer, :pointer], :void
30
+
31
+ # Context and misc api
32
+ attach_function :zmq_init, [:int], :pointer
33
+ attach_function :zmq_socket, [:pointer, :int], :pointer
34
+ attach_function :zmq_term, [:pointer], :int
35
+ attach_function :zmq_errno, [], :int
36
+ attach_function :zmq_strerror, [:int], :pointer
37
+
38
+ # Message api
39
+ attach_function :zmq_msg_init, [:pointer], :int
40
+ attach_function :zmq_msg_init_size, [:pointer, :size_t], :int
41
+ attach_function :zmq_msg_init_data, [:pointer, :pointer, :size_t, :pointer, :pointer], :int
42
+ attach_function :zmq_msg_close, [:pointer], :int
43
+ attach_function :zmq_msg_data, [:pointer], :pointer
44
+ attach_function :zmq_msg_size, [:pointer], :size_t
45
+ attach_function :zmq_msg_copy, [:pointer, :pointer], :int
46
+ attach_function :zmq_msg_move, [:pointer, :pointer], :int
47
+
48
+ unless RBX
49
+ MessageDeallocator = FFI::Function.new(:void, [:pointer, :pointer]) do |data_ptr, hint_ptr|
50
+ LibC.free data_ptr
51
+ end
52
+ MessageDeallocator.autorelease = false
53
+ end
54
+
55
+ # Used for casting pointers back to the struct
56
+ class Msg < FFI::Struct
57
+ layout :content, :pointer,
58
+ :flags, :uint8,
59
+ :vsm_size, :uint8,
60
+ :vsm_data, [:uint8, 30]
61
+ end # class Msg
62
+
63
+
64
+ # Socket api
65
+ # @blocking = true is a hint to FFI that the following (and only the following)
66
+ # function may block, therefore it should release the GIL before calling it.
67
+ # This can aid in situations where the function call will/may block and another
68
+ # thread within the lib may try to call back into the ruby runtime. Failure to
69
+ # release the GIL will result in a hang; the hint *may* allow things to run
70
+ # smoothly for Ruby runtimes hampered by a GIL.
71
+ attach_function :zmq_setsockopt, [:pointer, :int, :pointer, :int], :int
72
+ attach_function :zmq_getsockopt, [:pointer, :int, :pointer, :pointer], :int
73
+ attach_function :zmq_bind, [:pointer, :string], :int
74
+ attach_function :zmq_connect, [:pointer, :string], :int
75
+ @blocking = true
76
+ attach_function :zmq_send, [:pointer, :pointer, :int], :int
77
+ @blocking = true
78
+ attach_function :zmq_recv, [:pointer, :pointer, :int], :int
79
+ attach_function :zmq_close, [:pointer], :int
80
+
81
+ # Poll api
82
+ @blocking = true
83
+ attach_function :zmq_poll, [:pointer, :int, :long], :int
84
+
85
+ module PollItemLayout
86
+ def self.included(base)
87
+ base.class_eval do
88
+ layout :socket, :pointer,
89
+ :fd, :int,
90
+ :events, :short,
91
+ :revents, :short
92
+ end
93
+ end
94
+ end # module PollItemLayout
95
+
96
+ class PollItem < FFI::Struct
97
+ include PollItemLayout
98
+
99
+ def socket() self[:socket]; end
100
+
101
+ def readable?
102
+ (self[:revents] & ZMQ::POLLIN) > 0
103
+ end
104
+
105
+ def writable?
106
+ (self[:revents] & ZMQ::POLLOUT) > 0
107
+ end
108
+
109
+ def both_accessible?
110
+ readable? && writable?
111
+ end
112
+
113
+ def inspect
114
+ "socket [#{socket}], fd [#{self[:fd]}], events [#{self[:events]}], revents [#{self[:revents]}]"
115
+ end
116
+
117
+ def to_s; inspect; end
118
+ end # class PollItem
119
+
120
+ end # module ZMQ