ffi-rzmq 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/AUTHORS.txt +1 -0
  2. data/History.txt +35 -0
  3. data/README.rdoc +48 -15
  4. data/Rakefile +7 -2
  5. data/examples/README.rdoc +21 -76
  6. data/examples/{local_lat.rb → v2api/local_lat.rb} +27 -12
  7. data/examples/v2api/local_lat_poll.rb +66 -0
  8. data/examples/{local_throughput.rb → v2api/local_throughput.rb} +24 -9
  9. data/examples/v2api/publish_subscribe.rb +82 -0
  10. data/examples/{remote_lat.rb → v2api/remote_lat.rb} +26 -8
  11. data/examples/v2api/remote_throughput.rb +39 -0
  12. data/examples/v2api/reqrep_poll.rb +62 -0
  13. data/examples/v2api/request_response.rb +40 -0
  14. data/examples/v2api/throughput_measurement.rb +138 -0
  15. data/examples/v3api/local_lat.rb +59 -0
  16. data/examples/v3api/local_lat_poll.rb +66 -0
  17. data/examples/v3api/local_throughput.rb +65 -0
  18. data/examples/v3api/publish_subscribe.rb +82 -0
  19. data/examples/v3api/remote_lat.rb +71 -0
  20. data/examples/v3api/remote_throughput.rb +47 -0
  21. data/examples/v3api/reqrep_poll.rb +62 -0
  22. data/examples/v3api/request_response.rb +40 -0
  23. data/examples/v3api/throughput_measurement.rb +166 -0
  24. data/ext/README +5 -0
  25. data/ffi-rzmq.gemspec +4 -4
  26. data/lib/ffi-rzmq.rb +4 -1
  27. data/lib/ffi-rzmq/constants.rb +178 -0
  28. data/lib/ffi-rzmq/context.rb +61 -45
  29. data/lib/ffi-rzmq/device.rb +22 -9
  30. data/lib/ffi-rzmq/exceptions.rb +0 -98
  31. data/lib/ffi-rzmq/libc.rb +19 -0
  32. data/lib/ffi-rzmq/libzmq.rb +188 -0
  33. data/lib/ffi-rzmq/message.rb +33 -40
  34. data/lib/ffi-rzmq/poll.rb +49 -52
  35. data/lib/ffi-rzmq/socket.rb +902 -392
  36. data/lib/ffi-rzmq/util.rb +101 -0
  37. data/spec/context_spec.rb +47 -21
  38. data/spec/device_spec.rb +78 -58
  39. data/spec/message_spec.rb +90 -12
  40. data/spec/multipart_spec.rb +162 -0
  41. data/spec/nonblocking_recv_spec.rb +325 -0
  42. data/spec/pushpull_spec.rb +95 -34
  43. data/spec/reqrep_spec.rb +55 -20
  44. data/spec/socket_spec.rb +353 -204
  45. data/spec/spec_helper.rb +46 -3
  46. data/version.txt +1 -1
  47. metadata +91 -66
  48. data/examples/local_lat_poll.rb +0 -54
  49. data/examples/local_lat_zerocopy.rb +0 -24
  50. data/examples/publish_subscribe.rb +0 -52
  51. data/examples/remote_lat_zerocopy.rb +0 -35
  52. data/examples/remote_throughput.rb +0 -27
  53. data/examples/reqrep_poll.rb +0 -49
  54. data/examples/request_response.rb +0 -23
  55. data/lib/ffi-rzmq/wrapper.rb +0 -121
  56. data/lib/ffi-rzmq/zmq.rb +0 -198
@@ -1,8 +1,6 @@
1
1
 
2
2
  module ZMQ
3
3
 
4
- ZMQ_POLL_STR = 'zmq_poll'.freeze
5
-
6
4
  class Poller
7
5
  include ZMQ::Util
8
6
 
@@ -16,7 +14,7 @@ module ZMQ
16
14
  @writables = []
17
15
  end
18
16
 
19
- # Checks each poll item for selectability based on the poll items'
17
+ # Checks each registered socket for selectability based on the poll items'
20
18
  # registered +events+. Will block for up to +timeout+ milliseconds
21
19
  # A millisecond is 1/1000 of a second, so to block for 1 second
22
20
  # pass the value "1000" to #poll.
@@ -24,34 +22,42 @@ module ZMQ
24
22
  # Pass "-1" or +:blocking+ for +timeout+ for this call to block
25
23
  # indefinitely.
26
24
  #
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
25
  # This method will return *immediately* when there are no registered
32
26
  # sockets. In that case, the +timeout+ parameter is not honored. To
33
27
  # prevent a CPU busy-loop, the caller of this method should detect
34
28
  # this possible condition (via #size) and throttle the call
35
29
  # frequency.
36
30
  #
31
+ # Returns 0 when there are no registered sockets that are readable
32
+ # or writable.
33
+ #
34
+ # Return 1 (or greater) to indicate the number of readable or writable
35
+ # sockets. These sockets should be processed using the #readables and
36
+ # #writables accessors.
37
+ #
38
+ # Returns -1 when there is an error. Use ZMQ::Util.errno to get the related
39
+ # error number.
40
+ #
37
41
  def poll timeout = :blocking
38
42
  unless @items.empty?
39
43
  timeout = adjust timeout
40
44
  items_triggered = LibZMQ.zmq_poll @items.address, @items.size, timeout
41
- error_check ZMQ_POLL_STR, items_triggered >= 0 ? 0 : items_triggered
42
- update_selectables
43
- items_hash
45
+
46
+ if Util.resultcode_ok?(items_triggered)
47
+ update_selectables
48
+ end
49
+
50
+ items_triggered
44
51
  else
45
- {}
52
+ 0
46
53
  end
47
54
  end
48
55
 
49
56
  # The non-blocking version of #poll. See the #poll description for
50
57
  # potential exceptions.
51
58
  #
52
- # May raise a ZMQ::PollError exception. This occurs when one of the
53
- # registered sockets belongs to an application thread in another
54
- # Context.
59
+ # May return -1 when an error is encounted. Check ZMQ::Util.errno
60
+ # to determine the underlying cause.
55
61
  #
56
62
  def poll_nonblock
57
63
  poll 0
@@ -62,8 +68,6 @@ module ZMQ
62
68
  # will only get registered at most once. Calling multiple times with
63
69
  # different values for +events+ will OR the event information together.
64
70
  #
65
- # Does not raise any exceptions.
66
- #
67
71
  def register sock, events = ZMQ::POLLIN | ZMQ::POLLOUT, fd = 0
68
72
  return false if (sock.nil? && fd.zero?) || events.zero?
69
73
 
@@ -84,13 +88,12 @@ module ZMQ
84
88
  @raw_to_socket[item[:socket].address] = sock
85
89
  @items << item
86
90
  end
87
-
91
+
88
92
  item[:events] |= events
89
93
  end
90
94
 
91
- # Deregister the +sock+ for +events+.
92
- #
93
- # Does not raise any exceptions.
95
+ # Deregister the +sock+ for +events+. When there are no events left,
96
+ # this also deletes the socket from the poll items.
94
97
  #
95
98
  def deregister sock, events, fd = 0
96
99
  return unless sock || !fd.zero?
@@ -102,6 +105,9 @@ module ZMQ
102
105
  item[:events] ^= events
103
106
 
104
107
  delete sock if item[:events].zero?
108
+ true
109
+ else
110
+ false
105
111
  end
106
112
  end
107
113
 
@@ -129,33 +135,28 @@ module ZMQ
129
135
  deregister sock, ZMQ::POLLOUT, 0
130
136
  end
131
137
 
