ffi-rzmq 0.8.2 → 0.9.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.
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,15 +1,28 @@
1
1
  module ZMQ
2
- class Device
3
- attr_reader :device
4
-
5
- def initialize(device_type,frontend,backend)
6
- [["frontend", frontend],["backend", backend]].each do |name,socket|
7
- unless socket.is_a?(ZMQ::Socket)
8
- raise ArgumentError, "Expected a ZMQ::Socket, not a #{socket.class} as the #{name}"
2
+ if LibZMQ.version2?
3
+ class Device
4
+ attr_reader :device
5
+
6
+ def self.create(device_type, frontend, backend)
7
+ dev = nil
8
+ begin
9
+ dev = new device_type, frontend, backend
10
+ rescue ArgumentError
11
+ dev = nil
9
12
  end
13
+
14
+ dev
15
+ end
16
+
17
+ def initialize(device_type,frontend,backend)
18
+ [["frontend", frontend],["backend", backend]].each do |name,socket|
19
+ unless socket.is_a?(ZMQ::Socket)
20
+ raise ArgumentError, "Expected a ZMQ::Socket, not a #{socket.class} as the #{name}"
21
+ end
22
+ end
23
+
24
+ LibZMQ.zmq_device(device_type, frontend.socket, backend.socket)
10
25
  end
11
-
12
- LibZMQ.zmq_device(device_type, frontend.socket, backend.socket)
13
26
  end
14
27
  end
15
28
  end
@@ -34,104 +34,6 @@ module ZMQ
34
34
  end # class ContextError
35
35
 
36
36
 
37
- class PollError < ZeroMQError
38
- # True when the exception was raised due to the library
39
- # returning EMTHREAD.
40
- #
41
- # At least one of the members of the items array refers
42
- # to a socket belonging to a different application
43
- # thread.
44
- #
45
- def efault?() EFAULT == @error_code; end
46
-
47
- end # class PollError
48
-
49
-
50
- class SocketError < ZeroMQError
51
- # True when the exception was raised due to the library
52
- # returning EMTHREAD.
53
- #
54
- # Occurs for #send and #recv operations.
55
- # * When calling #send, non-blocking mode was requested
56
- # and the message cannot be queued at the moment.
57
- # * When calling #recv, non-blocking mode was requested
58
- # and no messages are available at the moment.
59
- #
60
- def egain?() EAGAIN == @error_code; end
61
-
62
- # True when the exception was raised due to the library
63
- # returning ENOCOMPATPROTO.
64
- #
65
- # The requested transport protocol is not compatible
66
- # with the socket type.
67
- #
68
- def enocompatproto?() ENOCOMPATPROTO == @error_code; end
69
-
70
- # True when the exception was raised due to the library
71
- # returning EPROTONOSUPPORT.
72
- #
73
- # The requested transport protocol is not supported.
74
- #
75
- def eprotonosupport?() EPROTONOSUPPORT == @error_code; end
76
-
77
- # True when the exception was raised due to the library
78
- # returning EADDRINUSE.
79
- #
80
- # The given address is already in use.
81
- #
82
- def eaddrinuse?() EADDRINUSE == @error_code; end
83
-
84
- # True when the exception was raised due to the library
85
- # returning EADDRNOTAVAIL.
86
- #
87
- # A nonexistent interface was requested or the
88
- # requested address was not local.
89
- #
90
- def eaddrnotavail?() EADDRNOTAVAIL == @error_code; end
91
-
92
- # True when the exception was raised due to the library
93
- # returning EMTHREAD.
94
- #
95
- # Occurs under 2 conditions.
96
- # * When creating a new #Socket, the requested socket
97
- # type is invalid.
98
- #
99
- # * When setting socket options with #setsockopt, the
100
- # requested option +option_name+ is unknown, or the
101
- # requested +option_len+ or +option_value+ is invalid.
102
- #
103
- def einval?() EINVAL == @error_code; end
104
-
105
- # True when the exception was raised due to the library
106
- # returning EMTHREAD.
107
- #
108
- # The send or recv operation cannot be performed on this
109
- # socket at the moment due to the socket not being in
110
- # the appropriate state. This error may occur with socket
111
- # types that switch between several states, such as ZMQ::REP.
112
- #
113
- def efsm?() EFSM == @error_code; end
114
-
115
- # True when the exception was raised due to the library
116
- # returning ENOTSUP.
117
- #
118
- # The send or recv operation is not supported by this socket
119
- # type.
120
- #
121
- def enotsup?() super; end
122
-
123
- # True when the exception was raised due to the library
124
- # returning EMTHREAD.
125
- #
126
- # The number of application threads using sockets within
127
- # this context has been exceeded. See the +app_threads+
128
- # parameter of #Context.
129
- #
130
- def emthread?() EMTHREAD == @error_code; end
131
-
132
- end # class SocketError
133
-
134
-
135
37
  class MessageError < ZeroMQError
