ffi-rzmq 0.9.0 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+
6
+ *.rbc
7
+ .redcar/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/History.txt CHANGED
@@ -1,3 +1,34 @@
1
+ == 0.9.2 / 20111115
2
+ * Removed all references to the version4 API.
3
+
4
+ * Dropped support for 3.0.x and added support for 3.1.x. The 0mq
5
+ community has pretty much voted to abandon the path taken in 3.0
6
+ so the 3.1 branch is the API that will be supported.
7
+
8
+ * Fixed a bug in Poller#delete where it would erroneously return
9
+ false even when it successfully deleted a socket. Issue 46.
10
+
11
+ * All specs pass for 2.1.x API.
12
+
13
+ * 3 specs fail when run with 3.1 API; these are due to bugs in the
14
+ 0mq library and are *not* ffi-rzmq bugs.
15
+
16
+ * Rescue LoadErrors when loading libzmq. Print a warning about
17
+ adding libzmq.dll to the Windows PATH for that platform. Print
18
+ the search paths where the gem looks for libzmq.
19
+
20
+ == 0.9.1 / 20111027
21
+ * Moved LibC and LibZMQ into the ZMQ module namespace. Necessary to
22
+ avoid namespace collisions with other libraries that also use
23
+ the constants LibC and/or LibZMQ.
24
+
25
+ * Fixed a bug where file descriptors registered on Poll were never
26
+ returned as readable or writable.
27
+
28
+ * Added Socket#recv_multipart. This returns the message body and
29
+ return address envelope as separate arrays. Only to be used with
30
+ XREQ/XREP/DEALER/ROUTER sockets.
31
+
1
32
  == 0.9.0 / 20110930
2
33
  * Changed the behavior of every method that used to produce exceptions.
3
34
  The methods now behave more like the C API functions. They return
data/README.rdoc CHANGED
@@ -9,10 +9,11 @@ function interface). It's a pure ruby wrapper so this gem can be loaded
9
9
  and run by any ruby runtime that supports FFI. That's all of them:
10
10
  MRI 1.9.x, Rubinius and JRuby.
11
11
 
12
- This single gem supports 0mq 2.x and 3.x 0mq APIs. The 0mq project started
13
- making backward-incompatible changes to the API with the 3.x release.
12
+ This single gem supports 0mq 2.x and 3.1.x 0mq APIs. The 0mq project started
13
+ making backward-incompatible changes to the API with the 3.1.x release.
14
14
  The gem auto-configures itself to expose the API conforming to the loaded
15
- C library.
15
+ C library. 0mq API 3.0 is *not* supported; the 0mq community voted to
16
+ abandon it.
16
17
 
17
18
  The impetus behind this library was to provide support for ZeroMQ in
18
19
  JRuby which has native threads. Unlike MRI, which has a GIL, JRuby and
@@ -25,9 +26,12 @@ JRuby and Rubinius will likely be the best environments to run this library.
25
26
  There has been a major change in API from the 0.8.x series to 0.9.0.
26
27
  Previously the Socket#send and Socket#recv methods could raise exceptions
27
28
  when there was an error. They also returned true or false depending on
28
- the success of failure of the operation. Mixing and matching return codes
29
- and exceptions is a terrible idea in practice, so all of the exception
30
- code from Socket has been removed.
29
+ the success or failure of the operation. The exceptions were also used
30
+ for managing flow control when doing non-blocking send/recv.
31
+
32
+ Mixing and matching return codes and exceptions is a terrible idea in
33
+ practice. Using them for flow control is an even worse sin. So all of
34
+ the exception code from Socket has been removed.
31
35
 
32
36
  The Poller and Message class have also had their exceptions removed for
33
37
  returning errors on instance methods.
@@ -42,9 +46,11 @@ constructed object or nil. Code should check for nil returns before using
42
46
  the object.
43
47
 
44
48
  This is a *breaking* change. I'm sorry but the old API's inconsistency was
45
- causing too many problems. It now more closely mimics the 0mq C API. If you
46
- prefer the original API, it should be relatively simple to wrap these
47
- classes up to provide the original API.
49
+ causing too many problems. It now more closely mimics the 0mq C API. IF YOU
50
+ PREFER THE ORIGINAL API WHICH WAS CLOSER TO IDIOMATIC RUBY, IT SHOULD BE
51
+ EASY TO WRAP THESE CLASSES UP TO PROVIDE A MORE IDIOMATIC API. I SUGGEST
52
+ YOU CREATE A PATCH *OR* CREATE A SEPARATE GEM TO WRAP THIS ONE. Sorry for
53
+ shouting but I wanted to make sure this stood out.
48
54
 