132
- # Deletes the +sock+ for all subscribed events.
133
- #
134
- # def delete sock
135
- # removed = false
136
- #
137
- # if index = @sockets.index(sock)
138
- # removed = @items.delete_at(index) and @sockets.delete(sock) and @raw_to_socket.delete(sock.socket.address)
139
- # end
140
- #
141
- # removed
142
- # end
138
+ # Deletes the +sock+ for all subscribed events. Called internally
139
+ # when a socket has been deregistered and has no more events
140
+ # registered anywhere.
141
+ #
143
142
  def delete sock
144
- removed = false
145
- deregister_readable sock
146
- deregister_writable sock
147
-
148
- size = @sockets.size
149
- @sockets.delete_if { |socket| socket.socket.address == sock.socket.address }
150
- socket_deleted = size != @sockets.size
151
-
152
- item_deleted = @items.delete sock
153
-
154
- size = @raw_to_socket.size
155
- @raw_to_socket.delete(sock.socket.address)
156
- raw_deleted = size != @raw_to_socket.size
157
-
158
- socket_deleted && item_deleted && raw_deleted
143
+ removed_readable = deregister_readable sock
144
+ removed_writable = deregister_writable sock
145
+
146
+ if (size = @sockets.size) > 0
147
+ @sockets.delete_if { |socket| socket.socket.address == sock.socket.address }
148
+ socket_deleted = size != @sockets.size
149
+
150
+ item_deleted = @items.delete sock
151
+
152
+ raw_deleted = @raw_to_socket.delete(sock.socket.address)
153
+
154
+ socket_deleted && item_deleted && raw_deleted
155
+
156
+ else
157
+ # return result of deregistration
158
+ removed_readable || removed_writable
159
+ end
159
160
  end
160
161
 
161
162
  def size(); @items.size; end
@@ -169,14 +170,10 @@ module ZMQ
169
170
 
170
171
  private
171
172
 
172
- def items_hash
173
- hsh = {}
174
-
173
+ def items_hash hash
175
174
  @items.each do |poll_item|
176
- hsh[@raw_to_socket[poll_item[:socket].address]] = poll_item
175
+ hash[@raw_to_socket[poll_item[:socket].address]] = poll_item
177
176
  end
178
-
179
- hsh
180
177
  end
181
178
 
182
179
  def update_selectables
@@ -1,16 +1,7 @@
1
1
 
2
2
  module ZMQ
3
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
4
+ module CommonSocketBehavior
14
5
  include ZMQ::Util
15
6
 
16
7
  attr_reader :socket, :name
@@ -18,7 +9,39 @@ module ZMQ
18
9
  # Allocates a socket of type +type+ for sending and receiving data.
19
10
  #
20
11
  # +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB,
21
- # ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH,
12
+ # ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH, ZMQ::XREQ, ZMQ::REP,
13
+ # ZMQ::DEALER or ZMQ::ROUTER.
14
+ #
15
+ # By default, this class uses ZMQ::Message for manual
16
+ # memory management. For automatic garbage collection of received messages,
17
+ # it is possible to override the :receiver_class to use ZMQ::ManagedMessage.
18
+ #
19
+ # sock = Socket.create(Context.create, ZMQ::REQ, :receiver_class => ZMQ::ManagedMessage)
20
+ #
21
+ # Advanced users may want to replace the receiver class with their
22
+ # own custom class. The custom class must conform to the same public API
23
+ # as ZMQ::Message.
24
+ #
25
+ # Creation of a new Socket object can return nil when socket creation
26
+ # fails.
27
+ #
28
+ # if (socket = Socket.new(context.pointer, ZMQ::REQ))
29
+ # ...
30
+ # else
31
+ # STDERR.puts "Socket creation failed"
32
+ # end
33
+ #
34
+ def self.create context_ptr, type, opts = {:receiver_class => ZMQ::Message}
35
+ new(context_ptr, type, opts) rescue nil
36
+ end
37
+
38
+ # To avoid rescuing exceptions, use the factory method #create for
39
+ # all socket creation.
40
+ #
41
+ # Allocates a socket of type +type+ for sending and receiving data.
42
+ #
43
+ # +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB,
44
+ # ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH, ZMQ::XREQ, ZMQ::REP,
22
45
  # ZMQ::DEALER or ZMQ::ROUTER.
23
46
  #
24
47
  # By default, this class uses ZMQ::Message for manual
@@ -31,26 +54,32 @@ module ZMQ
31
54
  # own custom class. The custom class must conform to the same public API
32
55
  # as ZMQ::Message.
33
56
  #
34
- # Can raise two kinds of exceptions depending on the error.
35
- # ContextError:: Raised when a socket operation is attempted on a terminated
36
- # #Context. See #ContextError.
37
- # SocketError:: See all of the possibilities in the docs for #SocketError.
57
+ # Creation of a new Socket object can raise an exception. This occurs when the
58
+ # +context_ptr+ is null or when the allocation of the 0mq socket within the
59
+ # context fails.
60
+ #
61
+ # begin
62
+ # socket = Socket.new(context.pointer, ZMQ::REQ)
63
+ # rescue ContextError => e
64
+ # # error handling
65
+ # end
38
66
  #
39
67
  def initialize context_ptr, type, opts = {:receiver_class => ZMQ::Message}
40
68
  # users may override the classes used for receiving; class must conform to the
41
69
  # same public API as ZMQ::Message
42
70
  @receiver_klass = opts[:receiver_class]
71
+
72
+ context_ptr = context_ptr.pointer if context_ptr.kind_of?(ZMQ::Context)
43
73
 
44
74
  unless context_ptr.null?
45
75
  @socket = LibZMQ.zmq_socket context_ptr, type
46
- if @socket
47
- error_check ZMQ_SOCKET_STR, @socket.null? ? 1 : 0
76
+ if @socket && !@socket.null?
48
77
  @name = SocketTypeNameMap[type]
49
78
  else
50
- raise ContextError.new ZMQ_SOCKET_STR, 0, ETERM, "Socket pointer was null"
79
+ raise ContextError.new 'zmq_socket', 0, ETERM, "Socket pointer was null"
51
80
  end
52
81
  else
53
- raise ContextError.new ZMQ_SOCKET_STR, 0, ETERM, "Context pointer was null"
82
+ raise ContextError.new 'zmq_socket', 0, ETERM, "Context pointer was null"
54
83
  end
55
84
 
56
85
  @sockopt_cache = {}
@@ -60,416 +89,897 @@ module ZMQ
60
89
 
61
90
  # Set the queue options on this socket.
62
91
  #
63
- # Valid +option_name+ values that take a numeric +option_value+ are:
92
+ # Valid +name+ values that take a numeric +value+ are:
64
93
  # ZMQ::HWM
65
- # ZMQ::SWAP
94
+ # ZMQ::SWAP (version 2 only)
66
95
  # ZMQ::AFFINITY
67
96
  # ZMQ::RATE
68
97
  # ZMQ::RECOVERY_IVL
69
- # ZMQ::MCAST_LOOP
98
+ # ZMQ::MCAST_LOOP (version 2 only)
70
99
  # ZMQ::LINGER
71
100
  # ZMQ::RECONNECT_IVL
72
101
  # ZMQ::BACKLOG
73
- # ZMQ::RECOVER_IVL_MSEC
102
+ # ZMQ::RECOVER_IVL_MSEC (version 2 only)
103
+ # ZMQ::RECONNECT_IVL_MAX (version 3/4 only)
104
+ # ZMQ::MAXMSGSIZE (version 3/4 only)
105
+ # ZMQ::SNDHWM (version 3/4 only)
106
+ # ZMQ::RCVHWM (version 3/4 only)
107
+ # ZMQ::MULTICAST_HOPS (version 3/4 only)
108
+ # ZMQ::RCVTIMEO (version 3/4 only)
109
+ # ZMQ::SNDTIMEO (version 3/4 only)
110
+ # ZMQ::RCVLABEL (version 3/4 only)
74
111
  #