136
38
  # True when the exception was raised due to the library
137
39
  # returning ENOMEM.
@@ -0,0 +1,19 @@
1
+
2
+ module LibC
3
+ extend FFI::Library
4
+ # figures out the correct libc for each platform including Windows
5
+ library = ffi_lib(FFI::Library::LIBC).first
6
+
7
+ # Size_t not working properly on Windows
8
+ find_type(:size_t) rescue typedef(:ulong, :size_t)
9
+
10
+ # memory allocators
11
+ attach_function :malloc, [:size_t], :pointer
12
+ attach_function :free, [:pointer], :void
13
+
14
+ # get a pointer to the free function; used for ZMQ::Message deallocation
15
+ Free = library.find_symbol('free')
16
+
17
+ # memory movers
18
+ attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer
19
+ end # module LibC
@@ -0,0 +1,188 @@
1
+
2
+ # Wraps the libzmq library and attaches to the functions that are
3
+ # common across the 2.x, 3.x and 4.x APIs.
4
+ #
5
+ module LibZMQ
6
+ extend FFI::Library
7
+
8
+ # bias the library discovery to a path inside the gem first, then
9
+ # to the usual system paths
10
+ inside_gem = File.join(File.dirname(__FILE__), '..', '..', 'ext')
11
+ ZMQ_LIB_PATHS = [
12
+ inside_gem, '/usr/local/lib', '/opt/local/lib', '/usr/local/homebrew/lib', '/usr/lib64'
13
+ ].map{|path| "#{path}/libzmq.#{FFI::Platform::LIBSUFFIX}"}
14
+ ffi_lib(ZMQ_LIB_PATHS + %w{libzmq})
15
+
16
+ # Size_t not working properly on Windows
17
+ find_type(:size_t) rescue typedef(:ulong, :size_t)
18
+
19
+ # Context and misc api
20
+ #
21
+ # @blocking = true is a hint to FFI that the following (and only the following)
22
+ # function may block, therefore it should release the GIL before calling it.
23
+ # This can aid in situations where the function call will/may block and another
24
+ # thread within the lib may try to call back into the ruby runtime. Failure to
25
+ # release the GIL will result in a hang; the hint *may* allow things to run
26
+ # smoothly for Ruby runtimes hampered by a GIL.
27
+ #
28
+ # This is really only honored by the MRI implementation but it *is* necessary
29
+ # otherwise the runtime hangs (and requires a kill -9 to terminate)
30
+ #
31
+ @blocking = true
32
+ attach_function :zmq_init, [:int], :pointer
33
+ @blocking = true
34
+ attach_function :zmq_socket, [:pointer, :int], :pointer
35
+ @blocking = true
36
+ attach_function :zmq_term, [:pointer], :int
37
+ @blocking = true
38
+ attach_function :zmq_errno, [], :int
39
+ @blocking = true
40
+ attach_function :zmq_strerror, [:int], :pointer
41
+ @blocking = true
42
+ attach_function :zmq_version, [:pointer, :pointer, :pointer], :void
43
+
44
+ def self.version
45
+ unless @version
46
+ major = FFI::MemoryPointer.new :int
47
+ minor = FFI::MemoryPointer.new :int
48
+ patch = FFI::MemoryPointer.new :int
49
+ LibZMQ.zmq_version major, minor, patch
50
+ @version = {:major => major.read_int, :minor => minor.read_int, :patch => patch.read_int}
51
+ end
52
+
53
+ @version
54
+ end
55
+
56
+ def self.version2?() version[:major] == 2 && version[:minor] >= 1 end
57
+
58
+ def self.version3?() version[:major] == 3 end
59
+
60
+ def self.version4?() version[:major] == 4 end
61
+
62
+
63
+ # Message api
64
+ @blocking = true
65
+ attach_function :zmq_msg_init, [:pointer], :int
66
+ @blocking = true
67
+ attach_function :zmq_msg_init_size, [:pointer, :size_t], :int
68
+ @blocking = true
69
+ attach_function :zmq_msg_init_data, [:pointer, :pointer, :size_t, :pointer, :pointer], :int
70
+ @blocking = true
71
+ attach_function :zmq_msg_close, [:pointer], :int
72
+ @blocking = true
73
+ attach_function :zmq_msg_data, [:pointer], :pointer
74
+ @blocking = true
75
+ attach_function :zmq_msg_size, [:pointer], :size_t
76
+ @blocking = true
77
+ attach_function :zmq_msg_copy, [:pointer, :pointer], :int
78
+ @blocking = true
79
+ attach_function :zmq_msg_move, [:pointer, :pointer], :int
80
+
81
+ # Used for casting pointers back to the struct
82
+ #
83
+ class Msg < FFI::Struct
84
+ layout :content, :pointer,
85
+ :flags, :uint8,
86
+ :vsm_size, :uint8,
87
+ :vsm_data, [:uint8, 30]
88
+ end # class Msg
89
+
90
+ # Socket api
91
+ @blocking = true
92
+ attach_function :zmq_setsockopt, [:pointer, :int, :pointer, :int], :int
93
+ @blocking = true
94
+ attach_function :zmq_bind, [:pointer, :string], :int
95
+ @blocking = true
96
+ attach_function :zmq_connect, [:pointer, :string], :int
97
+ @blocking = true
98
+ attach_function :zmq_close, [:pointer], :int
99
+
100
+ # Poll api
101
+ @blocking = true
102
+ attach_function :zmq_poll, [:pointer, :int, :long], :int
103
+
104
+ module PollItemLayout
105
+ def self.included(base)
106
+ base.class_eval do
107
+ layout :socket, :pointer,
108
+ :fd, :int,
109
+ :events, :short,
110
+ :revents, :short
111
+ end
112
+ end
113
+ end # module PollItemLayout
114
+
115
+ class PollItem < FFI::Struct
116
+ include PollItemLayout
117
+
118
+ def socket() self[:socket]; end
119
+
120
+ def readable?
121
+ (self[:revents] & ZMQ::POLLIN) > 0
122
+ end
123
+
124
+ def writable?
125
+ (self[:revents] & ZMQ::POLLOUT) > 0
126
+ end
127
+
128
+ def both_accessible?
129
+ readable? && writable?
130
+ end
131
+
132
+ def inspect
133
+ "socket [#{socket}], fd [#{self[:fd]}], events [#{self[:events]}], revents [#{self[:revents]}]"
134
+ end
135
+
136
+ def to_s; inspect; end
137
+ end # class PollItem
138
+
139
+ end
140
+
141
+
142
+ # Attaches to those functions specific to the 2.x API
143
+ #
144
+ if LibZMQ.version2?
145
+
146
+ module LibZMQ
147
+ # Socket api
148
+ @blocking = true
149
+ attach_function :zmq_getsockopt, [:pointer, :int, :pointer, :pointer], :int
150
+ @blocking = true
151
+ attach_function :zmq_recv, [:pointer, :pointer, :int], :int
152
+ @blocking = true
153
+ attach_function :zmq_send, [:pointer, :pointer, :int], :int
154
+ @blocking = true
155
+ attach_function :zmq_device, [:int, :pointer, :pointer], :int
156
+ end
157
+ end
158
+
159
+
160
+ # Attaches to those functions specific to the 3.x API
161
+ #
162
+ if LibZMQ.version3? || LibZMQ.version4?
163
+
164
+ module LibZMQ
165
+ # Socket api
166
+ @blocking = true
167
+ attach_function :zmq_getsockopt, [:pointer, :int, :pointer, :pointer], :int
168
+ @blocking = true
169
+ attach_function :zmq_recvmsg, [:pointer, :pointer, :int], :int
170
+ @blocking = true
171
+ attach_function :zmq_recv, [:pointer, :pointer, :size_t, :int], :int
172
+ @blocking = true
173
+ attach_function :zmq_sendmsg, [:pointer, :pointer, :int], :int
174
+ @blocking = true
175
+ attach_function :zmq_send, [:pointer, :pointer, :size_t, :int], :int
176
+ end
177
+ end
178
+
179
+
180
+ # Sanity check; print an error and exit if we are trying to load an unsupported
181
+ # version of libzmq.
182
+ #
183
+ unless LibZMQ.version2? || LibZMQ.version3? || LibZMQ.version4?
184
+ hash = LibZMQ.version
185
+ version = "#{hash[:major]}.#{hash[:minor]}.#{hash[:patch]}"
186
+ STDERR.puts "Unable to load this gem. The libzmq version #{version} is incompatible with ffi-rzmq."
187
+ exit 255
188
+ end
@@ -1,21 +1,12 @@
1
1
 