49
55
  All example code has been updated to use the new Ruby API.
50
56
 
@@ -135,7 +141,7 @@ I highly recommend visiting the Learn Ruby 0mq project for a bunch of good code
135
141
 
136
142
  == REQUIREMENTS:
137
143
 
138
- * 0mq 2.1.x or later; 2.0.x is no longer supported
144
+ * 0mq 2.1.x, 3.1.x or later; 2.0.x and 3.0.x are no longer supported
139
145
 
140
146
  The ZeroMQ library must be installed on your system in a well-known location
141
147
  like /usr/local/lib. This is the default for new ZeroMQ installs.
@@ -167,7 +173,11 @@ To build from git master:
167
173
  % gem build ffi-rzmq.gemspec
168
174
  % gem install ffi-rzmq-*.gem
169
175
 
170
-
176
+
177
+ NOTE for Windows users!
178
+ In order for this gem to find the libzmq.dll, it *must* be on the Windows PATH. Google
179
+ for "modify windows path" for instructions on how to do that if you are unfamiliar with
180
+ that activity.
171
181
 
172
182
  == LICENSE:
173
183
 
data/Rakefile CHANGED
@@ -1,38 +1,6 @@
1
- begin
2
- require 'bones'
3
- rescue LoadError
4
- abort '### Please install the "bones" gem ###'
5
- end
6
-
7
- namespace :win do
8
-
9
- desc 'Build and install gem under Windows. Mr Bones just has to break things using tar.'
10
- task :install do
11
- PKG_PATH = File.join(File.dirname(__FILE__), 'pkg')
12
- NAME = File.basename(File.dirname(__FILE__))
13
- rm_rf PKG_PATH
14
- system "gem build #{NAME}.gemspec"
15
- mkdir_p PKG_PATH
16
- mv "#{NAME}-*.gem", PKG_PATH
17
- system "gem install #{PKG_PATH}/#{NAME}-*.gem"
18
- end
19
- end
1
+ require 'bundler/gem_tasks'
20
2
 
21
3
  require 'rspec/core/rake_task'
22
- RSpec::Core::RakeTask.new(:spec)
4
+ RSpec::Core::RakeTask.new
23
5
 
24
6
  task :default => :spec
25
-
26
- Bones {
27
- name 'ffi-rzmq'
28
- authors 'Chuck Remes'
29
- email 'cremes@mac.com'
30
- url 'http://github.com/chuckremes/ffi-rzmq'
31
- readme_file 'README.rdoc'
32
- ruby_opts.clear # turn off warnings
33
-
34
- # necessary for MRI; unnecessary for JRuby and RBX
35
- # can't enable this until JRuby & RBX have a way of dealing with it cleanly
36
- #depend_on 'ffi', '>= 1.0.0'
37
- }
38
-
data/examples/README.rdoc CHANGED
@@ -10,7 +10,7 @@ All of the examples assume the lib directory containing the gem sources is two d
10
10
 
11
11
  The ZeroMQ C libraries need to be downloaded, compiled and installed separately from the gem. Please see http://www.zeromq.org/area:download for links to the downloadable files along with some simple installation instructions. Also, be sure to check the FAQ if you run into problems with compiling.
12
12
 
13
- This gem auto-configures itself to conform to the API for 0mq 2.1.x, 3.x and 4.x. The 0mq project started making backward-incompatible changes with the 3.x branch. Rather than create separate gems, this one handles all of them.
13
+ This gem auto-configures itself to conform to the API for 0mq 2.1.x and 3.1.x. The 0mq project started making backward-incompatible changes with the 3.x branch. Rather than create separate gems, this one handles all of them.
14
14
 
15
15
  It is possible to install the libzmq* files directly into the gem in the ext/ directory. This directory is checked for loadable libraries first before it falls back to checking the system paths.
16
16
 