75
- # Valid +option_name+ values that take a string +option_value+ are:
76
- # ZMQ::IDENTITY
112
+ # Valid +name+ values that take a string +value+ are:
113
+ # ZMQ::IDENTITY (version 2/3 only)
77
114
  # ZMQ::SUBSCRIBE
78
115
  # ZMQ::UNSUBSCRIBE
79
116
  #
80
- # Can raise two kinds of exceptions depending on the error.
81
- # ContextError:: Raised when a socket operation is attempted on a terminated
82
- # #Context. See #ContextError.
83
- # SocketError:: See all of the possibilities in the docs for #SocketError.
117
+ # Returns 0 when the operation completed successfully.
118
+ # Returns -1 when this operation failed.
84
119
  #
85
- def setsockopt option_name, option_value, option_len = nil
86
- option_value = sanitize_value option_name, option_value
120
+ # With a -1 return code, the user must check ZMQ.errno to determine the
121
+ # cause.
122
+ #
123
+ # rc = socket.setsockopt(ZMQ::LINGER, 1_000)
124
+ # ZMQ::Util.resultcode_ok?(rc) ? puts("succeeded") : puts("failed")
125
+ #
126
+ def setsockopt name, value, length = nil
127
+ if long_long_option?(name)
128
+ length = 8
129
+ pointer = LibC.malloc length
130
+ pointer.write_long_long value
131
+
132
+ elsif int_option?(name)
133
+ length = 4
134
+ pointer = LibC.malloc length
135
+ pointer.write_int value
136
+
137
+ elsif string_option?(name)
138
+ length ||= value.size
139
+
140
+ # note: not checking errno for failed memory allocations :(
141
+ pointer = LibC.malloc length
142
+ pointer.write_string value
143
+ end
87
144
 
88
- begin
89
- case option_name
90
- when HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, MCAST_LOOP, SNDBUF, RCVBUF, RECOVERY_IVL_MSEC
91
- option_len = 8 # all of these options are defined as int64_t or uint64_t
92
- option_value_ptr = LibC.malloc option_len
93
- option_value_ptr.write_long_long option_value
145
+ rc = LibZMQ.zmq_setsockopt @socket, name, pointer, length
146
+ LibC.free(pointer) unless pointer.nil? || pointer.null?
147
+ rc
148
+ end
94
149
 
95
- when LINGER, RECONNECT_IVL, BACKLOG
96
- option_len = 4 # hard-code "int" length to 4 bytes
97
- option_value_ptr = LibC.malloc option_len
98
- option_value_ptr.write_int option_value
150
+ # Convenience method for checking on additional message parts.
151
+ #
152
+ # Equivalent to calling Socket#getsockopt with ZMQ::RCVMORE.
153
+ #
154
+ # Warning: if the call to #getsockopt fails, this method will return
155
+ # false and swallow the error.
156
+ #
157
+ # message_parts = []
158
+ # message = Message.new
159
+ # rc = socket.recv(message)
160
+ # if ZMQ::Util.resultcode_ok?(rc)
161
+ # message_parts << message
162
+ # while more_parts?
163
+ # message = Message.new
164
+ # rc = socket.recv(message)
165
+ # message_parts.push(message) if resulcode_ok?(rc)
166
+ # end
167
+ # end
168
+ #
169
+ def more_parts?
170
+ array = []
171
+ rc = getsockopt ZMQ::RCVMORE, array
172
+
173
+ Util.resultcode_ok?(rc) ? array.at(0) : false
174
+ end
99
175
 
100
- when IDENTITY, SUBSCRIBE, UNSUBSCRIBE
101
- option_len ||= option_value.size
176
+ # Binds the socket to an +address+.
177
+ #
178
+ # socket.bind("tcp://127.0.0.1:5555")
179
+ #
180
+ def bind address
181
+ LibZMQ.zmq_bind @socket, address
182
+ end
102
183
 
103
- # note: not checking errno for failed memory allocations :(
104
- option_value_ptr = LibC.malloc option_len
105
- option_value_ptr.write_string option_value
184
+ # Connects the socket to an +address+.
185
+ #
186
+ # socket.connect("tcp://127.0.0.1:5555")
187
+ #
188
+ def connect address
189
+ rc = LibZMQ.zmq_connect @socket, address
190
+ end
106
191
 
107
- else
108
- # we didn't understand the passed option argument
109
- # will force a raise due to EINVAL being non-zero
110
- error_check ZMQ_SETSOCKOPT_STR, EINVAL
192
+ # Closes the socket. Any unprocessed messages in queue are sent or dropped
193
+ # depending upon the value of the socket option ZMQ::LINGER.
194
+ #
195
+ # Returns 0 upon success *or* when the socket has already been closed.
196
+ # Returns -1 when the operation fails. Check ZMQ.errno for the error code.
197
+ #
198
+ # rc = socket.close
199
+ # puts("Given socket was invalid!") unless 0 == rc
200
+ #
201
+ def close
202
+ if @socket
203
+ remove_finalizer
204
+ rc = LibZMQ.zmq_close @socket
205
+ @socket = nil
206
+ release_cache
207
+ rc
208
+ else
209
+ 0
210
+ end
211
+ end
212
+
213
+
214
+ private
215
+
216
+ def __getsockopt__ name, array
217
+ value, length = sockopt_buffers name
218
+
219
+ rc = LibZMQ.zmq_getsockopt @socket, name, value, length
220
+
221
+ if Util.resultcode_ok?(rc)
222
+ result = if int_option?(name)
223
+ value.read_int
224
+ elsif long_long_option?(name)
225
+ value.read_long_long
226
+ elsif string_option?(name)
227
+ value.read_string(length.read_int)
111
228
  end
112
229
 
113
- result_code = LibZMQ.zmq_setsockopt @socket, option_name, option_value_ptr, option_len
114
- error_check ZMQ_SETSOCKOPT_STR, result_code
115
- ensure
116
- LibC.free option_value_ptr unless option_value_ptr.nil? || option_value_ptr.null?
230
+ array << result
117
231
  end
118
- end
119
232
 
120
- # Get the options set on this socket. Returns a value dependent upon
121
- # the +option_name+ requested.
122
- #
123
- # Valid +option_name+ values and their return types:
124
- # ZMQ::RCVMORE - boolean
125
- # ZMQ::HWM - integer
126
- # ZMQ::SWAP - integer
127
- # ZMQ::AFFINITY - bitmap in an integer
128
- # ZMQ::IDENTITY - string
129
- # ZMQ::RATE - integer
130
- # ZMQ::RECOVERY_IVL - integer
131
- # ZMQ::MCAST_LOOP - boolean
132
- # ZMQ::SNDBUF - integer
133
- # ZMQ::RCVBUF - integer
134
- # ZMQ::FD - fd in an integer
135
- # ZMQ::EVENTS - bitmap integer
136
- # ZMQ::LINGER - integer measured in milliseconds
137
- # ZMQ::RECONNECT_IVL - integer measured in milliseconds
138
- # ZMQ::BACKLOG - integer
139
- # ZMQ::RECOVER_IVL_MSEC - integer measured in milliseconds
140
- #
141
- # Can raise two kinds of exceptions depending on the error.
142
- # ContextError:: Raised when a socket operation is attempted on a terminated
143
- # #Context. See #ContextError.
144
- # SocketError:: See all of the possibilities in the docs for #SocketError.
145
- #
146
- def getsockopt option_name
147
- begin
148
- option_value = FFI::MemoryPointer.new :pointer
149
- option_length = FFI::MemoryPointer.new(:size_t) rescue FFI::MemoryPointer.new(:ulong)
150
-
151
- unless [
152
- TYPE, RCVMORE, HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, MCAST_LOOP, IDENTITY,
153
- SNDBUF, RCVBUF, FD, EVENTS, LINGER, RECONNECT_IVL, BACKLOG, RECOVERY_IVL_MSEC
154
- ].include? option_name
155
- # we didn't understand the passed option argument
156
- # will force a raise
157
- error_check ZMQ_SETSOCKOPT_STR, -1
158
- end
159
-
160
- option_value, option_length = alloc_temp_sockopt_buffers option_name
161
-
162
- result_code = LibZMQ.zmq_getsockopt @socket, option_name, option_value, option_length
163
- error_check ZMQ_GETSOCKOPT_STR, result_code
164
- ret = 0
165
-
166
- case option_name
167
- when RCVMORE, MCAST_LOOP
168
- # boolean return
169
- ret = option_value.read_long_long != 0
170
- when HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, SNDBUF, RCVBUF, RECOVERY_IVL_MSEC
171
- ret = option_value.read_long_long
172
- when TYPE, LINGER, RECONNECT_IVL, BACKLOG, FD, EVENTS
173
- ret = option_value.read_int
174
- when IDENTITY
175
- ret = option_value.read_string(option_length.read_long_long)
176
- end
177
-
178
- ret
233
+ rc
179
234
  end