2
2
  module ZMQ
3
3
 
4
- ZMQ_MSG_INIT_SIZE_STR = 'zmq_msg_init_size'.freeze
5
- ZMQ_MSG_INIT_DATA_STR = 'zmq_msg_init_data'.freeze
6
- ZMQ_MSG_INIT_STR = 'zmq_msg_init'.freeze
7
- ZMQ_MSG_CLOSE_STR = 'zmq_msg_close'.freeze
8
- ZMQ_MSG_COPY_STR = 'zmq_msg_copy'.freeze
9
- ZMQ_MSG_MOVE_STR = 'zmq_msg_move'.freeze
10
- ZMQ_MSG_SIZE_STR = 'zmq_msg_size'.freeze
11
-
12
- # The constructor optionally takes a string as an argument. It will
4
+ # The factory constructor optionally takes a string as an argument. It will
13
5
  # copy this string to native memory in preparation for transmission.
14
6
  # So, don't pass a string unless you intend to send it. Internally it
15
7
  # calls #copy_in_string.
16
8
  #
17
- # Call #close to release buffers when you have *not* passed this on
18
- # to Socket#send. That method calls #close on your behalf.
9
+ # Call #close to release buffers when you are done with the data.
19
10
  #
20
11
  # (This class is not really zero-copy. Ruby makes this near impossible
