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,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