180
- end
181
-
182
- # Convenience method for checking on additional message parts.
183
- #
184
- # Equivalent to Socket#getsockopt ZMQ::RCVMORE
185
- #
186
- def more_parts?
187
- getsockopt ZMQ::RCVMORE
188
- end
189
-
190
- # Convenience method for getting the value of the socket IDENTITY.
191
- #
192
- def identity
193
- getsockopt ZMQ::IDENTITY
194
- end
195
-
196
- # Convenience method for setting the value of the socket IDENTITY.
197
- #
198
- def identity= value
199
- setsockopt ZMQ::IDENTITY, value.to_s
200
- end
201
-
202
- # Can raise two kinds of exceptions depending on the error.
203
- # ContextError:: Raised when a socket operation is attempted on a terminated
204
- # #Context. See #ContextError.
205
- # SocketError:: See all of the possibilities in the docs for #SocketError.
206
- #
207
- def bind address
208
- result_code = LibZMQ.zmq_bind @socket, address
209
- error_check ZMQ_BIND_STR, result_code
210
- end
211
-
212
- # Can raise two kinds of exceptions depending on the error.
213
- # ContextError:: Raised when a socket operation is attempted on a terminated
214
- # #Context. See #ContextError.
215
- # SocketError:: See all of the possibilities in the docs for #SocketError.
216
- #
217
- def connect address
218
- result_code = LibZMQ.zmq_connect @socket, address
219
- error_check ZMQ_CONNECT_STR, result_code
220
- end
221
-
222
- # Closes the socket. Any unprocessed messages in queue are sent or dropped
223
- # depending upon the value of the socket option ZMQ::LINGER.
224
- #
225
- def close
226
- if @socket
227
- remove_finalizer
228
- result_code = LibZMQ.zmq_close @socket
229
- error_check ZMQ_CLOSE_STR, result_code
230
- @socket = nil
231
- release_cache
235
+
236
+ # Calls to ZMQ.getsockopt require us to pass in some pointers. We can cache and save those buffers
237
+ # for subsequent calls. This is a big perf win for calling RCVMORE which happens quite often.
238
+ # Cannot save the buffer for the IDENTITY.
239
+ def sockopt_buffers name
240
+ if long_long_option?(name)
241
+ # int64_t or uint64_t
242
+ unless @sockopt_cache[:int64]
243
+ length = FFI::MemoryPointer.new :size_t
244
+ length.write_int 8
245
+ @sockopt_cache[:int64] = [FFI::MemoryPointer.new(:int64), length]
246
+ end
247
+
248
+ @sockopt_cache[:int64]
249
+
250
+ elsif int_option?(name)
251
+ # int, 0mq assumes int is 4-bytes
252
+ unless @sockopt_cache[:int32]
253
+ length = FFI::MemoryPointer.new :size_t
254
+ length.write_int 4
255
+ @sockopt_cache[:int32] = [FFI::MemoryPointer.new(:int32), length]
256
+ end
257
+
258
+ @sockopt_cache[:int32]
259
+
260
+ elsif string_option?(name)
261
+ length = FFI::MemoryPointer.new :size_t
262
+ # could be a string of up to 255 bytes
263
+ length.write_int 255
264
+ [FFI::MemoryPointer.new(255), length]
265
+
266
+ else
267
+ # uh oh, someone passed in an unknown option; use a slop buffer
268
+ unless @sockopt_cache[:unknown]
269
+ length = FFI::MemoryPointer.new :size_t
270
+ length.write_int 4
271
+ @sockopt_cache[:unknown] = [FFI::MemoryPointer.new(:int32), length]
272
+ end
273
+
274
+ @sockopt_cache[:unknown]
275
+ end
232
276
  end
233
- end
234
-
235
- # Queues the message for transmission. Message is assumed to conform to the
236
- # same public API as #Message.
237
- #
238
- # +flags+ may take two values:
239
- # * 0 (default) - blocking operation
240
- # * ZMQ::NOBLOCK - non-blocking operation
241
- # * ZMQ::SNDMORE - this message is part of a multi-part message
242
- #
243
- # Returns true when the message was successfully enqueued.
244
- # Returns false under two conditions.
245
- # 1. The message could not be enqueued
246
- # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
247
- #
248
- # The application code is responsible for handling the +message+ object
249
- # lifecycle when #send returns.
250
- #
251
- # Can raise two kinds of exceptions depending on the error.
252
- # ContextError:: Raised when a socket operation is attempted on a terminated
253
- # #Context. See #ContextError.
254
- # SocketError:: See all of the possibilities in the docs for #SocketError.
255
- #
256
- def send message, flags = 0
257
- begin
258
- result_code = LibZMQ.zmq_send @socket, message.address, flags
259
-
260
- # when the flag isn't set, do a normal error check
261
- # when set, check to see if the message was successfully queued
262
- queued = noblock?(flags) ? error_check_nonblock(result_code) : error_check(ZMQ_SEND_STR, result_code)
277
+
278
+ def supported_option? name
279
+ int_option?(name) || long_long_option?(name) || string_option?(name)
263
280
  end
264
281
 
265
- # true if sent, false if failed/EAGAIN
266
- queued
267
- end
268
-
269
- # Helper method to make a new #Message instance out of the +message_string+ passed
270
- # in for transmission.
271
- #
272
- # +flags+ may be ZMQ::NOBLOCK.
273
- #
274
- # Can raise two kinds of exceptions depending on the error.
275
- # ContextError:: Raised when a socket operation is attempted on a terminated
276
- # #Context. See #ContextError.
277
- # SocketError:: See all of the possibilities in the docs for #SocketError.
278
- #
279
- def send_string message_string, flags = 0
280
- message = Message.new message_string
281
- result_code = send_and_close message, flags
282
-
283
- result_code
284
- end
285
-
286
- # Send a sequence of strings as a multipart message out of the +parts+
287
- # passed in for transmission. Every element of +parts+ should be
288
- # a String.
289
- #
290
- # +flags+ may be ZMQ::NOBLOCK.
291
- #
292
- # Raises the same exceptions as Socket#send.
293
- #
294
- def send_strings parts, flags = 0
295
- return false if !parts || parts.empty?
296
-
297
- parts[0...-1].each do |part|
298
- return false unless send_string part, flags | ZMQ::SNDMORE
282
+ def int_option? name
283
+ EVENTS == name ||
284
+ LINGER == name ||
285
+ RECONNECT_IVL == name ||
286
+ FD == name ||
287
+ TYPE == name ||
288
+ BACKLOG == name
299
289
  end
