ffi-rzmq-2.0.4/ 0000755 0000041 0000041 00000000000 12463440532 013303 5 ustar www-data www-data ffi-rzmq-2.0.4/Rakefile 0000644 0000041 0000041 00000000463 12463440532 014753 0 ustar www-data www-data require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new
task :default => :spec
task :console do
require 'irb'
require 'irb/completion'
require 'ffi-rzmq'
ARGV.clear
IRB.start
end
task :pryconsole do
require 'pry'
require 'ffi-rzmq'
ARGV.clear
Pry.start
end ffi-rzmq-2.0.4/Gemfile 0000644 0000041 0000041 00000000120 12463440532 014567 0 ustar www-data www-data source "http://rubygems.org"
gemspec
gem "jruby-openssl", :platform => :jruby
ffi-rzmq-2.0.4/examples/ 0000755 0000041 0000041 00000000000 12463440532 015121 5 ustar www-data www-data ffi-rzmq-2.0.4/examples/publish_subscribe.rb 0000644 0000041 0000041 00000003701 12463440532 021156 0 ustar www-data www-data require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
link = "tcp://127.0.0.1:5555"
begin
ctx = ZMQ::Context.new
s1 = ctx.socket(ZMQ::PUB)
s2 = ctx.socket(ZMQ::SUB)
s3 = ctx.socket(ZMQ::SUB)
s4 = ctx.socket(ZMQ::SUB)
s5 = ctx.socket(ZMQ::SUB)
rescue ContextError => e
STDERR.puts "Failed to allocate context or socket!"
raise
end
assert(s1.setsockopt(ZMQ::LINGER, 100))
assert(s2.setsockopt(ZMQ::SUBSCRIBE, '')) # receive all
assert(s3.setsockopt(ZMQ::SUBSCRIBE, 'animals')) # receive any starting with this string
assert(s4.setsockopt(ZMQ::SUBSCRIBE, 'animals.dog'))
assert(s5.setsockopt(ZMQ::SUBSCRIBE, 'animals.cat'))
assert(s1.bind(link))
assert(s2.connect(link))
assert(s3.connect(link))
assert(s4.connect(link))
assert(s5.connect(link))
sleep 1
topic = "animals.dog"
payload = "Animal crackers!"
s1.identity = "publisher-A"
puts "sending"
# use the new multi-part messaging support to
# automatically separate the topic from the body
assert(s1.send_string(topic, ZMQ::SNDMORE))
assert(s1.send_string(payload, ZMQ::SNDMORE))
assert(s1.send_string(s1.identity))
topic = ''
assert(s2.recv_string(topic))
body = ''
assert(s2.recv_string(body)) if s2.more_parts?
identity = ''
assert(s2.recv_string(identity)) if s2.more_parts?
puts "s2 received topic [#{topic}], body [#{body}], identity [#{identity}]"
topic = ''
assert(s3.recv_string(topic))
body = ''
assert(s3.recv_string(body)) if s3.more_parts?
puts "s3 received topic [#{topic}], body [#{body}]"
topic = ''
assert(s4.recv_string(topic))
body = ''
assert(s4.recv_string(body)) if s4.more_parts?
puts "s4 received topic [#{topic}], body [#{body}]"
s5_string = ''
rc = s5.recv_string(s5_string, ZMQ::DONTWAIT)
eagain = (rc == -1 && ZMQ::Util.errno == ZMQ::EAGAIN)
puts(eagain ? "s5 received no messages" : "s5 FAILED")
[s1, s2, s3, s4, s5].each do |socket|
assert(socket.close)
end
ctx.terminate
ffi-rzmq-2.0.4/examples/request_response.rb 0000644 0000041 0000041 00000001447 12463440532 021062 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
link = "tcp://127.0.0.1:5555"
begin
ctx = ZMQ::Context.new
s1 = ctx.socket(ZMQ::REQ)
s2 = ctx.socket(ZMQ::REP)
rescue ContextError => e
STDERR.puts "Failed to allocate context or socket"
raise
end
assert(s1.setsockopt(ZMQ::LINGER, 100))
assert(s2.setsockopt(ZMQ::LINGER, 100))
assert(s2.bind(link))
assert(s1.connect(link))
payload = "#{ '3' * 2048 }"
sent_msg = ZMQ::Message.new(payload)
received_msg = ZMQ::Message.new
assert(s1.sendmsg(sent_msg))
assert(s2.recvmsg(received_msg))
result = payload == received_msg.copy_out_string ? "Request received" : "Received wrong payload"
p result
assert(s1.close)
assert(s2.close)
ctx.terminate
ffi-rzmq-2.0.4/examples/repreq_over_curve.rb 0000644 0000041 0000041 00000003100 12463440532 021175 0 ustar www-data www-data require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
# This example shows the basics of a CURVE-secured REP/REQ setup between
# a Server and Client. This is the minimal required setup for authenticating
# a connection between a Client and a Server. For validating the Client's authentication
# once the initial connection has succeeded, you'll need a ZAP handler on the Server.
# Build the Server's keys
server_public_key, server_private_key = ZMQ::Util.curve_keypair
# Build the Client's keys
client_public_key, client_private_key = ZMQ::Util.curve_keypair
context = ZMQ::Context.new
bind_point = "tcp://127.0.0.1:4455"
##
# Configure the Server
##
server = context.socket ZMQ::REP
server.setsockopt(ZMQ::CURVE_SERVER, 1)
server.setsockopt(ZMQ::CURVE_SECRETKEY, server_private_key)
server.bind(bind_point)
##
# Configure the Client to talk to the Server
##
client = context.socket ZMQ::REQ
client.setsockopt(ZMQ::CURVE_SERVERKEY, server_public_key)
client.setsockopt(ZMQ::CURVE_PUBLICKEY, client_public_key)
client.setsockopt(ZMQ::CURVE_SECRETKEY, client_private_key)
client.connect(bind_point)
##
# Show that communication still works
##
client_message = "Hello Server!"
server_response = "Hello Client!"
puts "Client sending: #{client_message}"
client.send_string client_message
server.recv_string(server_message = '')
puts "Server received: #{server_message}, replying with #{server_response}"
server.send_string(server_response)
client.recv_string(response = '')
puts "Client has received: #{response}"
puts "Finished"
client.close
server.close
context.terminate
ffi-rzmq-2.0.4/examples/remote_lat.rb 0000644 0000041 0000041 00000003676 12463440532 017615 0 ustar www-data www-data #
# Copyright (c) 2007-2010 iMatix Corporation
#
# This file is part of 0MQ.
#
# 0MQ is free software; you can redistribute it and/or modify it under
# the terms of the Lesser GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# 0MQ is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Lesser GNU General Public License for more details.
#
# You should have received a copy of the Lesser GNU General Public License
# along with this program. If not, see .
require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
if ARGV.length < 3
puts "usage: ruby remote_lat.rb "
exit
end
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
connect_to = ARGV[0]
message_size = ARGV[1].to_i
roundtrip_count = ARGV[2].to_i
begin
ctx = ZMQ::Context.new
s = ctx.socket(ZMQ::REQ)
rescue ContextError => e
STDERR.puts "Failed to allocate context or socket!"
raise
end
assert(s.setsockopt(ZMQ::LINGER, 100))
assert(s.connect(connect_to))
msg = "#{ '3' * message_size }"
start_time = Time.now
roundtrip_count.times do
assert(s.send_string(msg, 0))
msg = ''
assert(s.recv_string(msg, 0))
raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size
end
end_time = Time.now
elapsed_secs = (end_time.to_f - start_time.to_f)
elapsed_usecs = elapsed_secs * 1000000
latency = elapsed_usecs / roundtrip_count / 2
puts "message size: %i [B]" % message_size
puts "roundtrip count: %i" % roundtrip_count
puts "throughput (msgs/s): %i" % (roundtrip_count / elapsed_secs)
puts "mean latency: %.3f [us]" % latency
assert(s.close)
ctx.terminate
ffi-rzmq-2.0.4/examples/throughput_measurement.rb 0000644 0000041 0000041 00000007044 12463440532 022271 0 ustar www-data www-data require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
require 'thread'
# Within a single process, we start up five threads. Main thread has a PUB (publisher)
# socket and the secondary threads have SUB (subscription) sockets. We measure the
# *throughput* between these sockets. A high-water mark (HWM) is *not* set, so the
# publisher queue is free to grow to the size of memory without dropping packets.
#
# This example also illustrates how a single context can be shared amongst several
# threads. Sharing a single context also allows a user to specify the "inproc"
# transport in addition to "tcp" and "ipc".
#
# % ruby throughput_measurement.rb tcp://127.0.0.1:5555 1024 1_000_000
#
# % ruby throughput_measurement.rb inproc://lm_sock 1024 1_000_000
#
if ARGV.length < 3
puts "usage: ruby throughput_measurement.rb "
exit
end
link = ARGV[0]
message_size = ARGV[1].to_i
count = ARGV[2].to_i
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
begin
master_context = ZMQ::Context.new
rescue ContextError => e
STDERR.puts "Failed to allocate context or socket!"
raise
end
class Receiver
def initialize context, link, size, count, stats
@context = context
@link = link
@size = size
@count = count
@stats = stats
begin
@socket = @context.socket(ZMQ::SUB)
rescue ContextError => e
STDERR.puts "Failed to allocate SUB socket!"
raise
end
assert(@socket.setsockopt(ZMQ::LINGER, 100))
assert(@socket.setsockopt(ZMQ::SUBSCRIBE, ""))
assert(@socket.connect(@link))
end
def run
msg = ZMQ::Message.new
assert(@socket.recvmsg(msg))
elapsed = elapsed_microseconds do
(@count - 1).times do
assert(@socket.recvmsg(msg))
end
end
@stats.record_elapsed(elapsed)
assert(@socket.close)
end
def elapsed_microseconds(&blk)
start = Time.now
yield
((Time.now - start) * 1_000_000)
end
end
class Transmitter
def initialize context, link, size, count
@context = context
@link = link
@size = size
@count = count
begin
@socket = @context.socket(ZMQ::PUB)
rescue ContextError => e
STDERR.puts "Failed to allocate PUB socket!"
raise
end
assert(@socket.setsockopt(ZMQ::LINGER, 100))
assert(@socket.bind(@link))
end
def run
sleep 1
contents = "#{'0' * @size}"
i = 0
while i < @count
msg = ZMQ::Message.new(contents)
assert(@socket.sendmsg(msg))
i += 1
end
end
def close
assert(@socket.close)
end
end
class Stats
def initialize size, count
@size = size
@count = count
@mutex = Mutex.new
@elapsed = []
end
def record_elapsed(elapsed)
@mutex.synchronize do
@elapsed << elapsed
end
end
def output
@elapsed.each do |elapsed|
throughput = @count * 1000000 / elapsed
megabits = throughput * @size * 8 / 1000000
puts "message size: %i [B]" % @size
puts "message count: %i" % @count
puts "mean throughput: %i [msg/s]" % throughput
puts "mean throughput: %.3f [Mb/s]" % megabits
puts
end
end
end
threads = []
stats = Stats.new message_size, count
transmitter = Transmitter.new(master_context, link, message_size, count)
threads << Thread.new do
transmitter.run
end
1.times do
threads << Thread.new do
receiver = Receiver.new(master_context, link, message_size, count, stats)
receiver.run
end
end
threads.each {|t| t.join}
transmitter.close
stats.output
master_context.terminate
ffi-rzmq-2.0.4/examples/sub.rb 0000644 0000041 0000041 00000002671 12463440532 016245 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
#if ARGV.length != 3
# puts "usage: ruby local_throughtput.rb "
# Process.exit
#end
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
bind_to = ARGV[0]
message_size = ARGV[1].to_i
message_count = ARGV[2].to_i
sleep_time = ARGV[3].to_f
begin
ctx = ZMQ::Context.new
s = ZMQ::Socket.new(ctx.pointer, ZMQ::SUB)
rescue ContextError => e
STDERR.puts "Failed to allocate context or socket!"
raise
end
#assert(s.setsockopt(ZMQ::LINGER, 100))
assert(s.setsockopt(ZMQ::SUBSCRIBE, ""))
assert(s.setsockopt(ZMQ::RCVHWM, 20))
#assert(s.setsockopt(ZMQ::RCVHWM, 0))
#assert(s.setsockopt(ZMQ::SNDHWM, 0))
assert(s.connect(bind_to))
sleep 1
msg = ZMQ::Message.new
msg = ''
assert(s.recv_string(msg))
raise unless msg.to_i == 0
start_time = Time.now
i = 1
while i < message_count
assert(s.recv_string(msg))
msg_i = msg.to_i
puts "missed [#{msg_i - i}] messages"
i = msg_i
sleep(sleep_time)
end
end_time = Time.now
elapsed = (end_time.to_f - start_time.to_f) * 1000000
if elapsed == 0
elapsed = 1
end
throughput = message_count * 1000000 / elapsed
megabits = throughput * message_size * 8 / 1000000
puts "message size: %i [B]" % message_size
puts "message count: %i" % message_count
puts "mean throughput: %i [msg/s]" % throughput
puts "mean throughput: %.3f [Mb/s]" % megabits
assert(s.close)
ctx.terminate
ffi-rzmq-2.0.4/examples/reqrep_poll.rb 0000644 0000041 0000041 00000002364 12463440532 017777 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
link = "tcp://127.0.0.1:5554"
begin
ctx = ZMQ::Context.new
s1 = ctx.socket(ZMQ::REQ)
s2 = ctx.socket(ZMQ::REP)
rescue ContextError => e
STDERR.puts "Failed to allocate context or socket!"
raise
end
assert(s1.setsockopt(ZMQ::LINGER, 100))
assert(s2.setsockopt(ZMQ::LINGER, 100))
assert(s1.connect(link))
assert(s2.bind(link))
poller = ZMQ::Poller.new
poller.register_readable(s2)
poller.register_writable(s1)
start_time = Time.now
@unsent = true
until @done do
assert(poller.poll_nonblock)
# send the message after 5 seconds
if Time.now - start_time > 5 && @unsent
payload = "#{ '3' * 1024 }"
puts "sending payload nonblocking"
assert(s1.send_string(payload, ZMQ::DONTWAIT))
@unsent = false
end
# check for messages after 1 second
if Time.now - start_time > 1
poller.readables.each do |sock|
received_msg = ''
assert(sock.recv_string(received_msg, ZMQ::DONTWAIT))
puts "message received [#{received_msg}]"
@done = true
end
end
end
puts "executed in [#{Time.now - start_time}] seconds"
assert(s1.close)
assert(s2.close)
ctx.terminate
ffi-rzmq-2.0.4/examples/README.rdoc 0000644 0000041 0000041 00000012503 12463440532 016730 0 ustar www-data www-data = Examples
== Requirements
1. lib dir
All of the examples assume the lib directory containing the gem sources is one directory up from the location of the example.
2. Installed libzmq library
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.
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.
3. One terminal window
ZeroMQ is used to build network applications. At minimum, there is a "client" application and a "server" application that talk to each other over the network, IPC or an internal thread queue. Several of the examples start the client and server components within separate threads or use the polling mechanism to interleave I/O operations amongst several sockets. A few examples need two terminal windows because the client and server code is in separate files (local_lat.rb/remote_lat.rb, local_throughput.rb/remote_throughput.rb).
== Latency Test
The examples include a latency performance test. The example sets up a pair of REQ/REP sockets and send a message back and forth as fast as possible. There is only a single message in flight at any given moment. The time required to send the message the requested number of times determines overall single-message latency for this type of socket.
==== Files
* latency_measurement.rb
==== Arguments
The remote_lat.rb program takes 3 arguments:
[link_address] Requires a transport string of the format "transport"://"endpoint"<:>. For example, tcp://127.0.0.1:5555
[message size] Size of each message measured in bytes. Allowable range is 1 to 2^(64-1).
[message count] The number of round-trips used for the latency measurements. Allowable range is 1 to 2^(64-1).
==== Execution
In an open terminal window, execute the latency_measurement.rb file.
% ruby latency_measurement.rb tcp://127.0.0.1:5555 1024 100_000
On a relatively new system, it can run 100k messages in under 30 seconds. When complete, the program prints out a few statistics and exits.
Running with a larger "message count" will yield a more accurate latency measurement since nearly all Ruby runtimes require a little warm up time to hit their stride. I recommend 100k as a minimum while 10 million is better for determining a true measure.
On a desktop computer purchased in 2007, all of the Ruby runtimes report a latency of approximately 110 microseconds per message. For comparison, the pure C latency test reports approximately 88 microseconds of latency.
== Throughput Test
The examples include a throughput performance test. The example sets up a pair of PUB/SUB sockets and publish messages as fast as possible to a subscriber listening for every message. The publisher can send much faster than the subscriber can retrieve messages.
Since the publisher completes first, the program waits for all subscribers to exit before closing the PUB socket. This is necessary because all enqueued messages are discarded when the socket is closed.
The subscriber prints some statistics when it exits.
==== Files
* throughput_measurement.rb
==== Arguments
The throughput_measurement.rb program takes 3 arguments:
[link_address] Requires a transport string of the format "transport"://"endpoint"<:>. For example, tcp://127.0.0.1:5555
[message size] Size of each message measured in bytes. Allowable range is 1 to 2^(64-1).
[message count] The number of round-trips used for the latency measurements. Allowable range is 1 to 2^(64-1).
==== Execution
In an open terminal, execute the throughput_measurement.rb script.
% ruby throughput_measurement.rb tcp://127.0.0.1:5555 1024 100_000
On a relatively new system, it can run 100k messages in under 10 seconds. When complete, the program prints out a few statistics and exits.
Running with a larger "message count" will yield a more accurate latency measurement since nearly all Ruby runtimes require a little warm up time to hit their stride. I recommend 100k as a minimum while 1 million is better for determining a true measure. NOTE! The publisher can send much faster than the subscriber so the publisher's queue will grow very rapidly in RAM. For 1 million messages (or more) this can consume hundreds of megabytes or gigabytes of RAM. On my system, sending 10 million messages requires 10 GB of RAM before the subscriber can catch up.
On a desktop computer purchased in 2007, all of the Ruby runtimes report a throughput of approximately 150k messages per second. For comparison, the pure C throughput test reports approximately 260k messages per second.
== Poll
For a reasonable example of using zmq_poll(), take a look at the reqrep_poll.rb program. It illustrates the use of zmq_poll(), as wrapped by the Ruby library, for detecting and responding to read and write events recorded on sockets. It also shows how to use ZMQ::NO_BLOCK/ZMQ::DONTWAIT for non-blocking send and receive.
==== Files
* reqrep_poll.rb
==== Arguments
None.
==== Execution
This program is completely self-contained, so it only requires a single terminal window for execution.
% ruby reqrep_poll.rb
ffi-rzmq-2.0.4/examples/remote_throughput.rb 0000644 0000041 0000041 00000001767 12463440532 021245 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
if ARGV.length != 3
puts "usage: ruby remote_throughput.rb "
Process.exit
end
connect_to = ARGV[0]
message_size = ARGV[1].to_i
message_count = ARGV[2].to_i
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
begin
ctx = ZMQ::Context.new
s = ZMQ::Socket.new(ctx.pointer, ZMQ::PUB)
rescue ContextError => e
STDERR.puts "Could not allocate a context or socket!"
raise
end
#assert(s.setsockopt(ZMQ::LINGER, 1_000))
#assert(s.setsockopt(ZMQ::RCVHWM, 0))
#assert(s.setsockopt(ZMQ::SNDHWM, 0))
assert(s.connect(connect_to))
# give the downstream SUB socket a chance to register its
# subscription filters with this PUB socket
puts "Hit any key to start publishing"
STDIN.gets
contents = "#{'0'*message_size}"
i = 0
while i < message_count
msg = ZMQ::Message.new(contents)
assert(s.sendmsg(msg))
puts i
i += 1
end
sleep 10
assert(s.close)
ctx.terminate
ffi-rzmq-2.0.4/examples/xreqxrep_poll.rb 0000644 0000041 0000041 00000004443 12463440532 020357 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
link = "tcp://127.0.0.1:5555"
begin
ctx = ZMQ::Context.new
s1 = ctx.socket(ZMQ::XREQ)
s2 = ctx.socket(ZMQ::XREP)
rescue ContextError => e
STDERR.puts "Failed to allocate context or socket"
raise
end
s1.identity = 'socket1.xreq'
s2.identity = 'socket2.xrep'
assert(s1.setsockopt(ZMQ::LINGER, 100))
assert(s2.setsockopt(ZMQ::LINGER, 100))
assert(s1.bind(link))
assert(s2.connect(link))
poller = ZMQ::Poller.new
poller.register_readable(s2)
poller.register_writable(s1)
start_time = Time.now
@unsent = true
until @done do
assert(poller.poll_nonblock)
# send the message after 5 seconds
if Time.now - start_time > 5 && @unsent
puts "sending payload nonblocking"
5.times do |i|
payload = "#{ i.to_s * 40 }"
assert(s1.send_string(payload, ZMQ::DONTWAIT))
end
@unsent = false
end
# check for messages after 1 second
if Time.now - start_time > 1
poller.readables.each do |sock|
if sock.identity =~ /xrep/
routing_info = ''
assert(sock.recv_string(routing_info, ZMQ::DONTWAIT))
puts "routing_info received [#{routing_info}] on socket.identity [#{sock.identity}]"
else
routing_info = nil
received_msg = ''
assert(sock.recv_string(received_msg, ZMQ::DONTWAIT))
# skip to the next iteration if received_msg is nil; that means we got an EAGAIN
next unless received_msg
puts "message received [#{received_msg}] on socket.identity [#{sock.identity}]"
end
while sock.more_parts? do
received_msg = ''
assert(sock.recv_string(received_msg, ZMQ::DONTWAIT))
puts "message received [#{received_msg}]"
end
puts "kick back a reply"
assert(sock.send_string(routing_info, ZMQ::SNDMORE | ZMQ::DONTWAIT)) if routing_info
time = Time.now.strftime "%Y-%m-%dT%H:%M:%S.#{Time.now.usec}"
reply = "reply " + sock.identity.upcase + " #{time}"
puts "sent reply [#{reply}], #{time}"
assert(sock.send_string(reply))
@done = true
poller.register_readable(s1)
end
end
end
puts "executed in [#{Time.now - start_time}] seconds"
assert(s1.close)
assert(s2.close)
ctx.terminate
ffi-rzmq-2.0.4/examples/pub.rb 0000644 0000041 0000041 00000001734 12463440532 016241 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
if ARGV.length != 3
puts "usage: ruby remote_throughput.rb "
Process.exit
end
connect_to = ARGV[0]
message_size = ARGV[1].to_i
message_count = ARGV[2].to_i
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
begin
ctx = ZMQ::Context.new
s = ZMQ::Socket.new(ctx.pointer, ZMQ::PUB)
rescue ContextError => e
STDERR.puts "Could not allocate a context or socket!"
raise
end
#assert(s.setsockopt(ZMQ::LINGER, 1_000))
#assert(s.setsockopt(ZMQ::RCVHWM, 0))
assert(s.setsockopt(ZMQ::SNDHWM, 100))
assert(s.bind(connect_to))
# the sleep gives the downstream SUB socket a chance to register its
# subscription filters with this PUB socket
puts "Hit any key to start publishing"
STDIN.gets
i = 0
while i < message_count
msg = ZMQ::Message.new(i.to_s)
assert(s.sendmsg(msg))
puts i
i += 1
end
sleep 10
assert(s.close)
ctx.terminate
ffi-rzmq-2.0.4/examples/latency_measurement.rb 0000644 0000041 0000041 00000006466 12463440532 021526 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
# Within a single process, we start up two threads. One thread has a REQ (request)
# socket and the second thread has a REP (reply) socket. We measure the
# *round-trip* latency between these sockets. Only *one* message is in flight at
# any given moment.
#
# This example also illustrates how a single context can be shared amongst several
# threads. Sharing a single context also allows a user to specify the "inproc"
# transport in addition to "tcp" and "ipc".
#
# % ruby latency_measurement.rb tcp://127.0.0.1:5555 1024 1_000_000
#
# % ruby latency_measurement.rb inproc://lm_sock 1024 1_000_000
#
if ARGV.length < 3
puts "usage: ruby latency_measurement.rb "
exit
end
link = ARGV[0]
message_size = ARGV[1].to_i
roundtrip_count = ARGV[2].to_i
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
begin
master_context = ZMQ::Context.new
rescue ContextError => e
STDERR.puts "Failed to allocate context or socket!"
raise
end
class Receiver
def initialize context, link, size, count
@context = context
@link = link
@size = size
@count = count
begin
@socket = @context.socket(ZMQ::REP)
rescue ContextError => e
STDERR.puts "Failed to allocate REP socket!"
raise
end
assert(@socket.setsockopt(ZMQ::LINGER, 100))
assert(@socket.setsockopt(ZMQ::RCVHWM, 100))
assert(@socket.setsockopt(ZMQ::SNDHWM, 100))
assert(@socket.bind(@link))
end
def run
@count.times do
string = ''
assert(@socket.recv_string(string, 0))
raise "Message size doesn't match, expected [#{@size}] but received [#{string.size}]" if @size != string.size
assert(@socket.send_string(string, 0))
end
assert(@socket.close)
end
end
class Transmitter
def initialize context, link, size, count
@context = context
@link = link
@size = size
@count = count
begin
@socket = @context.socket(ZMQ::REQ)
rescue ContextError => e
STDERR.puts "Failed to allocate REP socket!"
raise
end
assert(@socket.setsockopt(ZMQ::LINGER, 100))
assert(@socket.setsockopt(ZMQ::RCVHWM, 100))
assert(@socket.setsockopt(ZMQ::SNDHWM, 100))
assert(@socket.connect(@link))
end
def run
msg = "#{ '3' * @size }"
elapsed = elapsed_microseconds do
@count.times do
assert(@socket.send_string(msg, 0))
assert(@socket.recv_string(msg, 0))
raise "Message size doesn't match, expected [#{@size}] but received [#{msg.size}]" if @size != msg.size
end
end
latency = elapsed / @count / 2
puts "message size: %i [B]" % @size
puts "roundtrip count: %i" % @count
puts "throughput (msgs/s): %i" % (@count / (elapsed / 1_000_000))
puts "mean latency: %.3f [us]" % latency
assert(@socket.close)
end
def elapsed_microseconds(&blk)
start = Time.now
yield
value = ((Time.now - start) * 1_000_000)
end
end
threads = []
threads << Thread.new do
receiver = Receiver.new(master_context, link, message_size, roundtrip_count)
receiver.run
end
sleep 1
threads << Thread.new do
transmitter = Transmitter.new(master_context, link, message_size, roundtrip_count)
transmitter.run
end
threads.each {|t| t.join}
master_context.terminate
ffi-rzmq-2.0.4/examples/local_throughput.rb 0000644 0000041 0000041 00000002500 12463440532 021026 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
if ARGV.length != 3
puts "usage: ruby local_throughtput.rb "
Process.exit
end
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
bind_to = ARGV[0]
message_size = ARGV[1].to_i
message_count = ARGV[2].to_i
begin
ctx = ZMQ::Context.new
s = ZMQ::Socket.new(ctx.pointer, ZMQ::SUB)
rescue ContextError => e
STDERR.puts "Failed to allocate context or socket!"
raise
end
#assert(s.setsockopt(ZMQ::LINGER, 100))
assert(s.setsockopt(ZMQ::SUBSCRIBE, ""))
#assert(s.setsockopt(ZMQ::RCVHWM, 0))
#assert(s.setsockopt(ZMQ::SNDHWM, 0))
assert(s.bind(bind_to))
sleep 1
msg = ZMQ::Message.new
msg = ''
assert(s.recv_string(msg))
#assert(s.recvmsg(msg))
start_time = Time.now
i = 1
while i < message_count
#assert(s.recvmsg(msg))
assert(s.recv_string(msg))
puts i
i += 1
end
end_time = Time.now
elapsed = (end_time.to_f - start_time.to_f) * 1000000
if elapsed == 0
elapsed = 1
end
throughput = message_count * 1000000 / elapsed
megabits = throughput * message_size * 8 / 1000000
puts "message size: %i [B]" % message_size
puts "message count: %i" % message_count
puts "mean throughput: %i [msg/s]" % throughput
puts "mean throughput: %.3f [Mb/s]" % megabits
assert(s.close)
ctx.terminate
ffi-rzmq-2.0.4/examples/local_lat_poll.rb 0000644 0000041 0000041 00000002621 12463440532 020427 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
if ARGV.length < 3
puts "usage: ruby local_lat.rb "
exit
end
link = ARGV[0]
message_size = ARGV[1].to_i
roundtrip_count = ARGV[2].to_i
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
begin
ctx = ZMQ::Context.new
s1 = ctx.socket(ZMQ::REQ)
s2 = ctx.socket(ZMQ::REP)
rescue ContextError => e
STDERR.puts "Failed to allocate context or socket!"
raise
end
assert(s1.setsockopt(ZMQ::LINGER, 100))
assert(s2.setsockopt(ZMQ::LINGER, 100))
assert(s1.connect(link))
assert(s2.bind(link))
poller = ZMQ::Poller.new
poller.register_readable(s2)
poller.register_readable(s1)
start_time = Time.now
# kick it off
message = ZMQ::Message.new("a" * message_size)
assert(s1.sendmsg(message, ZMQ::DONTWAIT))
i = roundtrip_count
until i.zero?
i -= 1
assert(poller.poll_nonblock)
poller.readables.each do |socket|
received_message = ''
assert(socket.recv_string(received_message, ZMQ::DONTWAIT))
assert(socket.sendmsg(ZMQ::Message.new(received_message), ZMQ::DONTWAIT))
end
end
elapsed_usecs = (Time.now.to_f - start_time.to_f) * 1_000_000
latency = elapsed_usecs / roundtrip_count / 2
puts "mean latency: %.3f [us]" % latency
puts "received all messages in %.3f seconds" % (elapsed_usecs / 1_000_000)
assert(s1.close)
assert(s2.close)
ctx.terminate
ffi-rzmq-2.0.4/examples/local_lat.rb 0000644 0000041 0000041 00000003172 12463440532 017403 0 ustar www-data www-data #
# Copyright (c) 2007-2010 iMatix Corporation
#
# This file is part of 0MQ.
#
# 0MQ is free software; you can redistribute it and/or modify it under
# the terms of the Lesser GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# 0MQ is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Lesser GNU General Public License for more details.
#
# You should have received a copy of the Lesser GNU General Public License
# along with this program. If not, see .
require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rzmq')
if ARGV.length < 3
puts "usage: ruby local_lat.rb "
exit
end
bind_to = ARGV[0]
message_size = ARGV[1].to_i
roundtrip_count = ARGV[2].to_i
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
begin
ctx = ZMQ::Context.new
s = ctx.socket(ZMQ::REP)
rescue ContextError => e
STDERR.puts "Failed to allocate context or socket!"
raise
end
assert(s.setsockopt(ZMQ::LINGER, 100))
assert(s.setsockopt(ZMQ::RCVHWM, 100))
assert(s.setsockopt(ZMQ::SNDHWM, 100))
assert(s.bind(bind_to))
roundtrip_count.times do
string = ''
assert(s.recv_string(string, 0))
raise "Message size doesn't match, expected [#{message_size}] but received [#{string.size}]" if message_size != string.size
assert(s.send_string(string, 0))
end
assert(s.close)
ctx.terminate
ffi-rzmq-2.0.4/History.txt 0000644 0000041 0000041 00000044671 12463440532 015521 0 ustar www-data www-data == 2.0.4
* Screwed up the release managment. Had wrong version in version.rb. Fixed.
== 2.0.3
* Updated specs to use RSpec 3.x syntax. Never again... if they change it
then I'm dumping rspec.
* Now loading version.rb during startup so that ZMQ::VERSION returns a string.
* Modified README to point most people to the Ruby gem that wraps the CZMQ
library. It's a much higher-level library that makes writing zeromq apps
very easy. This gem is for writing low-level code which most people don't
want or need to do. This gem is going into maintenance mode only.
== 2.0.2
* Updated README to make it clearer which versions of libzmq are supported.
* Updated README to use a newer example.
== 2.0.1 / 20140122
* Fix setsockopt with string parameters for options that have very strict
requirements (like CURVE_SECRETKEY) (37aed3c)
* Provide a wrapper for zmq_curve_keypair for ZeroMQ4
== 2.0.0 / 20140108
* Split out all of the FFI code to its own gem so other projects can use
it without the effort of duplication.
* Support libzmq 4 API and its new security API . Thanks to Devin
Christensen (devin-c) and Jason Roelofs (jasonroelofs) for related
patches.
* Minor documentation fixes. Thanks to Michael Zaccari (mzaccari) for
the fix.
* Improved the readability of any FFI code (now moved to ffi-rzmq-core
project). Thanks to Stephen von Takach (stakach) for the patch.
* Updated AUTHORS.txt
== 1.0.3 / 20131003
* Fixes for issues #96 and #97. Thanks to Paul Chechetin (paulche) for
the issues and patches.
== 1.0.2 / 20130929
* Fix for issue #94. Now includes local_path when searching for a libzmq
to load.
* Fix for issue #92. Context and Socket finalizers now track the process'
PID to handle a special case when forking a process. Thanks to
thinkerbot for the issue and a fix. (Spec is disabled on JRuby since
it does not support fork().)
* Fix a few RSpec deprecation warnings related to expectations on specific
Exception types.
== 1.0.1 / 20130318
* Fix for issue #77 was not included in 1.0.0 release by mistake.
* Add MIR 2.0.0 to travis runs.
* Add support for LAST_ENDPOINT and MULTICAST_HOPS to Socket#getsockopt.
== 1.0.0 / 20130109
* Fix for issue #74 (send_multiple improperly handled single part messages).
* Fix for issue #77 (unbind and disconnect).
* Fix for issue #75 (socket monitor events).
* The API is stable. Releasing 1.0.
== 0.9.7 / 20121221
* BROKE THE API.
ZMQ::Poller#register and ZMQ::Poller#deregister don't take fd argument
anymore. ZMQ::Poller#readables and ZMQ::Poller#writables return pollables
instead of just fd when pollable is other than ZMQ socket.
ZMQ::Poller#register now returns nil instead of false when no pollable
or events to register to are given, which is consistent with rest of api.
Thanks to Pawel Pacana for this code contribution.
* Added support in ZMQ::Poller for pollables responding to fileno and socket.
Standard Ruby Sockets and IOs can be now registered in poller to listen for
events. Thanks to Pawel Pacana for this code contribution.
* Fixed a bug in ZMQ::Poller#deregister where it would raise exception
when trying to deregister already closed ZMQ socket. Issue 59.
* Improved specs to use random inproc socket address to avoid race conditions
between tests under same context.
* Added continous integration for all supported platforms on Travis-CI.
Thanks to Pawel Pacana for this code contribution.
* Signed up for codeclimate.com and made some code changes to get a better
"grade" from it.
* Modified the library to *always* load the 'ffi' library upon startup. It
used to be conditional for Rubinius. Thanks to brixen for the change.
* There was a little bit of churn on the zmq "monitor" api. Thanks to
Nilson Santos F. Jr. for some code to conditionally attach to the
appropriate api depending on library version.
== 0.9.6 / 20120808
* Never released 0.9.5 as a gem. It was available via github only.
* Improved error message when DLL loading fails on Windows.
* Added support for 0mq 2.2. Support for 2.1 might be getting shakey...
patches to make it fully support 2.1 (assuming it's even broken at
all) are welcome.
* Added support for 0mq 3.2 (no support for 3.0 or 3.1). Not all methods
are exposed yet. For example, setting values on the context after it
has been created is not supported; instead, pass the correct keys
(:io_threads and :max_sockets) to the call to Context.create or
Context#new.
* Reduced spec running time from 30+ seconds to under 1 by eliminating
most uses of "sleep." It now polls sockets to wait for message
delivery. It also uses a technique of binding an inproc transport and
busy-looping on the connect side until it succeeds. These techniques
both allowed me to eliminate most uses of sleep.
* Some changes to support usage on Win7x64 with a 64-bit libzmq DLL.
== 0.9.5 / 20120119
* BROKE THE API.
In 0mq 2.x, there were two functions zmq_send() and zmq_recv().
As of 3.x, those functions were renamed zmq_sendmsg() and
zmq_recvmsg(). As everyone starts moving to 0mq 3.x, it doesn't
make sense to make the code break with 2.x. So, I'm breaking this
binding so that it always uses sendmsg/recvmsg and eliminates the
original send/recv methods.
Sorry!
This is likely to be the last non-backward-compatible API breakage.
Release 1.0 is around the corner and the API will be stable (I follow
semantic versioning).
* Introduced ZMQ::NonBlocking. This flag returns the correct value to set
a socket in non-blocking mode when sending/receiving. This hides the
differences between 0mq 2.x and 3.x since the constant names have
changed.
== 0.9.4 / 20120102
* Fixed bug in Poller#delete. Added specs to catch a regression.
In short, a socket that was deleted from the Poller set wasn't
always actually *removed* from the array. This led to a closed
socket being part of the pollset which would return errno 38.
This took about 4 days to find.
== 0.9.3 / 20111214
* Performance optimizations for #getsockopt.
* Fixed Message#copy and Message#move. They didn't work before.
* Cache LibZM::Msg.size in the ZMQ::Message class so that
initialization can skip recalculating what is effectively a
constant value. This speeds up ZMQ::Message instantiation by
5 to 10%. Wow.
* Modified calls to #super to use explicit arguments (e.g. #super())
because otherwise the Ruby runtime has to (at runtime) dig out
the arguments that are expected to be passed up the chain. By
explicitly listing the args and using parentheses, the runtime
can avoid that work and dispatch directly. This effects all
Ruby runtimes, but it was through the work of Evan Phoenix that
I figured this out. Results in a 2-5% speedup on method dispatch.
== 0.9.2 / 20111115
* Removed all references to the version4 API.
* Dropped support for 3.0.x and added support for 3.1.x. The 0mq
community has pretty much voted to abandon the path taken in 3.0
so the 3.1 branch is the API that will be supported.
* Fixed a bug in Poller#delete where it would erroneously return
false even when it successfully deleted a socket. Issue 46.
* All specs pass for 2.1.x API.
* 3 specs fail when run with 3.1 API; these are due to bugs in the
0mq library and are *not* ffi-rzmq bugs.
* Rescue LoadErrors when loading libzmq. Print a warning about
adding libzmq.dll to the Windows PATH for that platform. Print
the search paths where the gem looks for libzmq.
== 0.9.1 / 20111027
* Moved LibC and LibZMQ into the ZMQ module namespace. Necessary to
avoid namespace collisions with other libraries that also use
the constants LibC and/or LibZMQ.
* Fixed a bug where file descriptors registered on Poll were never
returned as readable or writable.
* Added Socket#recv_multipart. This returns the message body and
return address envelope as separate arrays. Only to be used with
XREQ/XREP/DEALER/ROUTER sockets.
== 0.9.0 / 20110930
* Changed the behavior of every method that used to produce exceptions.
The methods now behave more like the C API functions. They return
result codes instead of raising exceptions. Further, the "receive"
methods on Socket now all take an empty string as a buffer to read
the message into.
This is a BREAKING CHANGE and is NOT backward compatible with earlier
releases. I apologize for the inconvenience, but this API will be
much easier to test/spec and maintain. It will also allow for the
production of more logical code.
* Major refactoring of Socket internals so that a single gem can
support libzmq 2.x, 3.x and 4.x APIs without any user intervention.
The correct libzmq version is detected at runtime and used to
configure the Ruby classes to conform to the proper API.
* Added Socket#recvmsgs as a convenience method for receiving a
multipart message into an array of Messages.
* Added support for new 0mq API introduced in the 3.0 branch.
API mostly changed for sending and receiving messages with new
POSIX-compliant send() and recv() functions. The original
functions were renamed sendmsg() and recvmsg().
Additionally, most getsockopt() and setsockopt() calls now use
an int (4 bytes) instead of a mish-mash of 32-bit and 64-bit
values.
For a full list of differences, visit the 0mq wiki page at:
http://www.zeromq.org/docs:3-0-upgrade
* Created a new ext/ directory so that users can copy the libzmq*
library files directly into the gem for easier distribution. This
path is checked *before* the usual system paths.
* Rewrote all examples to use the revised API.
== 0.8.2 / 20110728
* Fixed major bug with Socket#setsockopt when writing 8-byte longs.
* Clarified a bit of logic for non-blocking sends.
* Improved readability of exceptions.
== 0.8.1 / 20110504
* Fixed bug where Socket#setsockopt was using a size from the current
runtime to determine how many bytes to use for HWM, et al. This was
incorrect. All of those socket options require 8 bytes. Discovered
this while getting the code running under mingw on Windows using a
32-bit Ruby runtime.
== 0.8.0 / 20110307
* API change!
Socket#send_message no longer automatically calls
Message#close on behalf of the user. The user is completely
responsible for the lifecycle management of all buffers associated
with the ZMQ::Message objects.
This is a breaking change.
If you want the old behavior (auto-close messages on send) then
use the new Socket#send_and_close method which does as its name
implies.
* Fixed bug with type :size_t on Windows (thank you to arvicco)
== 0.7.3 / 20110304
* Fixed a bug where we had a small memory leak. When closing a socket
I forgot to release a small amount of native memory used as a cache
for doing #getsockopt calls.
* Util.minimum_api? didn't work. Fixed.
* Added ROUTER/DEALER constants to reflect new naming for XREQ/XREP.
XREQ and XREP remain aliased for backward compatibility.
== 0.7.2 / 20110224
* Several minor refactorings to make the code intent clearer and to allow
for better testing. In particular, the error condition checking for
a non-blocking send/recv is much clearer.
== 0.7.1 / 20110130
* Fixed 1.9.1 Binary Encoding bug when UTF8 set as default (Thanks schmurfy)
* Improved rubinius compat for specs
* Improved spec compatibility on linux
== 0.7.0 / 20101222
* Improved performance of calls to Socket#getsockopt. There are usually
a lot of calls passing RCVMORE, so we now cache those buffers instead
of reallocating them every time.
* Updated the docs on Poller#poll to warn about a possible busy-loop
condition.
* Fixed some more specs to conform with the 0mq 2.1 requirement that
all sockets must be closed explicitly otherwise the program may
hang on exit.
== 0.6.1 / 20101127
* API Change!
Moved the #version method from the Util module and made it a class
method instead. Invoke as ZMQ::Util.version. Used for conditionally
enabling certain features based upon the 0mq version that is loaded.
* Preliminary support for the Windows platform. Patches supplied
by arvicco.
* Added support for FD and EVENTS socket options. These were added
in 0mq 2.1.0. Patches + specs supplied by andrewvc.
* Added support for LINGER, RECONNECT_IVL, BACKLOG and
RECOVERY_IVL_MSEC socket options.
* Conditionally re-enable the socket finalizer when we are running
with 0mq 2.1.0 or later.
* Drop support for MRI 1.8.x since the 'ffi' gem has dropped it as a
supported Ruby runtime with its 1.0 release. No action is taken to
prevent running with MRI 1.8.x but it won't be supported.
* Misc. spec fixes. Need more specs!
== 0.6.0 / 20100911
* API Change! Modified ZMQ::Message by removing automatic memory
management. While doing some performance tests I saw that
defining/undefining the finalizer added 15-30% processing
overhead on the latency test. So, I split this functionality
out to a subclass called ZMQ::ManagedMemory. Any existing code
that relies on the default Message class to clean up after itself
will now have a memory leak. Explicitly call #close on these
received messages *unless* they are sent out again. The #send
method automatically closes call on your behalf.
* Rubinius/rbx compatibility! Requires an rbx code pull from git
from 20100911 or later to get the necessary code fixes.
* Modify Message to use the @pointer directly rather than indirectly
via the @struct object. Provides better compatibility for rbx
since rbx does not yet support the FFI pointer protocol for structs
like the FFI gem.
* Modify Message to pass libC's free function for disposing of message
data buffers rather than trying to callback into ruby code to
do the same thing. External thread callbacks into ruby code will
never be supported in rbx; this also improves compatibility and
performance with MRI and JRuby. (In particular, MRI enqueues these
kinds of callbacks and spawns a *new* thread to execute each one.
Avoiding the ruby callback entirely eliminates this extra work
for MRI.)
* Modify FFI wrapper to capture the libC dynamic library to fetch
a pointer to the free function.
* Modify FFI wrapper to remove the FFI::Function callback used
by Message. It's no longer necessary since we now use free
directly.
== 0.5.1 / 20100830
* Works with 0mq 2.0.8 release.
* Removed the socket finalizer. The current 0mq framework cannot
handle the case where zmq_close is called on a socket that was
created from another thread. Therefore, the garbage collection
thread causes the framework to break. Version 2.1 (or later)
should fix this 0mq limitation.
* Misc fixes. See commits.
== 0.5.0 / 20100606
* Updated the bindings to conform to the 0mq 2.0.7 release.
Several parts of the API changed.
* Updated all examples to use the new Context api.
* Added Socket#getsockopt.
* Added a Socket#identity and Socket#identity= method pair to
allow for easy get/put on socket identities. Useful for async
request/reply using XREQ/XREP sockets.
* Added more specs (slowly but surely).
* Support multi-part messages (new as of 2.0.7). I am unsure how
to best support multi-part messages so the Message (and related)
API may change in the future. Added Socket#more_parts?.
* Lots of fixes. Many classes use finalizers to deallocate native
memory when they go out of scope; be sure to use JRuby 1.5.1 or
later to get important finalizer fixes.
== 0.4.1 / 20100511
* I was misusing all of the FFI memory allocator classes. I now
wrap libc and use malloc/free directly for creating buffers
used by libzmq.
== 0.4.0 / 20100510
* Changed the Socket#recv method signature to take an optional
message object as its first argument. This allows the library
user to allocate and pass in their own message object for the
purposes of zero-copy. Original behavior was for the library to
*always* allocate a new message object to receive a message into.
Hopefully this is the last change required.
* Modified the Socket constructor to take an optional hash as its
final argument. It honors two keys; :receiver_klass and
:sender_klass. Passing in a new constant for either (or both) keys
will override the class used by Socket for allocating new
Message objects.
== 0.3.1 / 20100509
* Modified ZMQ::Message so we have both an UnmanagedMessage where
memory management is manual via the #close method, and Message where
memory management is automated via a finalizer method run during
garbage collection.
* Updated ZMQ::Message docs to make it clearer how to use a subclass
and FFI::Struct to lazily access the message buffer. This gets us as
close to zero-copy as possible for performance.
* Fixed a memory leak in Message where the FFI::Struct backing the
C struct was not being freed.
* Tested the FFI code against MRI 1.8.x and 1.9.x. It works!
* Patched a potential problem in LibZMQ::MessageDeallocator. It was
crashing under MRI because it complained that FFI::Pointer did not
have a free method. It now checks for :free before calling it.
Need to investigate this further because it never happened under
JRuby.
* Modified the Socket constructor slightly to allow for using
unmanaged or managed messages.
* Changed the /examples to print a throughput (msgs/s) number upon
completion.
== 0.3.0 / 20100507
* ZMQ::Socket#send and ZMQ::Socket#recv semantics changed
* The official 0mq ruby bindings utilize strings for #send and #recv.
However, to do so requires lots of copying to and from buffers which
greatly impacts performance. These methods now return a ZMQ::Message
object which can be subclassed to do lazy evaluation of the buffer.
* Added ZMQ::Socket#send_string and ZMQ::Socket#recv_string. They
automatically convert the messages to strings just like the official
0mq ruby bindings.
* Fixed bug in ZMQ::Util#error_string
* Split the ZMQ::Message class into two classes. The base class called
UnmanagedMessage requires manual memory management. The Message
class (used by default by Socket) has a finalizer defined to
automatically release memory when the message object gets garbage
collected.
== 0.2.0 / 20100505
* 1 major enhancement
* Birthday!
ffi-rzmq-2.0.4/spec/ 0000755 0000041 0000041 00000000000 12463440532 014235 5 ustar www-data www-data ffi-rzmq-2.0.4/spec/reqrep_spec.rb 0000644 0000041 0000041 00000004562 12463440532 017101 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), %w[spec_helper])
module ZMQ
describe Socket do
context "when running ping pong" do
include APIHelper
let(:string) { "booga-booga" }
# reset sockets each time because we only send 1 message which leaves
# the REQ socket in a bad state. It cannot send again unless we were to
# send a reply with the REP and read it.
before(:each) do
@context = ZMQ::Context.new
poller_setup
endpoint = "inproc://reqrep_test"
@ping = @context.socket ZMQ::REQ
@pong = @context.socket ZMQ::REP
@pong.bind(endpoint)
connect_to_inproc(@ping, endpoint)
end
after(:each) do
@ping.close
@pong.close
@context.terminate
end
def send_ping(string)
@ping.send_string string
received_message = ''
rc = @pong.recv_string received_message
[rc, received_message]
end
it "should receive an exact string copy of the string message sent" do
rc, received_message = send_ping(string)
expect(received_message).to eq(string)
end
it "should generate a EFSM error when sending via the REQ socket twice in a row without an intervening receive operation" do
send_ping(string)
rc = @ping.send_string(string)
expect(rc).to eq(-1)
expect(Util.errno).to eq(ZMQ::EFSM)
end
it "should receive an exact copy of the sent message using Message objects directly" do
received_message = Message.new
rc = @ping.sendmsg(Message.new(string))
expect(rc).to eq(string.size)
rc = @pong.recvmsg received_message
expect(rc).to eq(string.size)
expect(received_message.copy_out_string).to eq(string)
end
it "should receive an exact copy of the sent message using Message objects directly in non-blocking mode" do
sent_message = Message.new string
received_message = Message.new
poll_it_for_read(@pong) do
rc = @ping.sendmsg(Message.new(string), ZMQ::DONTWAIT)
expect(rc).to eq(string.size)
end
rc = @pong.recvmsg received_message, ZMQ::DONTWAIT
expect(rc).to eq(string.size)
expect(received_message.copy_out_string).to eq(string)
end
end # context ping-pong
end # describe
end # module ZMQ
ffi-rzmq-2.0.4/spec/poll_spec.rb 0000644 0000041 0000041 00000020660 12463440532 016546 0 ustar www-data www-data require 'spec_helper'
module ZMQ
describe Poller do
context "when initializing" do
include APIHelper
it "should allocate a PollItems instance" do
expect(PollItems).to receive(:new)
Poller.new
end
end
context "#register" do
let(:pollable) { double('pollable') }
let(:poller) { Poller.new }
let(:socket) { FFI::MemoryPointer.new(4) }
let(:io) { double(:posix_fileno => fd) }
let(:fd) { 1 }
it "returns false when given a nil pollable" do
expect(poller.register(nil, ZMQ::POLLIN)).to be_falsy
end
it "returns false when given 0 for +events+ (e.g. no registration)" do
expect(poller.register(pollable, 0)).to be_falsy
end
it "returns the default registered event value when given a valid pollable" do
expect(poller.register(pollable)).to eq(ZMQ::POLLIN | ZMQ::POLLOUT)
end
it "returns the registered event value when given a pollable responding to socket (ZMQ::Socket)" do
expect(pollable).to receive(:socket).and_return(socket)
expect(poller.register(pollable, ZMQ::POLLIN)).to eq ZMQ::POLLIN
end
it "returns the registered event value when given a pollable responding to file descriptor (IO, BasicSocket)" do
expect(pollable).to receive(:posix_fileno).and_return(fd)
expect(poller.register(pollable, ZMQ::POLLIN)).to eq(ZMQ::POLLIN)
end
it "returns the registered event value when given a pollable responding to io (SSLSocket)" do
expect(pollable).to receive(:io).and_return(io)
expect(poller.register(pollable, ZMQ::POLLIN)).to eq(ZMQ::POLLIN)
end
end
context "#deregister" do
let(:pollable) { double('pollable') }
let(:poller) { Poller.new }
let(:socket) { FFI::MemoryPointer.new(4) }
let(:io) { double(:posix_fileno => fd) }
let(:fd) { 1 }
it "returns true when deregistered pollable from event" do
expect(pollable).to receive(:socket).at_least(:once).and_return(socket)
poller.register(pollable)
expect(poller.deregister(pollable, ZMQ::POLLIN)).to eq(true)
end
it "returns false when pollable not registered" do
expect(poller.deregister(pollable, ZMQ::POLLIN)).to eq(false)
end
it "returns false when pollable not registered for deregistered event" do
expect(pollable).to receive(:socket).at_least(:once).and_return(socket)
poller.register(pollable, ZMQ::POLLOUT)
expect(poller.deregister(pollable, ZMQ::POLLIN)).to eq(false)
end
it "deletes pollable when no events left" do
poller.register(pollable, ZMQ::POLLIN)
expect(poller.deregister(pollable, ZMQ::POLLIN)).to eq(true)
expect(poller.size).to eq 0
end
it "deletes closed pollable responding to socket (ZMQ::Socket)" do
expect(pollable).to receive(:socket).and_return(socket)
poller.register(pollable)
expect(pollable).to receive(:socket).and_return(nil)
expect(poller.deregister(pollable, ZMQ::POLLIN)).to eq(true)
expect(poller.size).to eq 0
end
it "deletes closed pollable responding to fileno (IO, BasicSocket)" do
expect(pollable).to receive(:posix_fileno).and_return(fd)
poller.register(pollable)
expect(pollable).to receive(:closed?).and_return(true)
expect(poller.deregister(pollable, ZMQ::POLLIN)).to eq(true)
expect(poller.size).to eq 0
end
it "deletes closed pollable responding to io (SSLSocket)" do
expect(pollable).to receive(:io).at_least(:once).and_return(io)
poller.register(pollable)
expect(io).to receive(:closed?).and_return(true)
expect(poller.deregister(pollable, ZMQ::POLLIN)).to eq(true)
expect(poller.size).to eq 0
end
end
context "#delete" do
before(:all) { @context = Context.new }
after(:all) { @context.terminate }
before(:each) do
@socket = @context.socket(XREQ)
@socket.setsockopt(LINGER, 0)
@poller = Poller.new
end
after(:each) do
@socket.close
end
it "should return false for an unregistered socket (i.e. not found)" do
expect(@poller.delete(@socket)).to eq(false)
end
it "returns true for a sucessfully deleted socket when only 1 is registered" do
socket1 = @context.socket(REP)
socket1.setsockopt(LINGER, 0)
@poller.register socket1
expect(@poller.delete(socket1)).to eq(true)
socket1.close
end
it "returns true for a sucessfully deleted socket when more than 1 is registered" do
socket1 = @context.socket(REP)
socket2 = @context.socket(REP)
socket1.setsockopt(LINGER, 0)
socket2.setsockopt(LINGER, 0)
@poller.register socket1
@poller.register socket2
expect(@poller.delete(socket2)).to eq(true)
socket1.close
socket2.close
end
it "returns true for a successfully deleted socket when the socket has been previously closed" do
socket1 = @context.socket(REP)
socket1.setsockopt(LINGER, 0)
@poller.register socket1
socket1.close
expect(@poller.delete(socket1)).to eq(true)
end
end
context "poll" do
include APIHelper
before(:all) { @context = Context.new }
after(:all) { @context.terminate }
before(:each) do
endpoint = "inproc://poll_test_#{SecureRandom.hex}"
@sockets = [@context.socket(DEALER), @context.socket(ROUTER)]
@sockets.each { |s| s.setsockopt(LINGER, 0) }
@sockets.first.bind(endpoint)
connect_to_inproc(@sockets.last, endpoint)
@poller = Poller.new
end
after(:each) { @sockets.each(&:close) }
it "returns 0 when there are no sockets to poll" do
expect(@poller.poll(100)).to eq 0
end
it "returns 0 when there is a single socket to poll and no events" do
@poller.register(@sockets.first, 0)
expect(@poller.poll(100)).to eq 0
end
it "returns 1 when there is a read event on a socket" do
first, last = @sockets
@poller.register_readable(last)
first.send_string('test')
expect(@poller.poll(1000)).to eq 1
end
it "returns 1 when there is a read event on one socket and the second socket has been removed from polling" do
first, last = @sockets
@poller.register_readable(last)
@poller.register_writable(first)
first.send_string('test')
@poller.deregister_writable(first)
expect(@poller.poll(1000)).to eq 1
end
it "works with BasiSocket" do
server = TCPServer.new("127.0.0.1", 0)
f, port, host, addr = server.addr
client = TCPSocket.new("127.0.0.1", port)
s = server.accept
@poller.register(s, ZMQ::POLLIN)
@poller.register(client, ZMQ::POLLOUT)
client.send("message", 0)
expect(@poller.poll).to eq 2
expect(@poller.readables).to eq [s]
expect(@poller.writables).to eq [client]
msg = s.read_nonblock(7)
expect(msg).to eq "message"
end
it "works with IO objects" do
r, w = IO.pipe
@poller.register(r, ZMQ::POLLIN)
@poller.register(w, ZMQ::POLLOUT)
w.write("message")
expect(@poller.poll).to eq 2
expect(@poller.readables).to eq [r]
expect(@poller.writables).to eq [w]
msg = r.read(7)
expect(msg).to eq "message"
end
it "works with SSLSocket" do
crt, key = %w[crt key].map { |ext| File.read(File.join(File.dirname(__FILE__), "support", "test." << ext)) }
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = OpenSSL::PKey::RSA.new(key)
ctx.cert = OpenSSL::X509::Certificate.new(crt)
server = TCPServer.new("127.0.0.1", 0)
f, port, host, addr = server.addr
client = TCPSocket.new("127.0.0.1", port)
s = server.accept
client = OpenSSL::SSL::SSLSocket.new(client)
server = OpenSSL::SSL::SSLSocket.new(s, ctx)
t = Thread.new { client.connect }
s = server.accept
t.join
@poller.register_readable(s)
@poller.register_writable(client)
client.write("message")
expect(@poller.poll).to eq 2
expect(@poller.readables).to eq [s]
expect(@poller.writables).to eq [client]
msg = s.read(7)
expect(msg).to eq "message"
end
end
end
end
ffi-rzmq-2.0.4/spec/pushpull_spec.rb 0000644 0000041 0000041 00000006550 12463440532 017456 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), %w[spec_helper])
module ZMQ
describe Socket do
context "when running basic push pull" do
include APIHelper
let(:string) { "booga-booga" }
before(:each) do
# Use new context for each iteration to avoid inproc race. See
# poll_spec.rb for more details.
@context = Context.new
poller_setup
@push = @context.socket ZMQ::PUSH
@pull = @context.socket ZMQ::PULL
@push.setsockopt ZMQ::LINGER, 0
@pull.setsockopt ZMQ::LINGER, 0
@link = "inproc://push_pull_test"
@push.bind @link
connect_to_inproc(@pull, @link)
end
after(:each) do
@push.close
@pull.close
@context.terminate
end
it "should receive an exact copy of the sent message using Message objects directly on one pull socket" do
@push.send_string string
received = ''
rc = @pull.recv_string received
assert_ok(rc)
expect(received).to eq(string)
end
it "should receive an exact string copy of the message sent when receiving in non-blocking mode and using Message objects directly" do
sent_message = Message.new string
received_message = Message.new
poll_it_for_read(@pull) do
rc = @push.sendmsg sent_message
expect(rc).to eq(string.size)
end
rc = @pull.recvmsg received_message, ZMQ::DONTWAIT
expect(rc).to eq(string.size)
expect(received_message.copy_out_string).to eq(string)
end
it "should receive a single message for each message sent on each socket listening, when an equal number of sockets pulls messages and where each socket is unique per thread" do
received = []
threads = []
sockets = []
count = 4
mutex = Mutex.new
# make sure all sockets are connected before we do our load-balancing test
(count - 1).times do
socket = @context.socket ZMQ::PULL
socket.setsockopt ZMQ::LINGER, 0
connect_to_inproc(socket, @link)
sockets << socket
end
sockets << @pull
sockets.each do |socket|
thr = Thread.new do
buffer = ''
rc = socket.recv_string buffer
expect(rc).to eq(buffer.size)
mutex.synchronize { received << buffer }
socket.close
end
threads << thr
end
count.times { @push.send_string(string) }
threads.each {|t| t.join}
expect(received.find_all {|r| r == string}.length).to eq(count)
end
it "should receive a single message for each message sent when using a single shared socket protected by a mutex" do
received = []
threads = []
count = 4
mutex = Mutex.new
count.times do |i|
threads << Thread.new do
buffer = ''
rc = 0
mutex.synchronize { rc = @pull.recv_string buffer }
expect(rc).to eq(buffer.size)
mutex.synchronize { received << buffer }
end
end
count.times { @push.send_string(string) }
threads.each {|t| t.join}
expect(received.find_all {|r| r == string}.length).to eq(count)
end
end # @context ping-pong
end # describe
end # module ZMQ
ffi-rzmq-2.0.4/spec/spec_helper.rb 0000644 0000041 0000041 00000004136 12463440532 017057 0 ustar www-data www-data
require File.expand_path(
File.join(File.dirname(__FILE__), %w[.. lib ffi-rzmq]))
Thread.abort_on_exception = true
require 'openssl'
require 'socket'
require 'securerandom'
# define some version guards so we can turn on/off specs based upon
# the version of the 0mq library that is loaded
def version4?
ZMQ::LibZMQ.version4?
end
def jruby?
RUBY_PLATFORM =~ /java/
end
def connect_to_inproc(socket, endpoint)
begin
rc = socket.connect(endpoint)
end until ZMQ::Util.resultcode_ok?(rc)
end
module APIHelper
def poller_setup
@helper_poller = ZMQ::Poller.new
end
def poller_register_socket(socket)
@helper_poller.register(socket, ZMQ::POLLIN)
end
def poller_deregister_socket(socket)
@helper_poller.deregister(socket, ZMQ::POLLIN)
end
def poll_delivery
# timeout after 1 second
@helper_poller.poll(1000)
end
def poll_it_for_read(socket, &blk)
poller_register_socket(socket)
blk.call
poll_delivery
poller_deregister_socket(socket)
end
# generate a random port between 10_000 and 65534
def random_port
rand(55534) + 10_000
end
def bind_to_random_tcp_port(socket, max_tries = 500)
tries = 0
rc = -1
while !ZMQ::Util.resultcode_ok?(rc) && tries < max_tries
tries += 1
random = random_port
rc = socket.bind(local_transport_string(random))
end
unless ZMQ::Util.resultcode_ok?(rc)
raise "Could not bind to random port successfully; retries all failed!"
end
random
end
def connect_to_random_tcp_port socket, max_tries = 500
tries = 0
rc = -1
while !ZMQ::Util.resultcode_ok?(rc) && tries < max_tries
tries += 1
random = random_port
rc = socket.connect(local_transport_string(random))
end
unless ZMQ::Util.resultcode_ok?(rc)
raise "Could not connect to random port successfully; retries all failed!"
end
random
end
def local_transport_string(port)
"tcp://127.0.0.1:#{port}"
end
def assert_ok(rc)
raise "Failed with rc [#{rc}] and errno [#{ZMQ::Util.errno}], msg [#{ZMQ::Util.error_string}]! #{caller(0)}" unless rc >= 0
end
end
ffi-rzmq-2.0.4/spec/context_spec.rb 0000644 0000041 0000041 00000006013 12463440532 017260 0 ustar www-data www-data $: << "." # added for ruby 1.9.2 compatibilty; it doesn't include the current directory on the load path anymore
require File.join(File.dirname(__FILE__), %w[spec_helper])
module ZMQ
describe Context do
context "when initializing with factory method #create" do
include APIHelper
it "should return nil for negative io threads" do
expect(Context.create(-1)).to eq(nil)
end
it "should default to requesting 1 i/o thread when no argument is passed" do
ctx = Context.create
expect(ctx.io_threads).to eq(1)
end
it "should set the :pointer accessor to non-nil" do
ctx = Context.create
expect(ctx.pointer).not_to be_nil
end
it "should set the :context accessor to non-nil" do
ctx = Context.create
expect(ctx.context).not_to be_nil
end
it "should set the :pointer and :context accessors to the same value" do
ctx = Context.create
expect(ctx.pointer).to eq(ctx.context)
end
it "should define a finalizer on this object" do
expect(ObjectSpace).to receive(:define_finalizer)
ctx = Context.create
end
end # context initializing
context "when initializing with #new" do
include APIHelper
it "should raise a ContextError exception for negative io threads" do
expect { Context.new(-1) }.to raise_exception(ZMQ::ContextError)
end
it "should default to requesting 1 i/o thread when no argument is passed" do
ctx = Context.new
expect(ctx.io_threads).to eq(1)
end
it "should set the :pointer accessor to non-nil" do
ctx = Context.new
expect(ctx.pointer).not_to be_nil
end
it "should set the :context accessor to non-nil" do
ctx = Context.new
expect(ctx.context).not_to be_nil
end
it "should set the :pointer and :context accessors to the same value" do
ctx = Context.new
expect(ctx.pointer).to eq(ctx.context)
end
it "should define a finalizer on this object" do
expect(ObjectSpace).to receive(:define_finalizer)
Context.new 1
end
end # context initializing
context "when terminating" do
it "should set the context to nil when terminating the library's context" do
ctx = Context.new # can't use a shared context here because we are terminating it!
ctx.terminate
expect(ctx.pointer).to be_nil
end
it "should call the correct library function to terminate the context" do
ctx = Context.new
expect(LibZMQ).to receive(:zmq_ctx_destroy).with(ctx.pointer).and_return(0)
ctx.terminate
end
end # context terminate
context "when allocating a socket" do
it "should return nil when allocation fails" do
ctx = Context.new
allow(LibZMQ).to receive(:zmq_socket).and_return(nil)
expect(ctx.socket(ZMQ::REQ)).to be_nil
end
end # context socket
end # describe Context
end # module ZMQ
ffi-rzmq-2.0.4/spec/device_spec.rb 0000644 0000041 0000041 00000003462 12463440532 017040 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), %w[spec_helper])
module ZMQ
describe Device do
include APIHelper
before(:each) do
@ctx = Context.new
poller_setup
@front_endpoint = "inproc://device_front_test"
@back_endpoint = "inproc://device_back_test"
@mutex = Mutex.new
end
after(:each) do
@ctx.terminate
end
def create_streamer
@device_thread = false
Thread.new do
back = @ctx.socket(ZMQ::PULL)
back.bind(@back_endpoint)
front = @ctx.socket(ZMQ::PUSH)
front.bind(@front_endpoint)
@mutex.synchronize { @device_thread = true }
puts "create streamer device and running..."
Device.new(back, front)
puts "device exited"
back.close
front.close
end
end
def wait_for_device
loop do
can_break = @mutex.synchronize { @device_thread }
break if can_break
end
puts "broke out of wait_for_device loop"
end
it "should create a device without error given valid opts" do
create_streamer
wait_for_device
end
it "should be able to send messages through the device" do
create_streamer
wait_for_device
pusher = @ctx.socket(ZMQ::PUSH)
connect_to_inproc(pusher, @back_endpoint)
puller = @ctx.socket(ZMQ::PULL)
connect_to_inproc(puller, @front_endpoint)
poll_it_for_read(puller) do
pusher.send_string("hello")
end
res = ''
rc = puller.recv_string(res, ZMQ::DONTWAIT)
expect(res).to eq("hello")
pusher.close
puller.close
end
it "should raise an ArgumentError when trying to pass non-socket objects into the device" do
expect {
Device.new(1,2)
}.to raise_exception(ArgumentError)
end
end
end
ffi-rzmq-2.0.4/spec/multipart_spec.rb 0000644 0000041 0000041 00000010002 12463440532 017606 0 ustar www-data www-data require File.join(File.dirname(__FILE__), %w[spec_helper])
module ZMQ
describe Socket do
context "multipart messages" do
before(:all) { @ctx = Context.new }
after(:all) { @ctx.terminate }
context "using #send_strings" do
include APIHelper
before(:all) do
@receiver = Socket.new(@ctx.pointer, ZMQ::REP)
port = bind_to_random_tcp_port(@receiver)
@sender = Socket.new(@ctx.pointer, ZMQ::REQ)
rc = @sender.connect("tcp://127.0.0.1:#{port}")
end
after(:all) do
@sender.close
@receiver.close
end
it "correctly handles a multipart message array with 1 element" do
data = [ "1" ]
@sender.send_strings(data)
sleep 1
strings = []
rc = @receiver.recv_strings(strings)
expect(strings).to eq(data)
end
end
context "without identity" do
include APIHelper
before(:all) do
@rep = Socket.new(@ctx.pointer, ZMQ::REP)
port = bind_to_random_tcp_port(@rep)
@req = Socket.new(@ctx.pointer, ZMQ::REQ)
@req.connect("tcp://127.0.0.1:#{port}")
end
after(:all) do
@req.close
@rep.close
end
it "should be delivered between REQ and REP returning an array of strings" do
req_data, rep_data = [ "1", "2" ], [ "2", "3" ]
@req.send_strings(req_data)
strings = []
rc = @rep.recv_strings(strings)
expect(strings).to eq(req_data)
@rep.send_strings(rep_data)
strings = []
rc = @req.recv_strings(strings)
expect(strings).to eq(rep_data)
end
it "should be delivered between REQ and REP returning an array of messages" do
req_data, rep_data = [ "1", "2" ], [ "2", "3" ]
@req.send_strings(req_data)
messages = []
rc = @rep.recvmsgs(messages)
messages.each_with_index do |message, index|
expect(message.copy_out_string).to eq(req_data[index])
end
@rep.send_strings(rep_data)
messages = []
rc = @req.recvmsgs(messages)
messages.each_with_index do |message, index|
expect(message.copy_out_string).to eq(rep_data[index])
end
end
end
context "with identity" do
include APIHelper
before(:each) do # was :all
@rep = Socket.new(@ctx.pointer, ZMQ::XREP)
port = bind_to_random_tcp_port(@rep)
@req = Socket.new(@ctx.pointer, ZMQ::REQ)
@req.identity = 'foo'
@req.connect("tcp://127.0.0.1:#{port}")
end
after(:each) do # was :all
@req.close
@rep.close
end
it "should be delivered between REQ and REP returning an array of strings with an empty string as the envelope delimiter" do
req_data, rep_data = "hello", [ @req.identity, "", "ok" ]
@req.send_string(req_data)
strings = []
rc = @rep.recv_strings(strings)
expect(strings).to eq([ @req.identity, "", "hello" ])
@rep.send_strings(rep_data)
string = ''
rc = @req.recv_string(string)
expect(string).to eq(rep_data.last)
end
it "should be delivered between REQ and REP returning an array of messages with an empty string as the envelope delimiter" do
req_data, rep_data = "hello", [ @req.identity, "", "ok" ]
@req.send_string(req_data)
msgs = []
rc = @rep.recvmsgs(msgs)
expect(msgs[0].copy_out_string).to eq(@req.identity)
expect(msgs[1].copy_out_string).to eq("")
expect(msgs[2].copy_out_string).to eq("hello")
@rep.send_strings(rep_data)
msgs = []
rc = @req.recvmsgs(msgs)
expect(msgs[0].copy_out_string).to eq(rep_data.last)
end
end
end
end
end
ffi-rzmq-2.0.4/spec/message_spec.rb 0000644 0000041 0000041 00000005673 12463440532 017233 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), %w[spec_helper])
module ZMQ
describe Message do
context "when initializing with an argument" do
it "calls zmq_msg_init_data()" do
expect(LibZMQ).to receive(:zmq_msg_init_data)
message = Message.new "text"
end
it "should *not* define a finalizer on this object" do
expect(ObjectSpace).not_to receive(:define_finalizer)
Message.new "text"
end
end # context initializing with arg
context "when initializing *without* an argument" do
it "calls zmq_msg_init()" do
expect(LibZMQ).to receive(:zmq_msg_init).and_return(0)
message = Message.new
end
it "should *not* define a finalizer on this object" do
expect(ObjectSpace).not_to receive(:define_finalizer)
Message.new "text"
end
end # context initializing with arg
context "#copy_in_string" do
it "calls zmq_msg_init_data()" do
message = Message.new "text"
expect(LibZMQ).to receive(:zmq_msg_init_data)
message.copy_in_string("new text")
end
it "correctly finds the length of binary data by ignoring encoding" do
message = Message.new
message.copy_in_string("\x83\x6e\x04\x00\x00\x44\xd1\x81")
expect(message.size).to eq(8)
end
end
context "#copy" do
it "calls zmq_msg_copy()" do
message = Message.new "text"
copy = Message.new
expect(LibZMQ).to receive(:zmq_msg_copy)
copy.copy(message)
end
end # context copy
context "#move" do
it "calls zmq_msg_move()" do
message = Message.new "text"
copy = Message.new
expect(LibZMQ).to receive(:zmq_msg_move)
copy.move(message)
end
end # context move
context "#size" do
it "calls zmq_msg_size()" do
message = Message.new "text"
expect(LibZMQ).to receive(:zmq_msg_size)
message.size
end
end # context size
context "#data" do
it "calls zmq_msg_data()" do
message = Message.new "text"
expect(LibZMQ).to receive(:zmq_msg_data)
message.data
end
end # context data
context "#close" do
it "calls zmq_msg_close() the first time" do
message = Message.new "text"
expect(LibZMQ).to receive(:zmq_msg_close)
message.close
end
it "*does not* call zmq_msg_close() on subsequent invocations" do
message = Message.new "text"
message.close
expect(LibZMQ).not_to receive(:zmq_msg_close)
message.close
end
end # context close
end # describe Message
describe ManagedMessage do
context "when initializing with an argument" do
it "should define a finalizer on this object" do
expect(ObjectSpace).to receive(:define_finalizer)
ManagedMessage.new "text"
end
end # context initializing
end # describe ManagedMessage
end # module ZMQ
ffi-rzmq-2.0.4/spec/socket_spec.rb 0000644 0000041 0000041 00000042403 12463440532 017067 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), %w[spec_helper])
module ZMQ
describe Socket do
include APIHelper
socket_types =
[ZMQ::REQ, ZMQ::REP, ZMQ::DEALER, ZMQ::ROUTER, ZMQ::PUB, ZMQ::SUB, ZMQ::PUSH, ZMQ::PULL, ZMQ::PAIR, ZMQ::XPUB, ZMQ::XSUB]
context "when initializing" do
before(:all) { @ctx = Context.new }
after(:all) { @ctx.terminate }
it "should raise an error for a nil context" do
expect { Socket.new(FFI::Pointer.new(0), ZMQ::REQ) }.to raise_exception(ZMQ::ContextError)
end
it "works with a Context#pointer as the context_ptr" do
expect do
s = Socket.new(@ctx.pointer, ZMQ::REQ)
s.close
end.not_to raise_exception
end
it "works with a Context instance as the context_ptr" do
expect do
s = Socket.new(@ctx, ZMQ::SUB)
s.close
end.not_to raise_exception
end
socket_types.each do |socket_type|
it "should not raise an error for a [#{ZMQ::SocketTypeNameMap[socket_type]}] socket type" do
sock = nil
expect { sock = Socket.new(@ctx.pointer, socket_type) }.not_to raise_error
sock.close
end
end # each socket_type
it "should set the :socket accessor to the raw socket allocated by libzmq" do
socket = double('socket')
allow(socket).to receive(:null?).and_return(false)
expect(LibZMQ).to receive(:zmq_socket).and_return(socket)
sock = Socket.new(@ctx.pointer, ZMQ::REQ)
expect(sock.socket).to eq(socket)
end
it "should define a finalizer on this object" do
expect(ObjectSpace).to receive(:define_finalizer).at_least(1)
sock = Socket.new(@ctx.pointer, ZMQ::REQ)
sock.close
end
unless jruby?
it "should track pid in finalizer so subsequent fork will not segfault" do
sock = Socket.new(@ctx.pointer, ZMQ::REQ)
pid = fork { }
Process.wait(pid)
sock.close
end
end
end # context initializing
context "calling close" do
before(:all) { @ctx = Context.new }
after(:all) { @ctx.terminate }
it "should call LibZMQ.close only once" do
sock = Socket.new @ctx.pointer, ZMQ::REQ
raw_socket = sock.socket
expect(LibZMQ).to receive(:close).with(raw_socket)
sock.close
sock.close
LibZMQ.close raw_socket # *really close it otherwise the context will block indefinitely
end
end # context calling close
context "identity=" do
before(:all) { @ctx = Context.new }
after(:all) { @ctx.terminate }
it "fails to set identity for identities in excess of 255 bytes" do
sock = Socket.new @ctx.pointer, ZMQ::REQ
sock.identity = ('a' * 256)
expect(sock.identity).to eq('')
sock.close
end
it "fails to set identity for identities of length 0" do
sock = Socket.new @ctx.pointer, ZMQ::REQ
sock.identity = ''
expect(sock.identity).to eq('')
sock.close
end
it "sets the identity for identities of 1 byte" do
sock = Socket.new @ctx.pointer, ZMQ::REQ
sock.identity = 'a'
expect(sock.identity).to eq('a')
sock.close
end
it "set the identity identities of 255 bytes" do
sock = Socket.new @ctx.pointer, ZMQ::REQ
sock.identity = ('a' * 255)
expect(sock.identity).to eq('a' * 255)
sock.close
end
it "should convert numeric identities to strings" do
sock = Socket.new @ctx.pointer, ZMQ::REQ
sock.identity = 7
expect(sock.identity).to eq('7')
sock.close
end
end # context identity=
socket_types.each do |socket_type|
context "#setsockopt for a #{ZMQ::SocketTypeNameMap[socket_type]} socket" do
before(:all) { @ctx = Context.new }
after(:all) { @ctx.terminate }
let(:socket) do
Socket.new @ctx.pointer, socket_type
end
after(:each) do
socket.close
end
context "using option ZMQ::IDENTITY" do
it "should set the identity given any string under 255 characters" do
length = 4
(1..255).each do |length|
identity = 'a' * length
socket.setsockopt ZMQ::IDENTITY, identity
array = []
rc = socket.getsockopt(ZMQ::IDENTITY, array)
expect(rc).to eq(0)
expect(array[0]).to eq(identity)
end
end
it "returns -1 given a string 256 characters or longer" do
identity = 'a' * 256
array = []
rc = socket.setsockopt(ZMQ::IDENTITY, identity)
expect(rc).to eq(-1)
end
end # context using option ZMQ::IDENTITY
context "using option ZMQ::IPV4ONLY" do
it "should enable use of IPV6 sockets when set to 0" do
value = 0
socket.setsockopt ZMQ::IPV4ONLY, value
array = []
rc = socket.getsockopt(ZMQ::IPV4ONLY, array)
expect(rc).to eq(0)
expect(array[0]).to eq(value)
end
it "should default to a value of 1" do
value = 1
array = []
rc = socket.getsockopt(ZMQ::IPV4ONLY, array)
expect(rc).to eq(0)
expect(array[0]).to eq(value)
end
it "returns -1 given a negative value" do
value = -1
rc = socket.setsockopt ZMQ::IPV4ONLY, value
expect(rc).to eq(-1)
end
it "returns -1 given a value > 1" do
value = 2
rc = socket.setsockopt ZMQ::IPV4ONLY, value
expect(rc).to eq(-1)
end
end # context using option ZMQ::IPV4ONLY
context "using option ZMQ::LAST_ENDPOINT" do
it "should return last enpoint" do
random_port = bind_to_random_tcp_port(socket, max_tries = 500)
array = []
rc = socket.getsockopt(ZMQ::LAST_ENDPOINT, array)
expect(ZMQ::Util.resultcode_ok?(rc)).to eq(true)
endpoint_regex = %r{\Atcp://(.*):(\d+)\0\z}
expect(array[0]).to match(endpoint_regex)
expect(Integer(array[0][endpoint_regex, 2])).to eq(random_port)
end
end
context "using option ZMQ::SUBSCRIBE" do
if ZMQ::SUB == socket_type
it "returns 0 for a SUB socket" do
rc = socket.setsockopt(ZMQ::SUBSCRIBE, "topic.string")
expect(rc).to eq(0)
end
else
it "returns -1 for non-SUB sockets" do
rc = socket.setsockopt(ZMQ::SUBSCRIBE, "topic.string")
expect(rc).to eq(-1)
end
end
end # context using option ZMQ::SUBSCRIBE
context "using option ZMQ::UNSUBSCRIBE" do
if ZMQ::SUB == socket_type
it "returns 0 given a topic string that was previously subscribed" do
socket.setsockopt ZMQ::SUBSCRIBE, "topic.string"
rc = socket.setsockopt(ZMQ::UNSUBSCRIBE, "topic.string")
expect(rc).to eq(0)
end
else
it "returns -1 for non-SUB sockets" do
rc = socket.setsockopt(ZMQ::UNSUBSCRIBE, "topic.string")
expect(rc).to eq(-1)
end
end
end # context using option ZMQ::UNSUBSCRIBE
context "using option ZMQ::AFFINITY" do
it "should set the affinity value given a positive value" do
affinity = 3
socket.setsockopt ZMQ::AFFINITY, affinity
array = []
rc = socket.getsockopt(ZMQ::AFFINITY, array)
expect(rc).to eq(0)
expect(array[0]).to eq(affinity)
end
end # context using option ZMQ::AFFINITY
context "using option ZMQ::RATE" do
it "should set the multicast send rate given a positive value" do
rate = 200
socket.setsockopt ZMQ::RATE, rate
array = []
rc = socket.getsockopt(ZMQ::RATE, array)
expect(rc).to eq(0)
expect(array[0]).to eq(rate)
end
it "returns -1 given a negative value" do
rate = -200
rc = socket.setsockopt ZMQ::RATE, rate
expect(rc).to eq(-1)
end
end # context using option ZMQ::RATE
context "using option ZMQ::RECOVERY_IVL" do
it "should set the multicast recovery buffer measured in seconds given a positive value" do
rate = 200
socket.setsockopt ZMQ::RECOVERY_IVL, rate
array = []
rc = socket.getsockopt(ZMQ::RECOVERY_IVL, array)
expect(rc).to eq(0)
expect(array[0]).to eq(rate)
end
it "returns -1 given a negative value" do
rate = -200
rc = socket.setsockopt ZMQ::RECOVERY_IVL, rate
expect(rc).to eq(-1)
end
end # context using option ZMQ::RECOVERY_IVL
context "using option ZMQ::SNDBUF" do
it "should set the OS send buffer given a positive value" do
size = 100
socket.setsockopt ZMQ::SNDBUF, size
array = []
rc = socket.getsockopt(ZMQ::SNDBUF, array)
expect(rc).to eq(0)
expect(array[0]).to eq(size)
end
end # context using option ZMQ::SNDBUF
context "using option ZMQ::RCVBUF" do
it "should set the OS receive buffer given a positive value" do
size = 100
socket.setsockopt ZMQ::RCVBUF, size
array = []
rc = socket.getsockopt(ZMQ::RCVBUF, array)
expect(rc).to eq(0)
expect(array[0]).to eq(size)
end
end # context using option ZMQ::RCVBUF
context "using option ZMQ::LINGER" do
it "should set the socket message linger option measured in milliseconds given a positive value" do
value = 200
socket.setsockopt ZMQ::LINGER, value
array = []
rc = socket.getsockopt(ZMQ::LINGER, array)
expect(rc).to eq(0)
expect(array[0]).to eq(value)
end
it "should set the socket message linger option to 0 for dropping packets" do
value = 0
socket.setsockopt ZMQ::LINGER, value
array = []
rc = socket.getsockopt(ZMQ::LINGER, array)
expect(rc).to eq(0)
expect(array[0]).to eq(value)
end
it "should default to a value of 0" do
value = [SUB, XSUB].include?(socket_type) ? 0 : -1
array = []
rc = socket.getsockopt(ZMQ::LINGER, array)
expect(rc).to eq(0)
expect(array[0]).to eq(value)
end
end # context using option ZMQ::LINGER
context "using option ZMQ::RECONNECT_IVL" do
it "should set the time interval for reconnecting disconnected sockets measured in milliseconds given a positive value" do
value = 200
socket.setsockopt ZMQ::RECONNECT_IVL, value
array = []
rc = socket.getsockopt(ZMQ::RECONNECT_IVL, array)
expect(rc).to eq(0)
expect(array[0]).to eq(value)
end
it "should default to a value of 100" do
value = 100
array = []
rc = socket.getsockopt(ZMQ::RECONNECT_IVL, array)
expect(rc).to eq(0)
expect(array[0]).to eq(value)
end
end # context using option ZMQ::RECONNECT_IVL
context "using option ZMQ::BACKLOG" do
it "should set the maximum number of pending socket connections given a positive value" do
value = 200
rc = socket.setsockopt ZMQ::BACKLOG, value
expect(rc).to eq(0)
array = []
rc = socket.getsockopt(ZMQ::BACKLOG, array)
expect(rc).to eq(0)
expect(array[0]).to eq(value)
end
it "should default to a value of 100" do
value = 100
array = []
rc = socket.getsockopt(ZMQ::BACKLOG, array)
expect(rc).to eq(0)
expect(array[0]).to eq(value)
end
end # context using option ZMQ::BACKLOG
end # context #setsockopt
context "#getsockopt for a #{ZMQ::SocketTypeNameMap[socket_type]} socket" do
before(:all) { @ctx = Context.new }
after(:all) { @ctx.terminate }
let(:socket) do
Socket.new @ctx.pointer, socket_type
end
after(:each) do
socket.close
end
if RUBY_PLATFORM =~ /linux|darwin/
# this spec doesn't work on Windows; hints welcome
context "using option ZMQ::FD" do
it "should return an FD as a positive integer" do
array = []
rc = socket.getsockopt(ZMQ::FD, array)
expect(rc).to eq(0)
expect(array[0]).to be > 0
end
it "returns a valid FD that is accepted by the system poll() function" do
# Use FFI to wrap the C library function +poll+ so that we can execute it
# on the 0mq file descriptor. If it returns 0, then it succeeded and the FD
# is valid!
module LibSocket
extend FFI::Library
# figures out the correct libc for each platform including Windows
library = ffi_lib(FFI::Library::LIBC).first
find_type(:nfds_t) rescue typedef(:uint32, :nfds_t)
attach_function :poll, [:pointer, :nfds_t, :int], :int
class PollFD < FFI::Struct
layout :fd, :int,
:events, :short,
:revents, :short
end
end # module LibSocket
array = []
rc = socket.getsockopt(ZMQ::FD, array)
expect(rc).to eq(0)
fd = array[0]
# setup the BSD poll_fd struct
pollfd = LibSocket::PollFD.new
pollfd[:fd] = fd
pollfd[:events] = 0
pollfd[:revents] = 0
rc = LibSocket.poll(pollfd, 1, 0)
expect(rc).to eq(0)
end
end
end # posix platform
context "using option ZMQ::EVENTS" do
it "should return a mask of events as a Fixnum" do
array = []
rc = socket.getsockopt(ZMQ::EVENTS, array)
expect(rc).to eq(0)
expect(array[0]).to be_a(Fixnum)
end
end
context "using option ZMQ::TYPE" do
it "should return the socket type" do
array = []
rc = socket.getsockopt(ZMQ::TYPE, array)
expect(rc).to eq(0)
expect(array[0]).to eq(socket_type)
end
end
end # context #getsockopt
end # each socket_type
describe "Mapping socket EVENTS to POLLIN and POLLOUT" do
include APIHelper
shared_examples_for "pubsub sockets where" do
it "SUB socket that received a message always has POLLIN set" do
events = []
rc = @sub.getsockopt(ZMQ::EVENTS, events)
expect(rc).to eq(0)
expect(events[0]).to eq ZMQ::POLLIN
end
it "PUB socket always has POLLOUT set" do
events = []
rc = @pub.getsockopt(ZMQ::EVENTS, events)
expect(rc).to eq(0)
expect(events[0]).to eq ZMQ::POLLOUT
end
it "PUB socket never has POLLIN set" do
events = []
rc = @pub.getsockopt(ZMQ::EVENTS, events)
expect(rc).to eq(0)
expect(events[0]).not_to eq ZMQ::POLLIN
end
it "SUB socket never has POLLOUT set" do
events = []
rc = @sub.getsockopt(ZMQ::EVENTS, events)
expect(rc).to eq(0)
expect(events[0]).not_to eq ZMQ::POLLOUT
end
end # shared example for pubsub
context "when SUB binds and PUB connects" do
before(:each) do
@ctx = Context.new
poller_setup
endpoint = "inproc://socket_test"
@sub = @ctx.socket ZMQ::SUB
rc = @sub.setsockopt ZMQ::SUBSCRIBE, ''
expect(rc).to eq(0)
@pub = @ctx.socket ZMQ::PUB
@sub.bind(endpoint)
connect_to_inproc(@pub, endpoint)
@pub.send_string('test')
end
#it_behaves_like "pubsub sockets where" # see Jira LIBZMQ-270
end # context SUB binds PUB connects
context "when SUB connects and PUB binds" do
before(:each) do
@ctx = Context.new
poller_setup
endpoint = "inproc://socket_test"
@sub = @ctx.socket ZMQ::SUB
rc = @sub.setsockopt ZMQ::SUBSCRIBE, ''
@pub = @ctx.socket ZMQ::PUB
@pub.bind(endpoint)
connect_to_inproc(@sub, endpoint)
poll_it_for_read(@sub) do
rc = @pub.send_string('test')
end
end
it_behaves_like "pubsub sockets where"
end # context SUB binds PUB connects
after(:each) do
@sub.close
@pub.close
# must call close on *every* socket before calling terminate otherwise it blocks indefinitely
@ctx.terminate
end
end # describe 'events mapping to pollin and pollout'
end # describe Socket
end # module ZMQ
ffi-rzmq-2.0.4/spec/util_spec.rb 0000644 0000041 0000041 00000001457 12463440532 016560 0 ustar www-data www-data require File.join(File.dirname(__FILE__), %w[spec_helper])
module ZMQ
describe Util do
if LibZMQ.version4?
describe "curve_keypair" do
it "returns a set of public and private keys (libsodium linked)" do
expect(ZMQ::Util).to receive(:curve_keypair).and_return([0, 1])
public_key, private_key = ZMQ::Util.curve_keypair
expect(public_key).not_to eq private_key
expect(public_key).not_to be_nil
expect(private_key).not_to be_nil
end
it "raises if zmq does not support CURVE (libsodium not linked)" do
expect do
allow(LibZMQ).to receive(:zmq_curve_keypair).and_return(-1)
ZMQ::Util.curve_keypair
end.to raise_exception(ZMQ::NotSupportedError)
end
end
end
end
end
ffi-rzmq-2.0.4/spec/support/ 0000755 0000041 0000041 00000000000 12463440532 015751 5 ustar www-data www-data ffi-rzmq-2.0.4/spec/support/test.key 0000644 0000041 0000041 00000001567 12463440532 017453 0 ustar www-data www-data -----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQD03tfzBYOIBoweSsf18z+R0MrNpEfl+/ofnBu48/afJjYlvxue
FkF+8+BMy60hazXV4XujUV86nFbcjZfTT4eEKT6TonXTozVsKK2ounezUoSuIBcy
+tqmOYErGeE2C8X0gxMumYIMtHxRbvAu4hyVaP9ZGANTsYpA5G3x19K1twIDAQAB
AoGBALdY8BsAIudD98Bqv+RxuUSGMIPfoRIcJMFsUvmeeifaJasHuDcbdPkIxAbc
boraSpoV1kyIDiTFkOhdgLPxFYaxlHGN7c/WqaGMtTMUuKgyItXPEFd9vHOWImIM
gJKaYGSrKbAsiFt1mGBKEPRDE1TwnrPZAKj9mGJA1gtzq6GxAkEA/z3HvaXoBuGw
9f7uSzURSIkL+HYPejr83IBeqFdH+roxhgkwh+WMWBbNicCu0LsM00QAuZXlnqX7
FUaKguL1GQJBAPWZK/+Gs7LF6GqkANu+FOVhe9zxbffvZ+ibvDraB7fvgay+TwYi
88J6IhSp30F/tUtTGRl2UyszXLXbAAxpC08CQCwQTFVPOPlHKTeupRDSvoMZNbnV
F+LwIAspFi5VsxVz42zSVVCArnPeq+kmHIfoYtRuHvnrCNMUsH4ByZPC/rECQQDO
GJ+Haq5ZkyKaes4NmNFIPCoJGsDBkrGLzUSDznszq1USdREzgRk1VfBLjtG+0UB9
2VnyuAzK7+sY4JKF15CZAkAewe1vMGsFAXYojCI+wsDVlvlGTFmAZYnXE4hUVft+
j/XwOPp7WiWl9dgJ25wQrkYOrYMkZ9SqHO0SSxeSo2yT
-----END RSA PRIVATE KEY-----
ffi-rzmq-2.0.4/spec/support/test.crt 0000644 0000041 0000041 00000001523 12463440532 017443 0 ustar www-data www-data -----BEGIN CERTIFICATE-----
MIICRzCCAbCgAwIBAgIJAIhPfXaKAijbMA0GCSqGSIb3DQEBBQUAMCIxCzAJBgNV
BAYTAlhZMRMwEQYDVQQIEwpOZXJ2ZXJsYW5kMB4XDTEyMDgyODE2NDIzMloXDTEz
MDgyODE2NDIzMlowIjELMAkGA1UEBhMCWFkxEzARBgNVBAgTCk5lcnZlcmxhbmQw
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPTe1/MFg4gGjB5Kx/XzP5HQys2k
R+X7+h+cG7jz9p8mNiW/G54WQX7z4EzLrSFrNdXhe6NRXzqcVtyNl9NPh4QpPpOi
ddOjNWworai6d7NShK4gFzL62qY5gSsZ4TYLxfSDEy6Zggy0fFFu8C7iHJVo/1kY
A1OxikDkbfHX0rW3AgMBAAGjgYQwgYEwHQYDVR0OBBYEFEbSBVWuzrSmust9Sa6J
sm7Tg40KMFIGA1UdIwRLMEmAFEbSBVWuzrSmust9Sa6Jsm7Tg40KoSakJDAiMQsw
CQYDVQQGEwJYWTETMBEGA1UECBMKTmVydmVybGFuZIIJAIhPfXaKAijbMAwGA1Ud
EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAS0+17FjKmjCPHfJApTZgVXGD/LHC
Bdzo+bl/hWpMyF27CUJxdZOMRpHqpE/Qv77jG/tfHnuArYPwDcB8AIErmvhNKCZx
GLm19kTd4K1Y5JcAqkOHBma1e1V/g4ryWvUtpQkqD6tQxX0ctlBmXXK/Jsj/wG0W
NNCQq2S19aNZcGo=
-----END CERTIFICATE-----
ffi-rzmq-2.0.4/spec/nonblocking_recv_spec.rb 0000644 0000041 0000041 00000023520 12463440532 021120 0 ustar www-data www-data
require File.join(File.dirname(__FILE__), %w[spec_helper])
module ZMQ
describe Socket do
include APIHelper
shared_examples_for "any socket" do
it "returns -1 when there are no messages to read" do
array = []
rc = @receiver.recvmsgs(array, ZMQ::DONTWAIT)
expect(Util.resultcode_ok?(rc)).to eq(false)
end
it "gets EAGAIN when there are no messages to read" do
array = []
rc = @receiver.recvmsgs(array, ZMQ::DONTWAIT)
expect(ZMQ::Util.errno).to eq(ZMQ::EAGAIN)
end
it "returns the given array unmodified when there are no messages to read" do
array = []
rc = @receiver.recvmsgs(array, ZMQ::DONTWAIT)
expect(array.size).to eq(0)
end
end
shared_examples_for "sockets without exposed envelopes" do
it "read the single message and returns a successful result code" do
poll_it_for_read(@receiver) do
rc = @sender.send_string('test')
expect(Util.resultcode_ok?(rc)).to eq(true)
end
array = []
rc = @receiver.recvmsgs(array, ZMQ::DONTWAIT)
expect(Util.resultcode_ok?(rc)).to eq(true)
expect(array.size).to eq(1)
end
it "read all message parts transmitted and returns a successful result code" do
poll_it_for_read(@receiver) do
strings = Array.new(10, 'test')
rc = @sender.send_strings(strings)
expect(Util.resultcode_ok?(rc)).to eq(true)
end
array = []
rc = @receiver.recvmsgs(array, ZMQ::DONTWAIT)
expect(Util.resultcode_ok?(rc)).to eq(true)
expect(array.size).to eq(10)
end
end
shared_examples_for "sockets with exposed envelopes" do
it "read the single message and returns a successful result code" do
poll_it_for_read(@receiver) do
rc = @sender.send_string('test')
expect(Util.resultcode_ok?(rc)).to eq(true)
end
array = []
rc = @receiver.recvmsgs(array, ZMQ::DONTWAIT)
expect(Util.resultcode_ok?(rc)).to eq(true)
expect(array.size).to eq(1 + 1) # extra 1 for envelope
end
it "read all message parts transmitted and returns a successful result code" do
poll_it_for_read(@receiver) do
strings = Array.new(10, 'test')
rc = @sender.send_strings(strings)
expect(Util.resultcode_ok?(rc)).to eq(true)
end
array = []
rc = @receiver.recvmsgs(array, ZMQ::DONTWAIT)
expect(Util.resultcode_ok?(rc)).to eq(true)
expect(array.size).to eq(10 + 1) # add 1 for the envelope
end
end
context "PUB" do
describe "non-blocking #recvmsgs where sender connects & receiver binds" do
include APIHelper
before(:each) do
@context = Context.new
poller_setup
endpoint = "inproc://nonblocking_test"
@receiver = @context.socket ZMQ::SUB
assert_ok(@receiver.setsockopt(ZMQ::SUBSCRIBE, ''))
@sender = @context.socket ZMQ::PUB
@receiver.bind(endpoint)
connect_to_inproc(@sender, endpoint)
end
after(:each) do
@receiver.close
@sender.close
@context.terminate
end
it_behaves_like "any socket"
#it_behaves_like "sockets without exposed envelopes" # see Jira LIBZMQ-270; fails with tcp transport
end # describe 'non-blocking recvmsgs'
describe "non-blocking #recvmsgs where sender binds & receiver connects" do
include APIHelper
before(:each) do
@context = Context.new
poller_setup
endpoint = "inproc://nonblocking_test"
@receiver = @context.socket ZMQ::SUB
port = connect_to_random_tcp_port(@receiver)
assert_ok(@receiver.setsockopt(ZMQ::SUBSCRIBE, ''))
@sender = @context.socket ZMQ::PUB
@sender.bind(endpoint)
connect_to_inproc(@receiver, endpoint)
end
after(:each) do
@receiver.close
@sender.close
@context.terminate
end
it_behaves_like "any socket"
it_behaves_like "sockets without exposed envelopes" # see Jira LIBZMQ-270; fails with tcp transport
end # describe 'non-blocking recvmsgs'
end # Pub
context "REQ" do
describe "non-blocking #recvmsgs where sender connects & receiver binds" do
include APIHelper
before(:each) do
@context = Context.new
poller_setup
endpoint = "inproc://nonblocking_test"
@receiver = @context.socket ZMQ::REP
@sender = @context.socket ZMQ::REQ
@receiver.bind(endpoint)
connect_to_inproc(@sender, endpoint)
end
after(:each) do
@receiver.close
@sender.close
@context.terminate
end
it_behaves_like "any socket"
it_behaves_like "sockets without exposed envelopes"
end # describe 'non-blocking recvmsgs'
describe "non-blocking #recvmsgs where sender binds & receiver connects" do
include APIHelper
before(:each) do
@context = Context.new
poller_setup
endpoint = "inproc://nonblocking_test"
@receiver = @context.socket ZMQ::REP
@sender = @context.socket ZMQ::REQ
@sender.bind(endpoint)
connect_to_inproc(@receiver, endpoint)
end
after(:each) do
@receiver.close
@sender.close
@context.terminate
end
it_behaves_like "any socket"
it_behaves_like "sockets without exposed envelopes"
end # describe 'non-blocking recvmsgs'
end # REQ
context "PUSH" do
describe "non-blocking #recvmsgs where sender connects & receiver binds" do
include APIHelper
before(:each) do
@context = Context.new
poller_setup
endpoint = "inproc://nonblocking_test"
@receiver = @context.socket ZMQ::PULL
@sender = @context.socket ZMQ::PUSH
@receiver.bind(endpoint)
connect_to_inproc(@sender, endpoint)
end
after(:each) do
@receiver.close
@sender.close
@context.terminate
end
it_behaves_like "any socket"
it_behaves_like "sockets without exposed envelopes"
end # describe 'non-blocking recvmsgs'
describe "non-blocking #recvmsgs where sender binds & receiver connects" do
include APIHelper
before(:each) do
@context = Context.new
poller_setup
endpoint = "inproc://nonblocking_test"
@receiver = @context.socket ZMQ::PULL
@sender = @context.socket ZMQ::PUSH
@sender.bind(endpoint)
connect_to_inproc(@receiver, endpoint)
end
after(:each) do
@receiver.close
@sender.close
@context.terminate
end
it_behaves_like "any socket"
it_behaves_like "sockets without exposed envelopes"
end # describe 'non-blocking recvmsgs'
end # PUSH
context "DEALER" do
describe "non-blocking #recvmsgs where sender connects & receiver binds" do
include APIHelper
before(:each) do
@context = Context.new
poller_setup
endpoint = "inproc://nonblocking_test"
@receiver = @context.socket ZMQ::ROUTER
@sender = @context.socket ZMQ::DEALER
@receiver.bind(endpoint)
connect_to_inproc(@sender, endpoint)
end
after(:each) do
@receiver.close
@sender.close
@context.terminate
end
it_behaves_like "any socket"
it_behaves_like "sockets with exposed envelopes"
end # describe 'non-blocking recvmsgs'
describe "non-blocking #recvmsgs where sender binds & receiver connects" do
include APIHelper
before(:each) do
@context = Context.new
poller_setup
endpoint = "inproc://nonblocking_test"
@receiver = @context.socket ZMQ::ROUTER
@sender = @context.socket ZMQ::DEALER
@sender.bind(endpoint)
connect_to_inproc(@receiver, endpoint)
end
after(:each) do
@receiver.close
@sender.close
@context.terminate
end
it_behaves_like "any socket"
it_behaves_like "sockets with exposed envelopes"
end # describe 'non-blocking recvmsgs'
end # DEALER
context "XREQ" do
describe "non-blocking #recvmsgs where sender connects & receiver binds" do
include APIHelper
before(:each) do
@context = Context.new
poller_setup
endpoint = "inproc://nonblocking_test"
@receiver = @context.socket ZMQ::XREP
@sender = @context.socket ZMQ::XREQ
@receiver.bind(endpoint)
connect_to_inproc(@sender, endpoint)
end
after(:each) do
@receiver.close
@sender.close
@context.terminate
end
it_behaves_like "any socket"
it_behaves_like "sockets with exposed envelopes"
end # describe 'non-blocking recvmsgs'
describe "non-blocking #recvmsgs where sender binds & receiver connects" do
include APIHelper
before(:each) do
@context = Context.new
poller_setup
endpoint = "inproc://nonblocking_test"
@receiver = @context.socket ZMQ::XREP
@sender = @context.socket ZMQ::XREQ
@sender.bind(endpoint)
connect_to_inproc(@receiver, endpoint)
end
after(:each) do
@receiver.close
@sender.close
@context.terminate
end
it_behaves_like "any socket"
it_behaves_like "sockets with exposed envelopes"
end # describe 'non-blocking recvmsgs'
end # XREQ
end # describe Socket
end # module ZMQ
ffi-rzmq-2.0.4/README.rdoc 0000644 0000041 0000041 00000016115 12463440532 015115 0 ustar www-data www-data ffi-rzmq
by Chuck Remes
http://www.zeromq.org/bindings:ruby-ffi
== FUTURE SUPPORT
Most people writing Zeromq applications in Ruby should be using the rbczmq
project. It wraps the CZMQ binding which is a much higher-level library
for writing Zeromq code. Find the Ruby gem here:
http://github.com/methodmissing/rbczmq
Few projects need to write the low-level zeromq code that this gem allows.
With the release of ffi-rzmq 2.0.3, this library is going into permanent
maintenance mode. As new versions of libzmq are released, interested parties
should send pull requests to this project or its related project
ffi-rzmq-core to support new features.
The original README text follows...
== DESCRIPTION:
This gem wraps the ZeroMQ networking library using the ruby FFI (foreign
function interface). It's a pure ruby wrapper so this gem can be loaded
and run by any ruby runtime that supports FFI. That's all of them:
MRI 1.9.x, Rubinius and JRuby.
The Ruby API provided by this gem is *NOT* very Ruby-like. It very closely
tracks the libzmq C API. However, the contributors to this project have
done all of the hard work to wrap up all of libzmq and make it accessible
from Ruby. If you want it to be more Ruby-like (e.g. raise Exceptions instead
of returning integer codes) then *wrap this library* with your own and release
it as a gem. We will all be grateful!
This single gem supports 0mq 3.2.x and 4.x APIs. The 0mq project started
making backward-incompatible changes to the API with the 3.1.x release.
The gem auto-configures itself to expose the API conforming to the loaded
C library. 0mq 2.x is no longer supported. 0mq API 3.0 is *not* supported;
the 0mq community voted to abandon it.
The impetus behind this library was to provide support for ZeroMQ in
JRuby which has native threads. Unlike MRI, which has a GIL, JRuby and
Rubinius allow for threaded access to Ruby code from outside extensions.
ZeroMQ is heavily threaded, so until the MRI runtime removes its GIL,
JRuby and Rubinius will likely be the best environments to run this library.
Please read the History.txt file for a description of all changes, including
API changes, since the last release!
== PERFORMANCE:
Check out the latest performance results:
http://www.zeromq.org/bindings:ruby-ffi
The short version is that the FFI bindings are a few microseconds slower
than using a C extension.
== FEATURES/PROBLEMS:
This gem needs more tests. This gem has been battle tested by myself
and others for years, so I am fairly confident that it is solid.
However, it is inevitable that there will be bugs, so please open
issues for them here or fork this project, fix them, and send me a pull
request.
The 'ffi' gem has dropped support for MRI 1.8.x. Since this project relies
on that gem to load and run this code, then this project also no longer
supports MRI 1.8.x. I recommend JRuby for the best performance and
stability.
All features are implemented.
== BUILD STATUS:
{
}[http://travis-ci.org/chuckremes/ffi-rzmq]
{
}[https://codeclimate.com/github/chuckremes/ffi-rzmq]
== SYNOPSIS:
0mq API v3.2-4 client code:
require 'rubygems'
require 'ffi-rzmq'
if ARGV.length < 3
puts "usage: ruby local_lat.rb "
exit
end
bind_to = ARGV[0]
message_size = ARGV[1].to_i
roundtrip_count = ARGV[2].to_i
ctx = ZMQ::Context.new
s = ctx.socket ZMQ::REP
rc = s.setsockopt(ZMQ::SNDHWM, 100)
rc = s.setsockopt(ZMQ::RCVHWM, 100)
rc = s.bind(bind_to)
roundtrip_count.times do
msg = ""
rc = s.recv_string msg
raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size
rc = s.send_string msg, 0
end
0mq API v3.2-4 server code:
require 'rubygems'
require 'ffi-rzmq'
if ARGV.length < 3
puts "usage: ruby remote_lat.rb "
exit
end
def assert(rc)
raise "Last API call failed at #{caller(1)}" unless rc >= 0
end
connect_to = ARGV[0]
message_size = ARGV[1].to_i
roundtrip_count = ARGV[2].to_i
ctx = ZMQ::Context.new
s = ctx.socket ZMQ::REQ
rc = s.connect(connect_to)
msg = "#{ '3' * message_size }"
time_start = Time.now
roundtrip_count.times do
assert(s.send_string(msg, 0))
msg = ''
assert(s.recv_string(msg, 0))
raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size
end
time_end = Time.now
puts "Time #{( time_end - time_start )}"
== Better Examples
I highly recommend visiting the Learn Ruby 0mq project for a bunch of good code examples.
http://github.com/andrewvc/learn-ruby-zeromq
== REQUIREMENTS:
* 0mq 3.2.x, 4.x or later; 2.x, 3.0.x and 3.1.x are no longer supported
The ZeroMQ library must be installed on your system in a well-known location
like /usr/local/lib. This is the default for new ZeroMQ installs.
If you have installed ZeroMQ using brew, you need to `brew link zeromq` before installing this gem.
* ffi (>= 1.0.0)
* ffi-rzmq-core
This is a requirement for MRI and Rubinius. JRuby has FFI support built
in as a standard component. Do *not* run this gem under MRI with an old 'ffi' gem.
It will crash randomly and you will be sad.
== INSTALL:
Make sure the ZeroMQ library is already installed on your system. We recommend 'brew' or 'macports' to get it.
% gem install ffi-rzmq # should grab the latest release
To build from git master:
% git clone git://github.com/chuckremes/ffi-rzmq
% cd ffi-rzmq
% gem build ffi-rzmq.gemspec
% gem install ffi-rzmq-*.gem
NOTE for Windows users!
In order for this gem to find the libzmq.dll, it *must* be on the Windows PATH. Google
for "modify windows path" for instructions on how to do that if you are unfamiliar with
that activity. That DLL also requires that you copy libstdc++-6.dll and libgcc_s_sjlj-1.dll from DevKit MinGW into the same folder that you copied libzmq.dll.
== LICENSE:
(The MIT License)
Copyright (c) 2013 Chuck Remes
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
ffi-rzmq-2.0.4/.travis.yml 0000644 0000041 0000041 00000000365 12463440532 015420 0 ustar www-data www-data before_install: sudo apt-get install libzmq3-dev
script: bundle exec rspec
language: ruby
rvm:
- 1.9.3
- 2.0.0
- ruby-head
- jruby-19mode
- jruby-head
- rbx-2.1.1
matrix:
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
ffi-rzmq-2.0.4/lib/ 0000755 0000041 0000041 00000000000 12463440532 014051 5 ustar www-data www-data ffi-rzmq-2.0.4/lib/ffi-rzmq/ 0000755 0000041 0000041 00000000000 12463440532 015604 5 ustar www-data www-data ffi-rzmq-2.0.4/lib/ffi-rzmq/device.rb 0000644 0000041 0000041 00000001275 12463440532 017375 0 ustar www-data www-data module ZMQ
class Device
attr_reader :device
def self.create(frontend, backend, capture=nil)
dev = nil
begin
dev = new(frontend, backend, capture)
rescue ArgumentError
dev = nil
end
dev
end
def initialize(frontend, backend, capture=nil)
[["frontend", frontend], ["backend", backend]].each do |name, socket|
unless socket.is_a?(ZMQ::Socket)
raise ArgumentError, "Expected a ZMQ::Socket, not a #{socket.class} as the #{name}"
end
end
LibZMQ.zmq_proxy(frontend.socket, backend.socket, capture ? capture.socket : nil)
end
end
# Alias for Device
#
class Proxy < Device; end
end
ffi-rzmq-2.0.4/lib/ffi-rzmq/context.rb 0000644 0000041 0000041 00000007152 12463440532 017622 0 ustar www-data www-data
module ZMQ
# Recommended to use the default for +io_threads+
# since most programs will not saturate I/O.
#
# The rule of thumb is to make +io_threads+ equal to the number
# gigabits per second that the application will produce.
#
# The +io_threads+ number specifies the size of the thread pool
# allocated by 0mq for processing incoming/outgoing messages.
#
# Returns a context object when allocation succeeds. It's necessary
# for passing to the
# #Socket constructor when allocating new sockets. All sockets
# live within a context.
#
# Also, Sockets should *only* be accessed from the thread where they
# were first created. Do *not* pass sockets between threads; pass
# in the context and allocate a new socket per thread. If you must
# use threads, then make sure to execute a full memory barrier (e.g.
# mutex) as you pass a socket from one thread to the next.
#
# To connect sockets between contexts, use +inproc+ or +ipc+
# transport and set up a 0mq socket between them. This is also the
# recommended technique for allowing sockets to communicate between
# threads.
#
# context = ZMQ::Context.create
# if context
# socket = context.socket(ZMQ::REQ)
# if socket
# ...
# else
# STDERR.puts "Socket allocation failed"
# end
# else
# STDERR.puts "Context allocation failed"
# end
#
#
class Context
attr_reader :context, :io_threads, :max_sockets
alias :pointer :context
# Use the factory method Context#create to make contexts.
#
def self.create(opts = {})
new(opts) rescue nil
end
def initialize(opts = {})
if opts.respond_to?(:empty?)
@io_threads = opts[:io_threads] || IO_THREADS_DFLT
@max_sockets = opts[:max_sockets] || MAX_SOCKETS_DFLT
else
@io_threads = opts || 1
@max_sockets = MAX_SOCKETS_DFLT
end
@context = LibZMQ.zmq_ctx_new
ZMQ::Util.error_check 'zmq_ctx_new', (@context.nil? || @context.null?) ? -1 : 0
rc = LibZMQ.zmq_ctx_set(@context, ZMQ::IO_THREADS, @io_threads)
ZMQ::Util.error_check 'zmq_ctx_set', rc
rc = LibZMQ.zmq_ctx_set(@context, ZMQ::MAX_SOCKETS, @max_sockets)
ZMQ::Util.error_check 'zmq_ctx_set', rc
define_finalizer
end
# Call to release the context and any remaining data associated
# with past sockets. This will close any sockets that remain
# open; further calls to those sockets will return -1 to indicate
# the operation failed.
#
# Returns 0 for success, -1 for failure.
#
def terminate
unless @context.nil? || @context.null?
remove_finalizer
rc = LibZMQ.zmq_ctx_destroy(@context)
@context = nil
rc
else
0
end
end
# Short-cut to allocate a socket for a specific context.
#
# Takes several +type+ values:
# #ZMQ::REQ
# #ZMQ::REP
# #ZMQ::PUB
# #ZMQ::SUB
# #ZMQ::PAIR
# #ZMQ::PULL
# #ZMQ::PUSH
# #ZMQ::DEALER
# #ZMQ::ROUTER
#
# Returns a #ZMQ::Socket when the allocation succeeds, nil
# if it fails.
#
def socket type
sock = nil
begin
sock = Socket.new @context, type
rescue ContextError => e
sock = nil
end
sock
end
private
def define_finalizer
ObjectSpace.define_finalizer(self, self.class.close(@context, Process.pid))
end
def remove_finalizer
ObjectSpace.undefine_finalizer self
end
def self.close context, pid
Proc.new { LibZMQ.zmq_term context if !context.null? && Process.pid == pid }
end
end
end # module ZMQ
ffi-rzmq-2.0.4/lib/ffi-rzmq/poll_items.rb 0000644 0000041 0000041 00000003525 12463440532 020305 0 ustar www-data www-data require 'forwardable'
require 'ostruct'
module ZMQ
class PollItems
include Enumerable
extend Forwardable
def_delegators :@pollables, :size, :empty?
def initialize
@pollables = {}
@item_size = LibZMQ::PollItem.size
@item_store = nil
end
def address
clean
@item_store
end
def get pollable
return unless entry = @pollables[pollable]
clean
pointer = @item_store + (@item_size * entry.index)
item = ZMQ::PollItem.from_pointer(pointer)
item.pollable = pollable
item
end
alias :[] :get
def <<(poll_item)
@dirty = true
@pollables[poll_item.pollable] = OpenStruct.new(:index => size, :data => poll_item)
end
alias :push :<<
def delete pollable
if @pollables.delete(pollable)
@dirty = true
clean
true
else
false
end
end
def each &blk
clean
@pollables.each_key do |pollable|
yield get(pollable)
end
end
def inspect
clean
str = ""
each { |item| str << "ptr [#{item[:socket]}], events [#{item[:events]}], revents [#{item[:revents]}], " }
str.chop.chop
end
def to_s; inspect; end
private
# Allocate a contiguous chunk of memory and copy over the PollItem structs
# to this block. Note that the old +@store+ value goes out of scope so when
# it is garbage collected that native memory should be automatically freed.
def clean
if @dirty
@item_store = FFI::MemoryPointer.new @item_size, size, true
offset = 0
@pollables.each_with_index do |(pollable, entry), index|
entry.index = index
LibC.memcpy(@item_store + offset, entry.data.pointer, @item_size)
offset += @item_size
end
@dirty = false
end
end
end
end
ffi-rzmq-2.0.4/lib/ffi-rzmq/version.rb 0000644 0000041 0000041 00000000043 12463440532 017613 0 ustar www-data www-data module ZMQ
VERSION = "2.0.4"
end
ffi-rzmq-2.0.4/lib/ffi-rzmq/poll_item.rb 0000644 0000041 0000041 00000002433 12463440532 020117 0 ustar www-data www-data require 'forwardable'
require 'io_extensions'
module ZMQ
class PollItem
extend Forwardable
def_delegators :@poll_item, :pointer, :readable?, :writable?
attr_accessor :pollable, :poll_item
def initialize(zmq_poll_item = nil)
@poll_item = zmq_poll_item || LibZMQ::PollItem.new
end
def self.from_pointer(pointer)
self.new(LibZMQ::PollItem.new(pointer))
end
def self.from_pollable(pollable)
item = self.new
item.pollable = pollable
case
when pollable.respond_to?(:socket)
item.socket = pollable.socket
when pollable.respond_to?(:posix_fileno)
item.fd = pollable.posix_fileno
when pollable.respond_to?(:io)
item.fd = pollable.io.posix_fileno
end
item
end
def closed?
case
when pollable.respond_to?(:closed?)
pollable.closed?
when pollable.respond_to?(:socket)
pollable.socket.nil?
when pollable.respond_to?(:io)
pollable.io.closed?
end
end
def socket=(arg); @poll_item[:socket] = arg; end
def socket; @poll_item[:socket]; end
def fd=(arg); @poll_item[:fd] = arg; end
def fd; @poll_item[:fd]; end
def events=(arg); @poll_item[:events] = arg; end
def events; @poll_item[:events]; end
end
end
ffi-rzmq-2.0.4/lib/ffi-rzmq/message.rb 0000644 0000041 0000041 00000021717 12463440532 017565 0 ustar www-data www-data
module ZMQ
# The factory constructor optionally takes a string as an argument. It will
# copy this string to native memory in preparation for transmission.
# So, don't pass a string unless you intend to send it. Internally it
# calls #copy_in_string.
#
# Call #close to release buffers when you are done with the data.
#
# (This class is not really zero-copy. Ruby makes this near impossible
# since Ruby objects can be relocated in memory by the GC at any
# time. There is no way to peg them to native memory or have them
# use non-movable native memory as backing store.)
#
# Message represents ruby equivalent of the +zmq_msg_t+ C struct.
# Access the underlying memory buffer and the buffer size using the
# #data and #size methods respectively.
#
# It is recommended that this class be composed inside another class for
# access to the underlying buffer. The outer wrapper class can provide
# nice accessors for the information in the data buffer; a clever
# implementation can probably lazily encode/decode the data buffer
# on demand. Lots of protocols send more information than is strictly
# necessary, so only decode (copy from the 0mq buffer to Ruby) that
# which is necessary.
#
# When you are done using a *received* message object, call #close to
# release the associated buffers.
#
# received_message = Message.create
# if received_message
# rc = socket.recvmsg(received_message)
# if ZMQ::Util.resultcode_ok?(rc)
# puts "Message contained: #{received_message.copy_out_string}"
# else
# STDERR.puts "Error when receiving message: #{ZMQ::Util.error_string}"
# end
#
#
# Define a custom layout for the data sent between 0mq peers.
#
# class MyMessage
# class Layout < FFI::Struct
# layout :value1, :uint8,
# :value2, :uint64,
# :value3, :uint32,
# :value4, [:char, 30]
# end
#
# def initialize msg_struct = nil
# if msg_struct
# @msg_t = msg_struct
# @data = Layout.new(@msg_t.data)
# else
# @pointer = FFI::MemoryPointer.new :byte, Layout.size, true
# @data = Layout.new @pointer
# end
# end
#
# def size() @size = @msg_t.size; end
#
# def value1
# @data[:value1]
# end
#
# def value4
# @data[:value4].to_ptr.read_string
# end
#
# def value1=(val)
# @data[:value1] = val
# end
#
# def create_sendable_message
# msg = Message.new
# msg.copy_in_bytes @pointer, Layout.size
# end
#
#
# message = Message.new
# successful_read = socket.recv message
# message = MyMessage.new message if successful_read
# puts "value1 is #{message.value1}"
#
class Message
# Recommended way to create a standard message. A Message object is
# returned upon success, nil when allocation fails.
#
def self.create message = nil
new(message) rescue nil
end
def initialize message = nil
# allocate our own pointer so that we can tell it to *not* zero out
# the memory; it's pointless work since the library is going to
# overwrite it anyway.
@pointer = FFI::MemoryPointer.new Message.msg_size, 1, false
if message
copy_in_string message
else
# initialize an empty message structure to receive a message
result_code = LibZMQ.zmq_msg_init @pointer
raise unless Util.resultcode_ok?(result_code)
end
end
# Makes a copy of the ruby +string+ into a native memory buffer so
# that libzmq can send it. The underlying library will handle
# deallocation of the native memory buffer.
#
# Can only be initialized via #copy_in_string or #copy_in_bytes once.
#
def copy_in_string string
string_size = string.respond_to?(:bytesize) ? string.bytesize : string.size
copy_in_bytes string, string_size if string
end
# Makes a copy of +len+ bytes from the ruby string +bytes+. Library
# handles deallocation of the native memory buffer.
#
# Can only be initialized via #copy_in_string or #copy_in_bytes once.
#
def copy_in_bytes bytes, len
data_buffer = LibC.malloc len
# writes the exact number of bytes, no null byte to terminate string
data_buffer.write_string bytes, len
# use libC to call free on the data buffer; earlier versions used an
# FFI::Function here that called back into Ruby, but Rubinius won't
# support that and there are issues with the other runtimes too
LibZMQ.zmq_msg_init_data @pointer, data_buffer, len, LibC::Free, nil
end
# Provides the memory address of the +zmq_msg_t+ struct. Used mostly for
# passing to other methods accessing the underlying library that
# require a real data address.
#
def address
@pointer
end
alias :pointer :address
def copy source
LibZMQ.zmq_msg_copy @pointer, source
end
def move source
LibZMQ.zmq_msg_move @pointer, source
end
# Provides the size of the data buffer for this +zmq_msg_t+ C struct.
#
def size
LibZMQ.zmq_msg_size @pointer
end
# Returns a pointer to the data buffer.
# This pointer should *never* be freed. It will automatically be freed
# when the +message+ object goes out of scope and gets garbage
# collected.
#
def data
LibZMQ.zmq_msg_data @pointer
end
# Returns the data buffer as a string.
#
# Note: If this is binary data, it won't print very prettily.
#
def copy_out_string
data.read_string(size)
end
# Manually release the message struct and its associated data
# buffer.
#
# Only releases the buffer a single time. Subsequent calls are
# no ops.
#
def close
rc = 0
if @pointer
rc = LibZMQ.zmq_msg_close @pointer
@pointer = nil
end
rc
end
# cache the msg size so we don't have to recalculate it when creating
# each new instance
@msg_size = LibZMQ::Message.size
def self.msg_size() @msg_size; end
end # class Message
class Message
def get(property)
LibZMQ.zmq_msg_get(@pointer, property)
end
# Returns true if this message has additional parts coming.
#
def more?
Util.resultcode_ok?(get(MORE))
end
def set(property, value)
LibZMQ.zmq_msg_set(@pointer, property, value)
end
end
# A subclass of #Message that includes finalizers for deallocating
# native memory when this object is garbage collected. Note that on
# certain Ruby runtimes the use of finalizers can add 10s of
# microseconds of overhead for each message. The convenience comes
# at a price.
#
# The constructor optionally takes a string as an argument. It will
# copy this string to native memory in preparation for transmission.
# So, don't pass a string unless you intend to send it. Internally it
# calls #copy_in_string.
#
# Call #close to release buffers when you have *not* passed this on
# to Socket#send. That method calls #close on your behalf.
#
# When you are done using a *received* message object, just let it go out of
# scope to release the memory. During the next garbage collection run
# it will call the equivalent of #LibZMQ.zmq_msg_close to release
# all buffers. Obviously, this automatic collection of message objects
# comes at the price of a larger memory footprint (for the
# finalizer proc object) and lower performance. If you wanted blistering
# performance, Ruby isn't there just yet.
#
# As noted above, for sent objects the underlying library will call close
# for you.
#
class ManagedMessage < Message
# Makes a copy of +len+ bytes from the ruby string +bytes+. Library
# handles deallocation of the native memory buffer.
#
def copy_in_bytes bytes, len
rc = super(bytes, len)
# make sure we have a way to deallocate this memory if the object goes
# out of scope
define_finalizer
rc
end
# Manually release the message struct and its associated data
# buffer.
#
def close
rc = super()
remove_finalizer
rc
end
private
def define_finalizer
ObjectSpace.define_finalizer(self, self.class.close(@pointer))
end
def remove_finalizer
ObjectSpace.undefine_finalizer self
end
# Message finalizer
# Note that there is no error checking for the call to #zmq_msg_close.
# This is intentional. Since this code runs as a finalizer, there is no
# way to catch a raised exception anywhere near where the error actually
# occurred in the code, so we just ignore deallocation failures here.
def self.close ptr
Proc.new do
# release the data buffer
LibZMQ.zmq_msg_close ptr
end
end
# cache the msg size so we don't have to recalculate it when creating
# each new instance
# need to do this again because ivars are not inheritable
@msg_size = LibZMQ::Message.size
end # class ManagedMessage
end # module ZMQ
ffi-rzmq-2.0.4/lib/ffi-rzmq/socket.rb 0000644 0000041 0000041 00000044554 12463440532 017435 0 ustar www-data www-data
module ZMQ
class Socket
attr_reader :socket, :name
# Allocates a socket of type +type+ for sending and receiving data.
#
# +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB,
# ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH, ZMQ::XREQ, ZMQ::REP,
# ZMQ::DEALER or ZMQ::ROUTER.
#
# By default, this class uses ZMQ::Message for manual
# memory management. For automatic garbage collection of received messages,
# it is possible to override the :receiver_class to use ZMQ::ManagedMessage.
#
# sock = Socket.create(Context.create, ZMQ::REQ, :receiver_class => ZMQ::ManagedMessage)
#
# Advanced users may want to replace the receiver class with their
# own custom class. The custom class must conform to the same public API
# as ZMQ::Message.
#
# Creation of a new Socket object can return nil when socket creation
# fails.
#
# if (socket = Socket.new(context.pointer, ZMQ::REQ))
# ...
# else
# STDERR.puts "Socket creation failed"
# end
#
def self.create context_ptr, type, opts = {:receiver_class => ZMQ::Message}
new(context_ptr, type, opts) rescue nil
end
# To avoid rescuing exceptions, use the factory method #create for
# all socket creation.
#
# Allocates a socket of type +type+ for sending and receiving data.
#
# +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB,
# ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH, ZMQ::XREQ, ZMQ::REP,
# ZMQ::DEALER or ZMQ::ROUTER.
#
# By default, this class uses ZMQ::Message for manual
# memory management. For automatic garbage collection of received messages,
# it is possible to override the :receiver_class to use ZMQ::ManagedMessage.
#
# sock = Socket.new(Context.new, ZMQ::REQ, :receiver_class => ZMQ::ManagedMessage)
#
# Advanced users may want to replace the receiver class with their
# own custom class. The custom class must conform to the same public API
# as ZMQ::Message.
#
# Creation of a new Socket object can raise an exception. This occurs when the
# +context_ptr+ is null or when the allocation of the 0mq socket within the
# context fails.
#
# begin
# socket = Socket.new(context.pointer, ZMQ::REQ)
# rescue ContextError => e
# # error handling
# end
#
def initialize context_ptr, type, opts = {:receiver_class => ZMQ::Message}
# users may override the classes used for receiving; class must conform to the
# same public API as ZMQ::Message
@receiver_klass = opts[:receiver_class]
context_ptr = context_ptr.pointer if context_ptr.kind_of?(ZMQ::Context)
if context_ptr.nil? || context_ptr.null?
raise ContextError.new 'zmq_socket', 0, ETERM, "Context pointer was null"
else
@socket = LibZMQ.zmq_socket context_ptr, type
if @socket && !@socket.null?
@name = SocketTypeNameMap[type]
else
raise ContextError.new 'zmq_socket', 0, ETERM, "Socket pointer was null"
end
end
@longlong_cache = @int_cache = nil
@more_parts_array = []
@option_lookup = []
populate_option_lookup
define_finalizer
end
# Set the queue options on this socket.
#
# Valid +name+ values that take a numeric +value+ are:
# ZMQ::HWM
# ZMQ::SWAP (version 2 only)
# ZMQ::AFFINITY
# ZMQ::RATE
# ZMQ::RECOVERY_IVL
# ZMQ::MCAST_LOOP (version 2 only)
# ZMQ::LINGER
# ZMQ::RECONNECT_IVL
# ZMQ::BACKLOG
# ZMQ::RECOVER_IVL_MSEC (version 2 only)
# ZMQ::RECONNECT_IVL_MAX (version 3 only)
# ZMQ::MAXMSGSIZE (version 3 only)
# ZMQ::SNDHWM (version 3 only)
# ZMQ::RCVHWM (version 3 only)
# ZMQ::MULTICAST_HOPS (version 3 only)
# ZMQ::RCVTIMEO (version 3 only)
# ZMQ::SNDTIMEO (version 3 only)
#
# Valid +name+ values that take a string +value+ are:
# ZMQ::IDENTITY (version 2/3 only)
# ZMQ::SUBSCRIBE
# ZMQ::UNSUBSCRIBE
#
# Returns 0 when the operation completed successfully.
# Returns -1 when this operation failed.
#
# With a -1 return code, the user must check ZMQ::Util.errno to determine the
# cause.
#
# rc = socket.setsockopt(ZMQ::LINGER, 1_000)
# ZMQ::Util.resultcode_ok?(rc) ? puts("succeeded") : puts("failed")
#
def setsockopt name, value, length = nil
if 1 == @option_lookup[name]
length = 8
pointer = LibC.malloc length
pointer.write_long_long value
elsif 0 == @option_lookup[name]
length = 4
pointer = LibC.malloc length
pointer.write_int value
elsif 2 == @option_lookup[name]
# Strings are treated as pointers by FFI so we'll just pass it through
length ||= value.size
pointer = value
end
rc = LibZMQ.zmq_setsockopt @socket, name, pointer, length
LibC.free(pointer) unless pointer.is_a?(String) || pointer.nil? || pointer.null?
rc
end
# Convenience method for checking on additional message parts.
#
# Equivalent to calling Socket#getsockopt with ZMQ::RCVMORE.
#
# Warning: if the call to #getsockopt fails, this method will return
# false and swallow the error.
#
# message_parts = []
# message = Message.new
# rc = socket.recvmsg(message)
# if ZMQ::Util.resultcode_ok?(rc)
# message_parts << message
# while more_parts?
# message = Message.new
# rc = socket.recvmsg(message)
# message_parts.push(message) if resulcode_ok?(rc)
# end
# end
#
def more_parts?
rc = getsockopt ZMQ::RCVMORE, @more_parts_array
Util.resultcode_ok?(rc) ? @more_parts_array.at(0) : false
end
# Binds the socket to an +address+.
#
# socket.bind("tcp://127.0.0.1:5555")
#
def bind address
LibZMQ.zmq_bind @socket, address
end
# Connects the socket to an +address+.
#
# rc = socket.connect("tcp://127.0.0.1:5555")
#
def connect address
rc = LibZMQ.zmq_connect @socket, address
end
# Closes the socket. Any unprocessed messages in queue are sent or dropped
# depending upon the value of the socket option ZMQ::LINGER.
#
# Returns 0 upon success *or* when the socket has already been closed.
# Returns -1 when the operation fails. Check ZMQ::Util.errno for the error code.
#
# rc = socket.close
# puts("Given socket was invalid!") unless 0 == rc
#
def close
if @socket
remove_finalizer
rc = LibZMQ.zmq_close @socket
@socket = nil
release_cache
rc
else
0
end
end
# Queues the message for transmission. Message is assumed to conform to the
# same public API as #Message.
#
# +flags+ may take two values:
# * 0 (default) - blocking operation
# * ZMQ::DONTWAIT - non-blocking operation
# * ZMQ::SNDMORE - this message is part of a multi-part message
#
# Returns 0 when the message was successfully enqueued.
# Returns -1 under two conditions.
# 1. The message could not be enqueued
# 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
#
# With a -1 return code, the user must check ZMQ::Util.errno to determine the
# cause.
#
def sendmsg message, flags = 0
__sendmsg__(@socket, message.address, flags)
end
# Helper method to make a new #Message instance out of the +string+ passed
# in for transmission.
#
# +flags+ may be ZMQ::DONTWAIT and ZMQ::SNDMORE.
#
# Returns 0 when the message was successfully enqueued.
# Returns -1 under two conditions.
# 1. The message could not be enqueued
# 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
#
# With a -1 return code, the user must check ZMQ::Util.errno to determine the
# cause.
#
def send_string string, flags = 0
message = Message.new string
send_and_close message, flags
end
# Send a sequence of strings as a multipart message out of the +parts+
# passed in for transmission. Every element of +parts+ should be
# a String.
#
# +flags+ may be ZMQ::DONTWAIT.
#
# Returns 0 when the messages were successfully enqueued.
# Returns -1 under two conditions.
# 1. A message could not be enqueued
# 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
#
# With a -1 return code, the user must check ZMQ::Util.errno to determine the
# cause.
#
def send_strings parts, flags = 0
send_multiple(parts, flags, :send_string)
end
# Send a sequence of messages as a multipart message out of the +parts+
# passed in for transmission. Every element of +parts+ should be
# a Message (or subclass).
#
# +flags+ may be ZMQ::DONTWAIT.
#
# Returns 0 when the messages were successfully enqueued.
# Returns -1 under two conditions.
# 1. A message could not be enqueued
# 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
#
# With a -1 return code, the user must check ZMQ::Util.errno to determine the
# cause.
#
def sendmsgs parts, flags = 0
send_multiple(parts, flags, :sendmsg)
end
# Sends a message. This will automatically close the +message+ for both successful
# and failed sends.
#
# Returns 0 when the message was successfully enqueued.
# Returns -1 under two conditions.
# 1. The message could not be enqueued
# 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
#
# With a -1 return code, the user must check ZMQ::Util.errno to determine the
# cause.
#
def send_and_close message, flags = 0
rc = sendmsg message, flags
message.close
rc
end
# Dequeues a message from the underlying queue. By default, this is a blocking operation.
#
# +flags+ may take two values:
# 0 (default) - blocking operation
# ZMQ::DONTWAIT - non-blocking operation
#
# Returns 0 when the message was successfully dequeued.
# Returns -1 under two conditions.
# 1. The message could not be dequeued
# 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
#
# With a -1 return code, the user must check ZMQ::Util.errno to determine the
# cause.
#
# The application code is responsible for handling the +message+ object lifecycle
# when #recv returns an error code.
#
def recvmsg message, flags = 0
#LibZMQ.zmq_recvmsg @socket, message.address, flags
__recvmsg__(@socket, message.address, flags)
end
# Helper method to make a new #Message instance and convert its payload
# to a string.
#
# +flags+ may be ZMQ::DONTWAIT.
#
# Returns 0 when the message was successfully dequeued.
# Returns -1 under two conditions.
# 1. The message could not be dequeued
# 2. When +flags+ is set with ZMQ::DONTWAIT and the socket returned EAGAIN.
#
# With a -1 return code, the user must check ZMQ::Util.errno to determine the
# cause.
#
# The application code is responsible for handling the +message+ object lifecycle
# when #recv returns an error code.
#
def recv_string string, flags = 0
message = @receiver_klass.new
rc = recvmsg message, flags
string.replace(message.copy_out_string) if Util.resultcode_ok?(rc)
message.close
rc
end
# Receive a multipart message as a list of strings.
#
# +flag+ may be ZMQ::DONTWAIT. Any other flag will be
# removed.
#
def recv_strings list, flag = 0
array = []
rc = recvmsgs array, flag
if Util.resultcode_ok?(rc)
array.each do |message|
list << message.copy_out_string
message.close
end
end
rc
end
# Receive a multipart message as an array of objects
# (by default these are instances of Message).
#
# +flag+ may be ZMQ::DONTWAIT. Any other flag will be
# removed.
#
def recvmsgs list, flag = 0
flag = DONTWAIT if dontwait?(flag)
message = @receiver_klass.new
rc = recvmsg message, flag
if Util.resultcode_ok?(rc)
list << message
# check rc *first*; necessary because the call to #more_parts? can reset
# the zmq_errno to a weird value, so the zmq_errno that was set on the
# call to #recv gets lost
while Util.resultcode_ok?(rc) && more_parts?
message = @receiver_klass.new
rc = recvmsg message, flag
if Util.resultcode_ok?(rc)
list << message
else
message.close
list.each { |msg| msg.close }
list.clear
end
end
else
message.close
end
rc
end
# Should only be used for XREQ, XREP, DEALER and ROUTER type sockets. Takes
# a +list+ for receiving the message body parts and a +routing_envelope+
# for receiving the message parts comprising the 0mq routing information.
#
def recv_multipart list, routing_envelope, flag = 0
parts = []
rc = recvmsgs parts, flag
if Util.resultcode_ok?(rc)
routing = true
parts.each do |part|
if routing
routing_envelope << part
routing = part.size > 0
else
list << part
end
end
end
rc
end
# Get the options set on this socket.
#
# +name+ determines the socket option to request
# +array+ should be an empty array; a result of the proper type
# (numeric, string, boolean) will be inserted into
# the first position.
#
# Valid +option_name+ values:
# ZMQ::RCVMORE - true or false
# ZMQ::HWM - integer
# ZMQ::SWAP - integer
# ZMQ::AFFINITY - bitmap in an integer
# ZMQ::IDENTITY - string
# ZMQ::RATE - integer
# ZMQ::RECOVERY_IVL - integer
# ZMQ::SNDBUF - integer
# ZMQ::RCVBUF - integer
# ZMQ::FD - fd in an integer
# ZMQ::EVENTS - bitmap integer
# ZMQ::LINGER - integer measured in milliseconds
# ZMQ::RECONNECT_IVL - integer measured in milliseconds
# ZMQ::BACKLOG - integer
# ZMQ::RECOVER_IVL_MSEC - integer measured in milliseconds
# ZMQ::IPV4ONLY - integer
#
# Returns 0 when the operation completed successfully.
# Returns -1 when this operation failed.
#
# With a -1 return code, the user must check ZMQ::Util.errno to determine the
# cause.
#
# # retrieve high water mark
# array = []
# rc = socket.getsockopt(ZMQ::HWM, array)
# hwm = array.first if ZMQ::Util.resultcode_ok?(rc)
#
def getsockopt name, array
rc = __getsockopt__ name, array
if Util.resultcode_ok?(rc) && (RCVMORE == name)
# convert to boolean
array[0] = 1 == array[0]
end
rc
end
# Convenience method for getting the value of the socket IDENTITY.
#
def identity
array = []
getsockopt IDENTITY, array
array.at(0)
end
# Convenience method for setting the value of the socket IDENTITY.
#
def identity=(value)
setsockopt IDENTITY, value.to_s
end
# Disconnect the socket from the given +endpoint+.
#
def disconnect(endpoint)
LibZMQ.zmq_disconnect(socket, endpoint)
end
# Unbind the socket from the given +endpoint+.
#
def unbind(endpoint)
LibZMQ.zmq_unbind(socket, endpoint)
end
private
def send_multiple(parts, flags, method_name)
if !parts || parts.empty?
-1
else
flags = DONTWAIT if dontwait?(flags)
rc = 0
parts[0..-2].each do |part|
rc = send(method_name, part, (flags | ZMQ::SNDMORE))
break unless Util.resultcode_ok?(rc)
end
Util.resultcode_ok?(rc) ? send(method_name, parts[-1], flags) : rc
end
end
def __getsockopt__ name, array
# a small optimization so we only have to determine the option
# type a single time; gives approx 5% speedup to do it this way.
option_type = @option_lookup[name]
value, length = sockopt_buffers option_type
rc = LibZMQ.zmq_getsockopt @socket, name, value, length
if Util.resultcode_ok?(rc)
array[0] = if 1 == option_type
value.read_long_long
elsif 0 == option_type
value.read_int
elsif 2 == option_type
value.read_string(length.read_int)
end
end
rc
end
# Calls to ZMQ.getsockopt require us to pass in some pointers. We can cache and save those buffers
# for subsequent calls. This is a big perf win for calling RCVMORE which happens quite often.
# Cannot save the buffer for the IDENTITY.
def sockopt_buffers option_type
if 1 == option_type
# int64_t or uint64_t
@longlong_cache ||= alloc_pointer(:int64, 8)
elsif 0 == option_type
# int, 0mq assumes int is 4-bytes
@int_cache ||= alloc_pointer(:int32, 4)
elsif 2 == option_type
# could be a string of up to 255 bytes, so allocate for worst case
alloc_pointer(255, 255)
else
# uh oh, someone passed in an unknown option; return nil
@int_cache ||= alloc_pointer(:int32, 4)
end
end
def release_cache
@longlong_cache = nil
@int_cache = nil
end
def dontwait?(flags)
(DONTWAIT & flags) == DONTWAIT
end
alias :noblock? :dontwait?
def alloc_pointer(kind, length)
pointer = FFI::MemoryPointer.new :size_t
pointer.write_int(length)
[FFI::MemoryPointer.new(kind), pointer]
end
def __sendmsg__(socket, address, flags)
LibZMQ.zmq_sendmsg(socket, address, flags)
end
def __recvmsg__(socket, address, flags)
LibZMQ.zmq_recvmsg(socket, address, flags)
end
def populate_option_lookup
IntegerSocketOptions.each { |option| @option_lookup[option] = 0 }
LongLongSocketOptions.each { |option| @option_lookup[option] = 1 }
StringSocketOptions.each { |option| @option_lookup[option] = 2 }
end
# these finalizer-related methods cannot live in the CommonSocketBehavior
# module; they *must* be in the class definition directly
def define_finalizer
ObjectSpace.define_finalizer(self, self.class.close(@socket, Process.pid))
end
def remove_finalizer
ObjectSpace.undefine_finalizer self
end
def self.close socket, pid
Proc.new { LibZMQ.zmq_close socket if Process.pid == pid }
end
end # Socket
end # module ZMQ
ffi-rzmq-2.0.4/lib/ffi-rzmq/poll.rb 0000644 0000041 0000041 00000011104 12463440532 017074 0 ustar www-data www-data require 'forwardable'
module ZMQ
class Poller
extend Forwardable
def_delegators :@poll_items, :size, :inspect
attr_reader :readables, :writables
def initialize
@poll_items = ZMQ::PollItems.new
@readables = []
@writables = []
end
# Checks each registered socket for selectability based on the poll items'
# registered +events+. Will block for up to +timeout+ milliseconds.
# A millisecond is 1/1000 of a second, so to block for 1 second
# pass the value "1000" to #poll.
#
# Pass "-1" or +:blocking+ for +timeout+ for this call to block
# indefinitely.
#
# This method will return *immediately* when there are no registered
# sockets. In that case, the +timeout+ parameter is not honored. To
# prevent a CPU busy-loop, the caller of this method should detect
# this possible condition (via #size) and throttle the call
# frequency.
#
# Returns 0 when there are no registered sockets that are readable
# or writable.
#
# Return 1 (or greater) to indicate the number of readable or writable
# sockets. These sockets should be processed using the #readables and
# #writables accessors.
#
# Returns -1 when there is an error. Use ZMQ::Util.errno to get the related
# error number.
#
def poll timeout = :blocking
unless @poll_items.empty?
timeout = adjust timeout
items_triggered = LibZMQ.zmq_poll @poll_items.address, @poll_items.size, timeout
update_selectables if Util.resultcode_ok?(items_triggered)
items_triggered
else
0
end
end
# The non-blocking version of #poll. See the #poll description for
# potential exceptions.
#
# May return -1 when an error is encounted. Check ZMQ::Util.errno
# to determine the underlying cause.
#
def poll_nonblock
poll 0
end
# Register the +pollable+ for +events+. This method is idempotent meaning
# it can be called multiple times with the same data and the socket
# will only get registered at most once. Calling multiple times with
# different values for +events+ will OR the event information together.
#
def register pollable, events = ZMQ::POLLIN | ZMQ::POLLOUT
return if pollable.nil? || events.zero?
unless item = @poll_items[pollable]
item = PollItem.from_pollable(pollable)
@poll_items << item
end
item.events |= events
end
# Deregister the +pollable+ for +events+. When there are no events left
# or socket has been closed this also deletes the socket from the poll items.
#
def deregister pollable, events
return unless pollable
item = @poll_items[pollable]
if item && (item.events & events) > 0
item.events ^= events
delete(pollable) if item.events.zero? || item.closed?
true
else
false
end
end
# A helper method to register a +pollable+ as readable events only.
#
def register_readable pollable
register pollable, ZMQ::POLLIN
end
# A helper method to register a +pollable+ for writable events only.
#
def register_writable pollable
register pollable, ZMQ::POLLOUT
end
# A helper method to deregister a +pollable+ for readable events.
#
def deregister_readable pollable
deregister pollable, ZMQ::POLLIN
end
# A helper method to deregister a +pollable+ for writable events.
#
def deregister_writable pollable
deregister pollable, ZMQ::POLLOUT
end
# Deletes the +pollable+ for all subscribed events. Called internally
# when a socket has been deregistered and has no more events
# registered anywhere.
#
# Can also be called directly to remove the socket from the polling
# array.
#
def delete pollable
return false if @poll_items.empty?
@poll_items.delete(pollable)
end
def to_s; inspect; end
private
def update_selectables
@readables.clear
@writables.clear
@poll_items.each do |poll_item|
@readables << poll_item.pollable if poll_item.readable?
@writables << poll_item.pollable if poll_item.writable?
end
end
# Convert the timeout value to something usable by
# the library.
#
# -1 or :blocking should be converted to -1.
#
# Users will pass in values measured as
# milliseconds, so we need to convert that value to
# microseconds for the library.
#
def adjust timeout
if :blocking == timeout || -1 == timeout
-1
else
timeout.to_i
end
end
end
end
ffi-rzmq-2.0.4/lib/ffi-rzmq/exceptions.rb 0000644 0000041 0000041 00000002403 12463440532 020311 0 ustar www-data www-data
module ZMQ
class ZeroMQError < StandardError
attr_reader :source, :result_code, :error_code, :message
def initialize source, result_code, error_code, message
@source = source
@result_code = result_code
@error_code = error_code
@message = "msg [#{message}], error code [#{error_code}], rc [#{result_code}]"
super message
end
end # call ZeroMQError
class NotSupportedError < ZeroMQError
end
class ContextError < ZeroMQError
# True when the exception was raised due to the library
# returning EINVAL.
#
# Occurs when he number of app_threads requested is less
# than one, or the number of io_threads requested is
# negative.
#
def einval?() EINVAL == @error_code; end
# True when the exception was raised due to the library
# returning ETERM.
#
# The associated context was terminated.
#
def eterm?() ETERM == @error_code; end
end # class ContextError
class MessageError < ZeroMQError
# True when the exception was raised due to the library
# returning ENOMEM.
#
# Only ever raised by the #Message class when it fails
# to allocate sufficient memory to send a message.
#
def enomem?() ENOMEM == @error_code; end
end
end # module ZMQ
ffi-rzmq-2.0.4/lib/ffi-rzmq/util.rb 0000644 0000041 0000041 00000006637 12463440532 017122 0 ustar www-data www-data
module ZMQ
# General utility methods.
#
class Util
# Generate and return a CURVE public/private keypair
#
# Raises an error if ZeroMQ is not configured for CURVE connections.
# Install libsodium if this is the case.
def self.curve_keypair
public_key = FFI::MemoryPointer.from_string(' ' * 41)
private_key = FFI::MemoryPointer.from_string(' ' * 41)
rc = LibZMQ.zmq_curve_keypair public_key, private_key
if rc < 0
raise NotSupportedError.new "zmq_curve_keypair" , rc, ZMQ::Util.errno,
"Rebuild zeromq with libsodium to enable CURVE security options."
end
[public_key.read_string, private_key.read_string]
end
# Returns true when +rc+ is greater than or equal to 0, false otherwise.
#
# We use the >= test because zmq_poll() returns the number of sockets
# that had a read or write event triggered. So, a >= 0 result means
# it succeeded.
#
def self.resultcode_ok? rc
rc >= 0
end
# Returns the +errno+ as set by the libzmq library.
#
def self.errno
LibZMQ.zmq_errno
end
# Returns a string corresponding to the currently set #errno. These
# error strings are defined by libzmq.
#
def self.error_string
LibZMQ.zmq_strerror(errno).read_string
end
# Attempts to bind to a random tcp port on +host+ up to +max_tries+
# times. Returns the port number upon success or nil upon failure.
#
def self.bind_to_random_tcp_port host = '127.0.0.1', max_tries = 500
tries = 0
rc = -1
while !resultcode_ok?(rc) && tries < max_tries
tries += 1
random = random_port
rc = socket.bind "tcp://#{host}:#{random}"
end
resultcode_ok?(rc) ? random : nil
end
# :doc:
# Called to verify whether there were any errors during
# operation. If any are found, raise the appropriate #ZeroMQError.
#
# When no error is found, this method returns +true+ which is behavior
# used internally by #send and #recv.
#
def self.error_check source, result_code
if -1 == result_code
raise_error source, result_code
end
# used by Socket::send/recv, ignored by others
true
end
private
# generate a random port between 10_000 and 65534
def self.random_port
rand(55534) + 10_000
end
def self.raise_error source, result_code
if context_error?(source)
raise ContextError.new source, result_code, ZMQ::Util.errno, ZMQ::Util.error_string
elsif message_error?(source)
raise MessageError.new source, result_code, ZMQ::Util.errno, ZMQ::Util.error_string
else
raise ZeroMQError.new source, result_code, -1,
"Source [#{source}] does not match any zmq_* strings, rc [#{result_code}], errno [#{ZMQ::Util.errno}], error_string [#{ZMQ::Util.error_string}]"
end
end
def self.eagain?
EAGAIN == ZMQ::Util.errno
end
def self.context_error?(source)
'zmq_ctx_new' == source ||
'zmq_ctx_set' == source ||
'zmq_ctx_get' == source ||
'zmq_ctx_destory' == source ||
'zmq_ctx_set_monitor' == source
end
def self.message_error?(source)
['zmq_msg_init', 'zmq_msg_init_data', 'zmq_msg_copy', 'zmq_msg_move', 'zmq_msg_close', 'zmq_msg_get',
'zmq_msg_more', 'zmq_msg_recv', 'zmq_msg_send', 'zmq_msg_set'].include?(source)
end
end # module Util
end # module ZMQ
ffi-rzmq-2.0.4/lib/ffi-rzmq.rb 0000644 0000041 0000041 00000003653 12463440532 016140 0 ustar www-data www-data module ZMQ
# :stopdoc:
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
# :startdoc:
# Returns the version string for the library.
#
def self.version
@version ||= ZMQ::VERSION
end
# Returns the library path for the module. If any arguments are given,
# they will be joined to the end of the libray path using
# File.join.
#
def self.libpath( *args, &block )
rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
if block
begin
$LOAD_PATH.unshift LIBPATH
rv = block.call
ensure
$LOAD_PATH.shift
end
end
return rv
end
# Returns the lpath for the module. If any arguments are given,
# they will be joined to the end of the path using
# File.join.
#
def self.path( *args, &block )
rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
if block
begin
$LOAD_PATH.unshift PATH
rv = block.call
ensure
$LOAD_PATH.shift
end
end
return rv
end
# Utility method used to require all files ending in .rb that lie in the
# directory below this file that has the same name as the filename passed
# in. Optionally, a specific _directory_ name can be passed in such that
# the _filename_ does not have to be equivalent to the directory.
#
def self.require_all_libs_relative_to( fname, dir = nil )
dir ||= ::File.basename(fname, '.*')
search_me = ::File.expand_path(
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
Dir.glob(search_me).sort.each {|rb| require rb}
end
end # module ZMQ
# some code is conditionalized based upon what ruby engine we are
# executing
require 'ffi-rzmq-core'
# the order of files is important
%w(util exceptions context message socket poll_items poll_item poll device version).each do |file|
require ZMQ.libpath(['ffi-rzmq', file])
end
ffi-rzmq-2.0.4/lib/io_extensions.rb 0000644 0000041 0000041 00000000563 12463440532 017270 0 ustar www-data www-data class IO
if defined? JRUBY_VERSION
require 'jruby'
def posix_fileno
case self
when STDIN, $stdin
0
when STDOUT, $stdout
1
when STDERR, $stderr
2
else
JRuby.reference(self).getOpenFile.getMainStream.getDescriptor.getChannel.getFDVal
end
end
else
alias :posix_fileno :fileno
end
end
ffi-rzmq-2.0.4/.bnsignore 0000644 0000041 0000041 00000000711 12463440532 015271 0 ustar www-data www-data # The list of files that should be ignored by Mr Bones.
# Lines that start with '#' are comments.
#
# A .gitignore file can be used instead by setting it as the ignore
# file in your Rakefile:
#
# Bones {
# ignore_file '.gitignore'
# }
#
# For a project with a C extension, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
announcement.txt
coverage
doc
pkg
*.tmproj
*.gem
*.rbc
*.html
ffi-rzmq-2.0.4/metadata.yml 0000644 0000041 0000041 00000007276 12463440532 015622 0 ustar www-data www-data --- !ruby/object:Gem::Specification
name: ffi-rzmq
version: !ruby/object:Gem::Version
version: 2.0.4
platform: ruby
authors:
- Chuck Remes
autorequire:
bindir: bin
cert_chain: []
date: 2015-01-28 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: ffi-rzmq-core
requirement: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: 1.0.1
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: 1.0.1
- !ruby/object:Gem::Dependency
name: rspec
requirement: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '2.14'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '2.14'
- !ruby/object:Gem::Dependency
name: rake
requirement: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
description: |-
This gem wraps the ZeroMQ networking library using the ruby FFI (foreign
function interface). It's a pure ruby wrapper so this gem can be loaded
and run by any ruby runtime that supports FFI. That's all of the major ones - MRI, Rubinius and JRuby.
email:
- git@chuckremes.com
executables: []
extensions: []
extra_rdoc_files: []
files:
- ".bnsignore"
- ".gitignore"
- ".travis.yml"
- AUTHORS.txt
- Gemfile
- History.txt
- README.rdoc
- Rakefile
- examples/README.rdoc
- examples/latency_measurement.rb
- examples/local_lat.rb
- examples/local_lat_poll.rb
- examples/local_throughput.rb
- examples/pub.rb
- examples/publish_subscribe.rb
- examples/remote_lat.rb
- examples/remote_throughput.rb
- examples/repreq_over_curve.rb
- examples/reqrep_poll.rb
- examples/request_response.rb
- examples/sub.rb
- examples/throughput_measurement.rb
- examples/xreqxrep_poll.rb
- ext/README
- ffi-rzmq.gemspec
- lib/ffi-rzmq.rb
- lib/ffi-rzmq/context.rb
- lib/ffi-rzmq/device.rb
- lib/ffi-rzmq/exceptions.rb
- lib/ffi-rzmq/message.rb
- lib/ffi-rzmq/poll.rb
- lib/ffi-rzmq/poll_item.rb
- lib/ffi-rzmq/poll_items.rb
- lib/ffi-rzmq/socket.rb
- lib/ffi-rzmq/util.rb
- lib/ffi-rzmq/version.rb
- lib/io_extensions.rb
- spec/context_spec.rb
- spec/device_spec.rb
- spec/message_spec.rb
- spec/multipart_spec.rb
- spec/nonblocking_recv_spec.rb
- spec/poll_spec.rb
- spec/pushpull_spec.rb
- spec/reqrep_spec.rb
- spec/socket_spec.rb
- spec/spec_helper.rb
- spec/support/test.crt
- spec/support/test.key
- spec/util_spec.rb
homepage: http://github.com/chuckremes/ffi-rzmq
licenses:
- MIT
metadata: {}
post_install_message:
rdoc_options: []
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
requirements: []
rubyforge_project: ffi-rzmq
rubygems_version: 2.2.2
signing_key:
specification_version: 4
summary: This gem wraps the ZeroMQ (0mq) networking library using Ruby FFI (foreign
function interface).
test_files:
- spec/context_spec.rb
- spec/device_spec.rb
- spec/message_spec.rb
- spec/multipart_spec.rb
- spec/nonblocking_recv_spec.rb
- spec/poll_spec.rb
- spec/pushpull_spec.rb
- spec/reqrep_spec.rb
- spec/socket_spec.rb
- spec/spec_helper.rb
- spec/support/test.crt
- spec/support/test.key
- spec/util_spec.rb
ffi-rzmq-2.0.4/ffi-rzmq.gemspec 0000644 0000041 0000041 00000002177 12463440532 016412 0 ustar www-data www-data # -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "ffi-rzmq/version"
Gem::Specification.new do |s|
s.name = "ffi-rzmq"
s.version = ZMQ::VERSION
s.authors = ["Chuck Remes"]
s.email = ["git@chuckremes.com"]
s.homepage = "http://github.com/chuckremes/ffi-rzmq"
s.summary = %q{This gem wraps the ZeroMQ (0mq) networking library using Ruby FFI (foreign function interface).}
s.description = %q{This gem wraps the ZeroMQ networking library using the ruby FFI (foreign
function interface). It's a pure ruby wrapper so this gem can be loaded
and run by any ruby runtime that supports FFI. That's all of the major ones - MRI, Rubinius and JRuby.}
s.license = 'MIT'
s.rubyforge_project = "ffi-rzmq"
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
s.add_runtime_dependency "ffi-rzmq-core", [">= 1.0.1"]
s.add_development_dependency "rspec", ["~> 2.14"]
s.add_development_dependency "rake"
end
ffi-rzmq-2.0.4/AUTHORS.txt 0000644 0000041 0000041 00000001106 12463440532 015167 0 ustar www-data www-data Chuck Remes, github: chuckremes
Andrew Cholakian, github: andrewvc
Ar Vicco, github: arvicco
Ben Mabey, github: bmabey
Julien Ammous, github: schmurfy
Zachary Belzer, github: zbelzer
Cory Forsyth, github: bantic
Stefan Kaes, github: skaes
Dmitry Ustalov, github: eveel
Patrik Sundberg, github: sundbp
Pawel Pacana, github: pawelpacana
Brian Ford, github: brixen
Nilson Santos F. Jr., github: nilsonfsj
Simon Chiang , github: thinkerbot
Harlow Ward, github: harlow
Paul Chechetin, github: paulche
Michael Zaccari, github: mzaccari
Jason Roelofs, github: jasonroelofs ffi-rzmq-2.0.4/.gitignore 0000644 0000041 0000041 00000000061 12463440532 015270 0 ustar www-data www-data *.gem
.bundle
Gemfile.lock
pkg/*
*.rbc
.redcar/
ffi-rzmq-2.0.4/ext/ 0000755 0000041 0000041 00000000000 12463440532 014103 5 ustar www-data www-data ffi-rzmq-2.0.4/ext/README 0000644 0000041 0000041 00000000344 12463440532 014764 0 ustar www-data www-data To avoid loading a system-wide 0mq library, place
the C libraries here. This let's you run your Ruby
code with a 0mq C library build that is different
from the system-wide one. This can be handy for
rolling upgrades or testing.