@@ -0,0 +1,139 @@
1
+
2
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq')
3
+
4
+
5
+ # Within a single process, we start up two threads. One thread has a REQ (request)
6
+ # socket and the second thread has a REP (reply) socket. We measure the
7
+ # *round-trip* latency between these sockets. Only *one* message is in flight at
8
+ # any given moment.
9
+ #
10
+ # This example also illustrates how a single context can be shared amongst several
11
+ # threads. Sharing a single context also allows a user to specify the "inproc"
12
+ # transport in addition to "tcp" and "ipc".
13
+ #
14
+ # % ruby latency_measurement.rb tcp://127.0.0.1:5555 1024 1_000_000
15
+ #
16
+ # % ruby latency_measurement.rb inproc://lm_sock 1024 1_000_000
17
+ #
18
+
19
+ if ARGV.length < 3
20
+ puts "usage: ruby latency_measurement.rb <connect-to> <message-size> <roundtrip-count>"
21
+ exit
22
+ end
23
+
24
+ link = ARGV[0]
25
+ message_size = ARGV[1].to_i
26
+ roundtrip_count = ARGV[2].to_i
27
+
28
+ def assert(rc)
29
+ raise "Last API call failed at #{caller(1)}" unless rc >= 0
30
+ end
31
+
32
+ begin
33
+ master_context = ZMQ::Context.new
34
+ rescue ContextError => e
35
+ STDERR.puts "Failed to allocate context or socket!"
36
+ raise
37
+ end
38
+
39
+
40
+ class Receiver
41
+ def initialize context, link, size, count
42
+ @context = context
43
+ @link = link
44
+ @size = size
45
+ @count = count
46
+
47
+ begin
48
+ @socket = @context.socket(ZMQ::REP)
49
+ rescue ContextError => e
50
+ STDERR.puts "Failed to allocate REP socket!"
51
+ raise
52
+ end
53
+
54
+ assert(@socket.setsockopt(ZMQ::LINGER, 100))
55
+
56
+ assert(@socket.setsockopt(ZMQ::HWM, 100))
57
+
58
+ assert(@socket.bind(@link))
59
+ end
60
+
61
+ def run
62
+ @count.times do
63
+ string = ''
64
+ assert(@socket.recv_string(string, 0))
65
+
66
+ raise "Message size doesn't match, expected [#{@size}] but received [#{string.size}]" if @size != string.size
67
+
68
+ assert(@socket.send_string(string, 0))
69
+ end
70
+
71
+ assert(@socket.close)
72
+ end
73
+ end
74
+
75
+ class Transmitter
76
+ def initialize context, link, size, count
77
+ @context = context
78
+ @link = link
79
+ @size = size
80
+ @count = count
81
+
82
+ begin
83
+ @socket = @context.socket(ZMQ::REQ)
84
+ rescue ContextError => e
85
+ STDERR.puts "Failed to allocate REP socket!"
86
+ raise
87
+ end
88
+
89
+ assert(@socket.setsockopt(ZMQ::LINGER, 100))
90
+
91
+ assert(@socket.setsockopt(ZMQ::HWM, 100))
92
+
93
+ assert(@socket.connect(@link))
94
+ end
95
+
96
+ def run
97
+ msg = "#{ '3' * @size }"
98
+
99
+ elapsed = elapsed_microseconds do
100
+ @count.times do
101
+ assert(@socket.send_string(msg, 0))
102
+ assert(@socket.recv_string(msg, 0))
103
+
104
+ raise "Message size doesn't match, expected [#{@size}] but received [#{msg.size}]" if @size != msg.size
105
+ end
106
+ end
107
+
108
+ latency = elapsed / @count / 2
109
+
110
+ puts "message size: %i [B]" % @size
111
+ puts "roundtrip count: %i" % @count
112
+ puts "throughput (msgs/s): %i" % (@count / (elapsed / 1_000_000))
113
+ puts "mean latency: %.3f [us]" % latency
114
+ assert(@socket.close)
115
+ end
116
+
117
+ def elapsed_microseconds(&blk)
118
+ start = Time.now
119
+ yield
120
+ value = ((Time.now - start) * 1_000_000)
121
+ end
122
+ end
123
+
124
+ threads = []
125
+ threads << Thread.new do
126
+ receiver = Receiver.new(master_context, link, message_size, roundtrip_count)
127
+ receiver.run
128
+ end
129
+
130
+ sleep 1
131
+
132
+ threads << Thread.new do
133
+ transmitter = Transmitter.new(master_context, link, message_size, roundtrip_count)
134
+ transmitter.run
135
+ end
136
+
137
+ threads.each {|t| t.join}
138
+
139
+ master_context.terminate
@@ -0,0 +1,93 @@
1
+
2
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq')
3
+
4
+
5
+ def assert(rc)
6
+ raise "Last API call failed at #{caller(1)}" unless rc >= 0
7
+ end
8
+
9
+ link = "tcp://127.0.0.1:5555"
10
+
11
+
12
+ begin
13
+ ctx = ZMQ::Context.new
14
+ s1 = ctx.socket(ZMQ::XREQ)
15
+ s2 = ctx.socket(ZMQ::XREP)
16
+ rescue ContextError => e
17
+ STDERR.puts "Failed to allocate context or socket"
18
+ raise
19
+ end
20
+
21
+ s1.identity = 'socket1.xreq'
22
+ s2.identity = 'socket2.xrep'
23
+
24
+ assert(s1.setsockopt(ZMQ::LINGER, 100))
25
+ assert(s2.setsockopt(ZMQ::LINGER, 100))
26
+
27
+ assert(s1.bind(link))
28
+ assert(s2.connect(link))
29
+
30
+ poller = ZMQ::Poller.new
31
+ poller.register_readable(s2)
32
+ poller.register_writable(s1)
33
+
34
+ start_time = Time.now
35
+ @unsent = true
36
+
37
+ until @done do
38
+ assert(poller.poll_nonblock)
39
+
40
+ # send the message after 5 seconds
41
+ if Time.now - start_time > 5 && @unsent
42
+ puts "sending payload nonblocking"
43
+
44
+ 5.times do |i|
45
+ payload = "#{ i.to_s * 40 }"
46
+ assert(s1.send_string(payload, ZMQ::NOBLOCK))
47
+ end
48
+ @unsent = false
49
+ end
50
+
51
+ # check for messages after 1 second
52
+ if Time.now - start_time > 1
53
+ poller.readables.each do |sock|
54
+
55
+ if sock.identity =~ /xrep/
56
+ routing_info = ''
57
+ assert(sock.recv_string(routing_info, ZMQ::NOBLOCK))
58
+ puts "routing_info received [#{routing_info}] on socket.identity [#{sock.identity}]"
59
+ else
60
+ routing_info = nil
61
+ received_msg = ''
62
+ assert(sock.recv_string(received_msg, ZMQ::NOBLOCK))
63
+
64
+ # skip to the next iteration if received_msg is nil; that means we got an EAGAIN
65
+ next unless received_msg
66
+ puts "message received [#{received_msg}] on socket.identity [#{sock.identity}]"
67
+ end
68
+
69
+ while sock.more_parts? do
70
+ received_msg = ''
71
+ assert(sock.recv_string(received_msg, ZMQ::NOBLOCK))
72
+
73
+ puts "message received [#{received_msg}]"
74
+ end
75
+
76
+ puts "kick back a reply"
77
+ assert(sock.send_string(routing_info, ZMQ::SNDMORE | ZMQ::NOBLOCK)) if routing_info
78
+ time = Time.now.strftime "%Y-%m-%dT%H:%M:%S.#{Time.now.usec}"
79
+ reply = "reply " + sock.identity.upcase + " #{time}"
80
+ puts "sent reply [#{reply}], #{time}"
81
+ assert(sock.send_string(reply))
82
+ @done = true
83
+ poller.register_readable(s1)
84
+ end
85
+ end
86
+ end
87
+
88
+ puts "executed in [#{Time.now - start_time}] seconds"
89
+
90
+ assert(s1.close)
91
+ assert(s2.close)
92
+
93
+ ctx.terminate
@@ -0,0 +1,139 @@
1
+
2
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq')
3
+
4
+
5
+ # Within a single process, we start up two threads. One thread has a REQ (request)
6
+ # socket and the second thread has a REP (reply) socket. We measure the
7
+ # *round-trip* latency between these sockets. Only *one* message is in flight at
8
+ # any given moment.
9
+ #
10
+ # This example also illustrates how a single context can be shared amongst several
11
+ # threads. Sharing a single context also allows a user to specify the "inproc"
12
+ # transport in addition to "tcp" and "ipc".
13
+ #
14
+ # % ruby latency_measurement.rb tcp://127.0.0.1:5555 1024 1_000_000
15
+ #
16
+ # % ruby latency_measurement.rb inproc://lm_sock 1024 1_000_000
17
+ #
18
+
19
+ if ARGV.length < 3
20
+ puts "usage: ruby latency_measurement.rb <connect-to> <message-size> <roundtrip-count>"
21
+ exit
22
+ end
23
+
24
+ link = ARGV[0]
25
+ message_size = ARGV[1].to_i
26
+ roundtrip_count = ARGV[2].to_i
27
+
28
+ def assert(rc)
29
+ raise "Last API call failed at #{caller(1)}" unless rc >= 0
30
+ end
31
+
32
+ begin
33
+ master_context = ZMQ::Context.new
34
+ rescue ContextError => e
35
+ STDERR.puts "Failed to allocate context or socket!"
36
+ raise
37
+ end
38
+
39
+
40
+ class Receiver
41
+ def initialize context, link, size, count
42
+ @context = context
43
+ @link = link
44
+ @size = size
45
+ @count = count
46
+
47
+ begin
48
+ @socket = @context.socket(ZMQ::REP)
49
+ rescue ContextError => e
50
+ STDERR.puts "Failed to allocate REP socket!"
51
+ raise
52
+ end
53
+
54
+ assert(@socket.setsockopt(ZMQ::LINGER, 100))
55
+ assert(@socket.setsockopt(ZMQ::RCVHWM, 100))
56
+ assert(@socket.setsockopt(ZMQ::SNDHWM, 100))
57
+
58
+ assert(@socket.bind(@link))
59
+ end
60
+
61
+ def run
62
+ @count.times do
63
+ string = ''
64
+ assert(@socket.recv_string(string, 0))
65
+
66
+ raise "Message size doesn't match, expected [#{@size}] but received [#{string.size}]" if @size != string.size
67
+
68
+ assert(@socket.send_string(string, 0))
69
+ end
70
+
71
+ assert(@socket.close)
72
+ end
73
+ end
74
+
75
+ class Transmitter
76
+ def initialize context, link, size, count
77
+ @context = context
78
+ @link = link
79
+ @size = size
80
+ @count = count
81
+
82
+ begin
83
+ @socket = @context.socket(ZMQ::REQ)
84
+ rescue ContextError => e
85
+ STDERR.puts "Failed to allocate REP socket!"
86
+ raise
87
+ end
88
+
89
+ assert(@socket.setsockopt(ZMQ::LINGER, 100))
90
+ assert(@socket.setsockopt(ZMQ::RCVHWM, 100))
91
+ assert(@socket.setsockopt(ZMQ::SNDHWM, 100))
92
+
93
+ assert(@socket.connect(@link))
94
+ end
95
+
96
+ def run
97
+ msg = "#{ '3' * @size }"
98
+
99
+ elapsed = elapsed_microseconds do
100
+ @count.times do
101
+ assert(@socket.send_string(msg, 0))
102
+ assert(@socket.recv_string(msg, 0))
103
+
104
+ raise "Message size doesn't match, expected [#{@size}] but received [#{msg.size}]" if @size != msg.size
105
+ end
106
+ end
107
+
108
+ latency = elapsed / @count / 2
109
+
110
+ puts "message size: %i [B]" % @size
111
+ puts "roundtrip count: %i" % @count
112
+ puts "throughput (msgs/s): %i" % (@count / (elapsed / 1_000_000))
113
+ puts "mean latency: %.3f [us]" % latency
114
+ assert(@socket.close)
115
+ end
116
+
117
+ def elapsed_microseconds(&blk)
118
+ start = Time.now
119
+ yield
120
+ value = ((Time.now - start) * 1_000_000)
121
+ end
122
+ end
123
+
124
+ threads = []
125
+ threads << Thread.new do
126
+ receiver = Receiver.new(master_context, link, message_size, roundtrip_count)
127
+ receiver.run
128
+ end
129
+
130
+ sleep 1
131
+
132
+ threads << Thread.new do
133
+ transmitter = Transmitter.new(master_context, link, message_size, roundtrip_count)
134
+ transmitter.run
135
+ end
136
+
137
+ threads.each {|t| t.join}
138
+
139
+ master_context.terminate