300
290
 
301
- send_string parts[-1], flags
302
- end
303
-
304
- # Sends a message. This will automatically close the +message+ for both successful
305
- # and failed sends.
306
- #
307
- # Raises the same exceptions as Socket#send
308
- #
309
- def send_and_close message, flags = 0
310
- begin
311
- result_code = send message, flags
312
- ensure
313
- message.close
291
+ def string_option? name
292
+ SUBSCRIBE == name ||
293
+ UNSUBSCRIBE == name
314
294
  end
315
- result_code
316
- end
317
-
318
- # Dequeues a message from the underlying queue. By default, this is a blocking operation.
319
- #
320
- # +flags+ may take two values:
321
- # 0 (default) - blocking operation
322
- # ZMQ::NOBLOCK - non-blocking operation
323
- #
324
- # Returns a true when it successfully dequeues one from the queue. Also, the +message+
325
- # object is populated by the library with a data buffer containing the received
326
- # data.
327
- #
328
- # Returns nil when a message could not be dequeued *and* +flags+ is set
329
- # with ZMQ::NOBLOCK. The +message+ object is not modified in this situation.
330
- #
331
- # The application code is *not* responsible for handling the +message+ object lifecycle
332
- # when #recv raises an exception. The #recv method takes ownership of the
333
- # +message+ and its associated buffers. A failed call will
334
- # release the data buffers assigned to the +message+.
335
- #
336
- # Can raise two kinds of exceptions depending on the error.
337
- # ContextError:: Raised when a socket operation is attempted on a terminated
338
- # #Context. See #ContextError.
339
- # SocketError:: See all of the possibilities in the docs for #SocketError.
340
- #
341
- def recv message, flags = 0
342
- begin
343
- dequeued = _recv message, flags
344
- rescue ZeroMQError
345
- message.close
346
- raise
295
+
296
+ def long_long_option? name
297
+ RCVMORE == name ||
298
+ AFFINITY == name
347
299
  end
348
300
 
349
- dequeued ? true : nil
350
- end
351
-
352
- # Helper method to make a new #Message instance and convert its payload
353
- # to a string.
354
- #
355
- # +flags+ may be ZMQ::NOBLOCK.
356
- #
357
- # Can raise two kinds of exceptions depending on the error.
358
- # ContextError:: Raised when a socket operation is attempted on a terminated
359
- # #Context. See #ContextError.
360
- # SocketError:: See all of the possibilities in the docs for #SocketError.
361
- #
362
- def recv_string flags = 0
363
- message = @receiver_klass.new
364
-
365
- begin
366
- dequeued = _recv message, flags
367
-
368
- if dequeued
369
- message.copy_out_string
370
- else
371
- nil
372
- end
373
- ensure
374
- message.close
301
+ def unsupported_setsock_option? name
302
+ RCVMORE == name
375
303
  end
376
- end
377
-
378
- # Receive a multipart message as a list of strings.
379
- #
380
- # +flags+ may be ZMQ::NOBLOCK.
381
- #
382
- # Raises the same exceptions as Socket#recv.
383
- #
384
- def recv_strings flags = 0
385
- parts = []
386
- parts << recv_string(flags)
387
- parts << recv_string(flags) while more_parts?
388
- parts
389
- end
390
-
391
- private
392
-
393
- def noblock? flag
394
- (NOBLOCK & flag) == NOBLOCK
395
- end
396
-
397
- def _recv message, flags = 0
398
- result_code = LibZMQ.zmq_recv @socket, message.address, flags
399
-
400
- if noblock?(flags)
401
- error_check_nonblock(result_code)
402
- else
403
- error_check(ZMQ_RECV_STR, result_code)
304
+
305
+ def unsupported_getsock_option? name
306
+ UNSUBSCRIBE == name ||
307
+ SUBSCRIBE == name
404
308
  end
405
- end
406
-
407
- # Calls to ZMQ.getsockopt require us to pass in some pointers. We can cache and save those buffers
408
- # for subsequent calls. This is a big perf win for calling RCVMORE which happens quite often.
409
- # Cannot save the buffer for the IDENTITY.
410
- def alloc_temp_sockopt_buffers option_name
411
- case option_name
412
- when RCVMORE, MCAST_LOOP, HWM, SWAP, AFFINITY, RATE, RECOVERY_IVL, SNDBUF, RCVBUF, RECOVERY_IVL_MSEC
413
- # int64_t
414
- unless @sockopt_cache[:int64]
415
- length = FFI::MemoryPointer.new :int64
416
- length.write_long_long 8
417
- @sockopt_cache[:int64] = [FFI::MemoryPointer.new(:int64), length]
418
- end
419
- @sockopt_cache[:int64]
420
-
421
- when TYPE, LINGER, RECONNECT_IVL, BACKLOG, FD, EVENTS
422
- # int, 0mq assumes int is 4-bytes
423
- unless @sockopt_cache[:int32]
424
- length = FFI::MemoryPointer.new :int32
425
- length.write_int 4
426
- @sockopt_cache[:int32] = [FFI::MemoryPointer.new(:int32), length]
427
- end
428
- @sockopt_cache[:int32]
429
-
430
- when IDENTITY
431
- length = FFI::MemoryPointer.new :int64
432
- # could be a string of up to 255 bytes
433
- length.write_long_long 255
434
- [FFI::MemoryPointer.new(255), length]
309
+
310
+ def release_cache
311
+ @sockopt_cache.clear
435
312
  end
436
- end
437
-
438
- def sanitize_value option_name, option_value
439
- case option_name
440
- when HWM, AFFINITY, SNDBUF, RCVBUF
441
- option_value.abs
442
- when MCAST_LOOP
443
- option_value ? 1 : 0
444
- else
445
- option_value
313
+ end # module CommonSocketBehavior
314
+
315
+
316
+ module IdentitySupport
317
+
318
+ # Convenience method for getting the value of the socket IDENTITY.
319
+ #
320
+ def identity
321
+ array = []
322
+ getsockopt IDENTITY, array
323
+ array.at(0)
446
324
  end
447
- end
448
-
449
- def release_cache
450
- @sockopt_cache.clear
451
- end
452
-
453
- # require a minimum of 0mq 2.1.0 to support socket finalizers; it contains important
454
- # fixes for sockets and threads so that a garbage collector thread can successfully
455
- # reap this resource without crashing
456
- if Util.minimum_api?([2, 1, 0])
457
- def define_finalizer
458
- ObjectSpace.define_finalizer(self, self.class.close(@socket))
325
+
326
+ # Convenience method for setting the value of the socket IDENTITY.
327
+ #
328
+ def identity=(value)
329
+ setsockopt IDENTITY, value.to_s
459
330
  end
460
- else
461
- def define_finalizer
462
- # no op
331
+
332
+
333
+ private
334
+
335
+ def string_option? name
336
+ super ||
337
+ IDENTITY == name
463
338
  end
