ffi-rzmq 0.5.0

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