21
12
  # since Ruby objects can be relocated in memory by the GC at any
@@ -37,8 +28,17 @@ module ZMQ
37
28
  # When you are done using a *received* message object, call #close to
38
29
  # release the associated buffers.
39
30
  #
40
- # As noted above, for sent objects the underlying library will call close
41
- # for you.
31
+ # received_message = Message.create
32
+ # if received_message
33
+ # rc = socket.recv(received_message)
34
+ # if ZMQ::Util.resultcode_ok?(rc)
35
+ # puts "Message contained: #{received_message.copy_out_string}"
36
+ # else
37
+ # STDERR.puts "Error when receiving message: #{ZMQ::Util.error_string}"
38
+ # end
39
+ #
40
+ #
41
+ # Define a custom layout for the data sent between 0mq peers.
42
42
  #
43
43
  # class MyMessage
44
44
  # class Layout < FFI::Struct
@@ -85,6 +85,13 @@ module ZMQ
85
85
  #
86
86
  class Message
87
87
  include ZMQ::Util
88
+
89
+ # Recommended way to create a standard message. A Message object is
90
+ # returned upon success, nil when allocation fails.
91
+ #
92
+ def self.create message = nil
93
+ new(message) rescue nil
94
+ end
88
95
 
89
96
  def initialize message = nil
90
97
  @state = :uninitialized
@@ -99,7 +106,7 @@ module ZMQ
99
106
  else