464
- end
339
+ end # module IdentitySupport
340
+
341
+
342
+ if LibZMQ.version2?
343
+
344
+ class Socket
345
+ # Inclusion order is *important* since later modules may have a call
346
+ # to #super. We want those calls to go up the chain in a particular
347
+ # order
348
+ include CommonSocketBehavior
349
+ include IdentitySupport
350
+
351
+ # Get the options set on this socket.
352
+ #
353
+ # +name+ determines the socket option to request
354
+ # +array+ should be an empty array; a result of the proper type
355
+ # (numeric, string, boolean) will be inserted into
356
+ # the first position.
357
+ #
358
+ # Valid +option_name+ values:
359
+ # ZMQ::RCVMORE - true or false
360
+ # ZMQ::HWM - integer
361
+ # ZMQ::SWAP - integer
362
+ # ZMQ::AFFINITY - bitmap in an integer
363
+ # ZMQ::IDENTITY - string
364
+ # ZMQ::RATE - integer
365
+ # ZMQ::RECOVERY_IVL - integer
366
+ # ZMQ::MCAST_LOOP - true or false
367
+ # ZMQ::SNDBUF - integer
368
+ # ZMQ::RCVBUF - integer
369
+ # ZMQ::FD - fd in an integer
370
+ # ZMQ::EVENTS - bitmap integer
371
+ # ZMQ::LINGER - integer measured in milliseconds
372
+ # ZMQ::RECONNECT_IVL - integer measured in milliseconds
373
+ # ZMQ::BACKLOG - integer
374
+ # ZMQ::RECOVER_IVL_MSEC - integer measured in milliseconds
375
+ #
376
+ # Returns 0 when the operation completed successfully.
377
+ # Returns -1 when this operation failed.
378
+ #
379
+ # With a -1 return code, the user must check ZMQ.errno to determine the
380
+ # cause.
381
+ #
382
+ # # retrieve high water mark
383
+ # array = []
384
+ # rc = socket.getsockopt(ZMQ::HWM, array)
385
+ # hwm = array.first if ZMQ::Util.resultcode_ok?(rc)
386
+ #
387
+ def getsockopt name, array
388
+ rc = __getsockopt__ name, array
389
+
390
+ if Util.resultcode_ok?(rc) && (RCVMORE == name || MCAST_LOOP == name)
391
+ # convert to boolean
392
+ array[0] = 1 == array[0]
393
+ end
394
+
395
+ rc
396
+ end
397
+
398
+ # Queues the message for transmission. Message is assumed to conform to the
399
+ # same public API as #Message.
400
+ #
401
+ # +flags+ may take two values:
402
+ # * 0 (default) - blocking operation
403
+ # * ZMQ::NOBLOCK - non-blocking operation
404
+ # * ZMQ::SNDMORE - this message is part of a multi-part message
405
+ #
406
+ # Returns 0 when the message was successfully enqueued.
407
+ # Returns -1 under two conditions.
408
+ # 1. The message could not be enqueued
409
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
410
+ #
411
+ # With a -1 return code, the user must check ZMQ.errno to determine the
412
+ # cause.
413
+ #
414
+ # The application code is responsible for handling the +message+ object
415
+ # lifecycle when #send returns. Regardless of the return code, the user
416
+ # is responsible for calling message.close to free the memory in use.
417
+ #
418
+ def send message, flags = 0
419
+ LibZMQ.zmq_send @socket, message.address, flags
420
+ end
421
+
422
+ # Helper method to make a new #Message instance out of the +message_string+ passed
423
+ # in for transmission.
424
+ #
425
+ # +flags+ may be ZMQ::NOBLOCK.
426
+ #
427
+ # Returns 0 when the message was successfully enqueued.
428
+ # Returns -1 under two conditions.
429
+ # 1. The message could not be enqueued
430
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
431
+ #
432
+ # With a -1 return code, the user must check ZMQ.errno to determine the
433
+ # cause.
434
+ #
435
+ def send_string message_string, flags = 0
436
+ message = Message.new message_string
437
+ send_and_close message, flags
438
+ end
439
+
440
+ # Send a sequence of strings as a multipart message out of the +parts+
441
+ # passed in for transmission. Every element of +parts+ should be
442
+ # a String.
443
+ #
444
+ # +flags+ may be ZMQ::NOBLOCK.
445
+ #
446
+ # Returns 0 when the message was successfully enqueued.
447
+ # Returns -1 under two conditions.
448
+ # 1. The message could not be enqueued
449
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
450
+ #
451
+ # With a -1 return code, the user must check ZMQ.errno to determine the
452
+ # cause.
453
+ #
454
+ def send_strings parts, flags = 0
455
+ return -1 if !parts || parts.empty?
456
+
457
+ parts[0..-2].each do |part|
458
+ rc = send_string part, flags | ZMQ::SNDMORE
459
+ return rc unless Util.resultcode_ok?(rc)
460
+ end
461
+
462
+ send_string parts[-1], flags
463
+ end
464
+
465
+ # Send a sequence of messages as a multipart message out of the +parts+
466
+ # passed in for transmission. Every element of +parts+ should be
467
+ # a Message (or subclass).
468
+ #
469
+ # +flags+ may be ZMQ::NOBLOCK.
470
+ #
471
+ # Returns 0 when the message was successfully enqueued.
472
+ # Returns -1 under two conditions.
473
+ # 1. The message could not be enqueued
474
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
475
+ #
476
+ # With a -1 return code, the user must check ZMQ.errno to determine the
477
+ # cause.
478
+ #
479
+ def sendmsgs parts, flags = 0
480
+ return -1 if !parts || parts.empty?
481
+
482
+ parts[0..-2].each do |part|
483
+ rc = send part, flags | ZMQ::SNDMORE
484
+ return rc unless Util.resultcode_ok?(rc)
485
+ end
486
+
487
+ send parts[-1], flags
488
+ end
489
+
490
+ # Sends a message. This will automatically close the +message+ for both successful
491
+ # and failed sends.
492
+ #
493
+ # Returns 0 when the message was successfully enqueued.
494
+ # Returns -1 under two conditions.
495
+ # 1. The message could not be enqueued
496
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
497
+ #
498
+ # With a -1 return code, the user must check ZMQ.errno to determine the
499
+ # cause.
500
+ #
501
+ def send_and_close message, flags = 0
502
+ rc = send message, flags
503
+ message.close
504
+ rc
505
+ end
506
+
507
+ # Dequeues a message from the underlying queue. By default, this is a blocking operation.
508
+ #
509
+ # +flags+ may take two values:
510
+ # 0 (default) - blocking operation
511
+ # ZMQ::NOBLOCK - non-blocking operation
512
+ #
513
+ # Returns 0 when the message was successfully dequeued.
514
+ # Returns -1 under two conditions.
515
+ # 1. The message could not be dequeued
516
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
517
+ #
518
+ # With a -1 return code, the user must check ZMQ.errno to determine the
519
+ # cause.
520
+ #
521
+ # The application code is responsible for handling the +message+ object lifecycle
522
+ # when #recv returns an error code.
523
+ #
524
+ def recv message, flags = 0
525
+ LibZMQ.zmq_recv @socket, message.address, flags
526
+ end
527
+
528
+ # Converts the received message to a string and replaces the +string+ arg
529
+ # contents.
530
+ #
531
+ # +string+ should be an empty string, .e.g. ''
532
+ # +flags+ may be ZMQ::NOBLOCK.
533
+ #
534
+ # Returns 0 when the message was successfully dequeued.
535
+ # Returns -1 under two conditions.
536
+ # 1. The message could not be dequeued
537
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
538
+ #
539
+ # With a -1 return code, the user must check ZMQ.errno to determine the
540
+ # cause.
541
+ #
542
+ def recv_string string, flags = 0
543
+ message = @receiver_klass.new
544
+ rc = recv message, flags
545
+ string.replace(message.copy_out_string) if Util.resultcode_ok?(rc)
546
+ message.close
547
+ rc
548
+ end
549
+
550
+ # Receive a multipart message as a list of strings.
551
+ #
552
+ # +list+ should be an object that responds to #append or #<< so received
553
+ # strings can be appended to it
554
+ # +flag+ may be ZMQ::NOBLOCK. Any other flag will be removed
555
+ #
556
+ # Returns 0 when all messages were successfully dequeued.
557
+ # Returns -1 under two conditions.
558
+ # 1. A message could not be dequeued
559
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
560
+ #
561
+ # With a -1 return code, the user must check ZMQ.errno to determine the
562
+ # cause. Also, the +list+ will not be modified when there was an error.
563
+ #
564
+ def recv_strings list, flag = 0
565
+ array = []
566
+ rc = recvmsgs array, flag
567
+
568
+ if Util.resultcode_ok?(rc)
569
+ array.each do |message|
570
+ list << message.copy_out_string
571
+ message.close
572
+ end
573
+ end
574
+
575
+ rc
576
+ end
577
+
578
+ # Receive a multipart message as an array of objects
579
+ # (by default these are instances of Message).
580
+ #
581
+ # +list+ should be an object that responds to #append or #<< so received
582
+ # messages can be appended to it
583
+ # +flag+ may be ZMQ::NOBLOCK. Any other flag will be
584
+ # removed.
585
+ #
586
+ # Returns 0 when all messages were successfully dequeued.
587
+ # Returns -1 under two conditions.
588
+ # 1. A message could not be dequeued
589
+ # 2. When +flags+ is set with ZMQ::NOBLOCK and the socket returned EAGAIN.
590
+ #
591
+ # With a -1 return code, the user must check ZMQ.errno to determine the
592
+ # cause. Also, the +list+ will not be modified when there was an error.
593
+ #
594
+ def recvmsgs list, flag = 0
595
+ flag = NOBLOCK if noblock?(flag)
596
+
597
+ parts = []
598
+ message = @receiver_klass.new
599
+ rc = recv message, flag
600
+ parts << message
601
+
602
+ # check rc *first*; necessary because the call to #more_parts? can reset
603
+ # the zmq_errno to a weird value, so the zmq_errno that was set on the
604
+ # call to #recv gets lost
605
+ while Util.resultcode_ok?(rc) && more_parts?
606
+ message = @receiver_klass.new
607
+ rc = recv message, flag
608
+ parts << message
609
+ end
610
+
611
+ # only append the received parts if there were no errors
612
+ # FIXME:
613
+ # need to detect EAGAIN if flag is set; EAGAIN means we have read all that we
614
+ # can and should return whatever was already read; need a spec!
615
+ if Util.resultcode_ok?(rc)
616
+ parts.each { |part| list << part }
617
+ end
618
+
619
+ rc
620
+ end
621
+
622
+
623
+ private
624
+
625
+ def noblock? flags
626
+ (NOBLOCK & flags) == NOBLOCK
627
+ end
628
+
629
+ def int_option? name
630
+ super ||
631
+ RECONNECT_IVL_MAX == name
632
+ end
633
+
634
+ def long_long_option? name
635
+ super ||
636
+ HWM == name ||
637
+ SWAP == name ||
638
+ RATE == name ||
639
+ RECOVERY_IVL == name ||
640
+ RECOVERY_IVL_MSEC == name ||
641
+ MCAST_LOOP == name ||
642
+ SNDBUF == name ||
643
+ RCVBUF == name
644
+ end
645
+
646
+ # these finalizer-related methods cannot live in the CommonSocketBehavior
647
+ # module; they *must* be in the class definition directly
648
+
649
+ def define_finalizer
650
+ ObjectSpace.define_finalizer(self, self.class.close(@socket))
651
+ end
652
+
653
+ def remove_finalizer
654
+ ObjectSpace.undefine_finalizer self
655
+ end
656
+
657
+ def self.close socket
658
+ Proc.new { LibZMQ.zmq_close socket }
659
+ end
660
+ end # class Socket for version2
661
+
662
+ end # LibZMQ.version2?
663
+
664
+
665
+ if LibZMQ.version3? || LibZMQ.version4?
666
+ class Socket
667
+ include CommonSocketBehavior
668
+ include IdentitySupport
669
+
670
+ # Get the options set on this socket.
671
+ #
672
+ # +name+ determines the socket option to request
673
+ # +array+ should be an empty array; a result of the proper type
674
+ # (numeric, string, boolean) will be inserted into
675
+ # the first position.
676
+ #
677
+ # Valid +option_name+ values:
678
+ # ZMQ::RCVMORE - true or false
679
+ # ZMQ::HWM - integer
680
+ # ZMQ::SWAP - integer
681
+ # ZMQ::AFFINITY - bitmap in an integer
682
+ # ZMQ::IDENTITY - string
683
+ # ZMQ::RATE - integer
684
+ # ZMQ::RECOVERY_IVL - integer
685
+ # ZMQ::SNDBUF - integer
686
+ # ZMQ::RCVBUF - integer
687
+ # ZMQ::FD - fd in an integer
688
+ # ZMQ::EVENTS - bitmap integer
689
+ # ZMQ::LINGER - integer measured in milliseconds
690
+ # ZMQ::RECONNECT_IVL - integer measured in milliseconds
691
+ # ZMQ::BACKLOG - integer
692
+ # ZMQ::RECOVER_IVL_MSEC - integer measured in milliseconds
693
+ #
694
+ # Returns 0 when the operation completed successfully.
695
+ # Returns -1 when this operation failed.
696
+ #
697
+ # With a -1 return code, the user must check ZMQ.errno to determine the
698
+ # cause.
699
+ #
700
+ # # retrieve high water mark
701
+ # array = []
702
+ # rc = socket.getsockopt(ZMQ::HWM, array)
703
+ # hwm = array.first if ZMQ::Util.resultcode_ok?(rc)
704
+ #
705
+ def getsockopt name, array
706
+ rc = __getsockopt__ name, array
707
+
708
+ if Util.resultcode_ok?(rc) && (RCVMORE == name)
709
+ # convert to boolean
710
+ array[0] = 1 == array[0]
711
+ end
712
+
713
+ rc
714
+ end
715
+
716
+ # The last message part received is tested to see if it is a label.
717
+ #
718
+ # Equivalent to calling Socket#getsockopt with ZMQ::RCVLABEL.
719
+ #
720
+ # Warning: if the call to #getsockopt fails, this method will return
721
+ # false and swallow the error.
722
+ #
723
+ # labels = []
724
+ # message_parts = []
725
+ # message = Message.new
726
+ # rc = socket.recv(message)
727
+ # if ZMQ::Util.resultcode_ok?(rc)
728
+ # label? ? labels.push(message) : message_parts.push(message)
729
+ # while more_parts?
730
+ # message = Message.new
731
+ # if ZMQ::Util.resulcode_ok?(socket.recv(message))
732
+ # label? ? labels.push(message) : message_parts.push(message)
733
+ # end
734
+ # end
735
+ # end
736
+ #
737
+ def label?
738
+ array = []
739
+ rc = getsockopt ZMQ::RCVLABEL, array
740
+
741
+ Util.resultcode_ok?(rc) ? array.at(0) : false
742
+ end
743
+
744
+ # Queues the message for transmission. Message is assumed to conform to the
745
+ # same public API as #Message.
746
+ #
747
+ # +flags+ may take two values:
748
+ # * 0 (default) - blocking operation
749
+ # * ZMQ::DONTWAIT - non-blocking operation
750
+ # * ZMQ::SNDMORE - this message is part of a multi-part message
751
+ #
752
+ # Returns 0 when the message was successfully enqueued.
753
+ # Returns -1 under two conditions.
754
+ # 1. The message could not be enqueued
755
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
756
+ #
757
+ # With a -1 return code, the user must check ZMQ.errno to determine the
758
+ # cause.
759
+ #
760
+ def sendmsg message, flags = 0
761
+ LibZMQ.zmq_sendmsg @socket, message.address, flags
762
+ end
763
+
764
+ # Helper method to make a new #Message instance out of the +string+ passed
765
+ # in for transmission.
766
+ #
767
+ # +flags+ may be ZMQ::DONTWAIT and ZMQ::SNDMORE.
768
+ #
769
+ # Returns 0 when the message was successfully enqueued.
770
+ # Returns -1 under two conditions.
771
+ # 1. The message could not be enqueued
772
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
773
+ #
774
+ # With a -1 return code, the user must check ZMQ.errno to determine the
775
+ # cause.
776
+ #
777
+ def send_string string, flags = 0
778
+ message = Message.new string
779
+ send_and_close message, flags
780
+ end
781
+
782
+ # Send a sequence of strings as a multipart message out of the +parts+
783
+ # passed in for transmission. Every element of +parts+ should be
784
+ # a String.
785
+ #
786
+ # +flags+ may be ZMQ::DONTWAIT.
787
+ #
788
+ # Returns 0 when the messages were successfully enqueued.
789
+ # Returns -1 under two conditions.
790
+ # 1. A message could not be enqueued
791
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
792
+ #
793
+ # With a -1 return code, the user must check ZMQ.errno to determine the
794
+ # cause.
795
+ #
796
+ def send_strings parts, flags = 0
797
+ return -1 if !parts || parts.empty?
798
+ flags = DONTWAIT if dontwait?(flags)
799
+
800
+ parts[0..-2].each do |part|
801
+ rc = send_string part, (flags | ZMQ::SNDMORE)
802
+ return rc unless Util.resultcode_ok?(rc)
803
+ end
804
+
805
+ send_string parts[-1], flags
806
+ end
807
+
808
+ # Send a sequence of messages as a multipart message out of the +parts+
809
+ # passed in for transmission. Every element of +parts+ should be
810
+ # a Message (or subclass).
811
+ #
812
+ # +flags+ may be ZMQ::DONTWAIT.
813
+ #
814
+ # Returns 0 when the messages were successfully enqueued.
815
+ # Returns -1 under two conditions.
816
+ # 1. A message could not be enqueued
817
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
818
+ #
819
+ # With a -1 return code, the user must check ZMQ.errno to determine the
820
+ # cause.
821
+ #
822
+ def sendmsgs parts, flags = 0
823
+ return -1 if !parts || parts.empty?
824
+ flags = DONTWAIT if dontwait?(flags)
825
+
826
+ parts[0..-2].each do |part|
827
+ rc = sendmsg part, (flags | ZMQ::SNDMORE)
828
+ return rc unless Util.resultcode_ok?(rc)
829
+ end
830
+
831
+ sendmsg parts[-1], flags
832
+ end
833
+
834
+ # Sends a message. This will automatically close the +message+ for both successful
835
+ # and failed sends.
836
+ #
837
+ # Returns 0 when the message was successfully enqueued.
838
+ # Returns -1 under two conditions.
839
+ # 1. The message could not be enqueued
840
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
841
+ #
842
+ # With a -1 return code, the user must check ZMQ.errno to determine the
843
+ # cause.
844
+ #
845
+ def send_and_close message, flags = 0
846
+ rc = sendmsg message, flags
847
+ message.close
848
+ rc
849
+ end
850
+
851
+ # Dequeues a message from the underlying queue. By default, this is a blocking operation.
852
+ #
853
+ # +flags+ may take two values:
854
+ # 0 (default) - blocking operation
855
+ # ZMQ::DONTWAIT - non-blocking operation
856
+ #
857
+ # Returns 0 when the message was successfully dequeued.
858
+ # Returns -1 under two conditions.
859
+ # 1. The message could not be dequeued
860
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
861
+ #
862
+ # With a -1 return code, the user must check ZMQ.errno to determine the
863
+ # cause.
864
+ #
865
+ # The application code is responsible for handling the +message+ object lifecycle
866
+ # when #recv returns an error code.
867
+ #
868
+ def recvmsg message, flags = 0
869
+ LibZMQ.zmq_recvmsg @socket, message.address, flags
870
+ end
871
+
872
+ # Helper method to make a new #Message instance and convert its payload
873
+ # to a string.
874
+ #
875
+ # +flags+ may be ZMQ::DONTWAIT.
876
+ #
877
+ # Returns 0 when the message was successfully dequeued.
878
+ # Returns -1 under two conditions.
879
+ # 1. The message could not be dequeued
880
+ # 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
881
+ #
882
+ # With a -1 return code, the user must check ZMQ.errno to determine the
883
+ # cause.
884
+ #
885
+ # The application code is responsible for handling the +message+ object lifecycle
886
+ # when #recv returns an error code.
887
+ #
888
+ def recv_string string, flags = 0
889
+ message = @receiver_klass.new
890
+ rc = recvmsg message, flags
891
+ string.replace(message.copy_out_string) if Util.resultcode_ok?(rc)
892
+ message.close
893
+ rc
894
+ end
465
895
 