100
107
  # initialize an empty message structure to receive a message
101
108
  result_code = LibZMQ.zmq_msg_init @pointer
102
- error_check ZMQ_MSG_INIT_STR, result_code
109
+ raise unless Util.resultcode_ok?(result_code)
103
110
  end
104
111
  end
105
112
 
@@ -109,9 +116,6 @@ module ZMQ
109
116
  #
110
117
  # Can only be initialized via #copy_in_string or #copy_in_bytes once.
111
118
  #
112
- # Can raise a MessageError when #copy_in_string or #copy_in_bytes is
113
- # called multiple times on the same instance.
114
- #
115
119
  def copy_in_string string
116
120
  string_size = string.respond_to?(:bytesize) ? string.bytesize : string.size
117
121
  copy_in_bytes string, string_size if string
@@ -122,12 +126,7 @@ module ZMQ
122
126
  #
123
127
  # Can only be initialized via #copy_in_string or #copy_in_bytes once.
124
128
  #
125
- # Can raise a MessageError when #copy_in_string or #copy_in_bytes is
126
- # called multiple times on the same instance.
127
- #
128
129
  def copy_in_bytes bytes, len
129
- raise MessageError.new "#{self}", -1, -1, "This object cannot be reused; allocate a new one!" if initialized?
130
-
131
130
  data_buffer = LibC.malloc len
132
131
  # writes the exact number of bytes, no null byte to terminate string
133
132
  data_buffer.write_string bytes, len
@@ -135,10 +134,7 @@ module ZMQ
135
134
  # use libC to call free on the data buffer; earlier versions used an
136
135
  # FFI::Function here that called back into Ruby, but Rubinius won't
137
136
  # 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
139
-
140
- error_check ZMQ_MSG_INIT_DATA_STR, result_code
141
- @state = :initialized
137
+ LibZMQ.zmq_msg_init_data @pointer, data_buffer, len, LibC::Free, nil
142
138
  end
143
139
 
144
140
  # Provides the memory address of the +zmq_msg_t+ struct. Used mostly for
@@ -151,15 +147,11 @@ module ZMQ
151
147
  alias :pointer :address
152
148
 
153
149
  def copy source
154
- result_code = LibZMQ.zmq_msg_copy @pointer, source.address
155
- error_check ZMQ_MSG_COPY_STR, result_code
156
- @state = :initialized
150
+ LibZMQ.zmq_msg_copy @pointer, source.address
157
151
  end
158
152
 
159
153
  def move source
160
- result_code = LibZMQ.zmq_msg_copy @pointer, source.address
161
- error_check ZMQ_MSG_MOVE_STR, result_code
162
- @state = :initialized
154
+ LibZMQ.zmq_msg_move @pointer, source.address
163
155
  end
164
156
 
165
157
  # Provides the size of the data buffer for this +zmq_msg_t+ C struct.
@@ -192,17 +184,16 @@ module ZMQ
192
184
  # no ops.
193
185
  #
194
186
  def close
187
+ rc = 0
188
+
195
189
  if @pointer
196
- LibZMQ.zmq_msg_close @pointer
190
+ rc = LibZMQ.zmq_msg_close @pointer
197
191
  @pointer = nil
198
192
  end
193
+
194
+ rc
199
195
  end
200
196
 
201
-
202
- private
203
-
204
- def initialized?(); :initialized == @state; end
205
-
206
197
  end # class Message
207
198
 
208
199
 
@@ -237,19 +228,21 @@ module ZMQ
237
228
  # handles deallocation of the native memory buffer.
238
229
  #
239
230
  def copy_in_bytes bytes, len
240
- super
231
+ rc = super
241
232
 
242
233
  # make sure we have a way to deallocate this memory if the object goes
243
234
  # out of scope
244
235
  define_finalizer
236
+ rc
245
237
  end
246
238
 
247
239
  # Manually release the message struct and its associated data
248
240
  # buffer.
249
241
  #
250
242
  def close
251
- super
243
+ rc = super
252
244
  remove_finalizer
245
+ rc
253
246
  end
254
247
 
255
248