466
- def remove_finalizer
467
- ObjectSpace.undefine_finalizer self
468
- end
896
+ # Receive a multipart message as a list of strings.
897
+ #
898
+ # +flag+ may be ZMQ::DONTWAIT. Any other flag will be
899
+ # removed.
900
+ #
901
+ def recv_strings list, flag = 0
902
+ array = []
903
+ rc = recvmsgs array, flag
904
+
905
+ if Util.resultcode_ok?(rc)
906
+ array.each do |message|
907
+ list << message.copy_out_string
908
+ message.close
909
+ end
910
+ end
911
+
912
+ rc
913
+ end
914
+
915
+ # Receive a multipart message as an array of objects
916
+ # (by default these are instances of Message).
917
+ #
918
+ # +flag+ may be ZMQ::DONTWAIT. Any other flag will be
919
+ # removed.
920
+ #
921
+ # Raises the same exceptions as Socket#recv.
922
+ #
923
+ def recvmsgs list, flag = 0
924
+ flag = DONTWAIT if dontwait?(flag)
925
+
926
+ parts = []
927
+ message = @receiver_klass.new
928
+ rc = recvmsg message, flag
929
+ parts << message
930
+
931
+ # check rc *first*; necessary because the call to #more_parts? can reset
932
+ # the zmq_errno to a weird value, so the zmq_errno that was set on the
933
+ # call to #recv gets lost
934
+ while Util.resultcode_ok?(rc) && more_parts?
935
+ message = @receiver_klass.new
936
+ rc = recvmsg message, flag
937
+ parts << message
938
+ end
939
+
940
+ # only append the received parts if there were no errors
941
+ if Util.resultcode_ok?(rc)
942
+ parts.each { |part| list << part }
943
+ end
944
+
945
+ rc
946
+ end
947
+
948
+
949
+ private
469
950
 
470
- def self.close socket
471
- Proc.new { LibZMQ.zmq_close socket }
472
- end
473
- end # class Socket
951
+ def dontwait? flags
952
+ (DONTWAIT & flags) == DONTWAIT
953
+ end
954
+ alias :noblock? :dontwait?
955
+
956
+ def int_option? name
957
+ super ||
958
+ RCVLABEL == name ||
959
+ RECONNECT_IVL_MAX == name ||
960
+ RCVHWM == name ||
961
+ SNDHWM == name ||
962
+ RATE == name ||
963
+ RECOVERY_IVL == name ||
964
+ SNDBUF == name ||
965
+ RCVBUF == name
966
+ end
967
+
968
+ # these finalizer-related methods cannot live in the CommonSocketBehavior
969
+ # module; they *must* be in the class definition directly
970
+
971
+ def define_finalizer
972
+ ObjectSpace.define_finalizer(self, self.class.close(@socket))
973
+ end
974
+
975
+ def remove_finalizer
976
+ ObjectSpace.undefine_finalizer self
977
+ end
978
+
979
+ def self.close socket
980
+ Proc.new { LibZMQ.zmq_close socket }
981
+ end
982
+ end # Socket for version3
983
+ end # LibZMQ.version3?
474
984
 
475
985
  end # module ZMQ