ffi-rzmq 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +92 -0
- data/README.rdoc +162 -0
- data/Rakefile +19 -0
- data/examples/local_lat.rb +43 -0
- data/examples/local_lat_zerocopy.rb +24 -0
- data/examples/publish_subscribe.rb +52 -0
- data/examples/remote_lat.rb +53 -0
- data/examples/remote_lat_zerocopy.rb +35 -0
- data/examples/reqrep_poll.rb +49 -0
- data/examples/request_response.rb +23 -0
- data/ffi-rzmq.gemspec +43 -0
- data/lib/ffi-rzmq.rb +71 -0
- data/lib/ffi-rzmq/context.rb +99 -0
- data/lib/ffi-rzmq/exceptions.rb +145 -0
- data/lib/ffi-rzmq/message.rb +210 -0
- data/lib/ffi-rzmq/poll.rb +186 -0
- data/lib/ffi-rzmq/poll_items.rb +98 -0
- data/lib/ffi-rzmq/socket.rb +344 -0
- data/lib/ffi-rzmq/wrapper.rb +120 -0
- data/lib/ffi-rzmq/zmq.rb +147 -0
- data/spec/context_spec.rb +96 -0
- data/spec/reqrep_spec.rb +56 -0
- data/spec/socket_spec.rb +111 -0
- data/spec/spec_helper.rb +38 -0
- data/version.txt +1 -0
- metadata +113 -0
data/lib/ffi-rzmq/zmq.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
|
2
|
+
module ZMQ
|
3
|
+
|
4
|
+
# Socket types
|
5
|
+
PAIR = 0
|
6
|
+
PUB = 1
|
7
|
+
SUB = 2
|
8
|
+
REQ = 3
|
9
|
+
REP = 4
|
10
|
+
XREQ = 5
|
11
|
+
XREP = 6
|
12
|
+
UPSTREAM = 7
|
13
|
+
DOWNSTREAM = 8
|
14
|
+
|
15
|
+
# Socket options
|
16
|
+
HWM = 1
|
17
|
+
SWAP = 3
|
18
|
+
AFFINITY = 4
|
19
|
+
IDENTITY = 5
|
20
|
+
SUBSCRIBE = 6
|
21
|
+
UNSUBSCRIBE = 7
|
22
|
+
RATE = 8
|
23
|
+
RECOVERY_IVL = 9
|
24
|
+
MCAST_LOOP = 10
|
25
|
+
SNDBUF = 11
|
26
|
+
RCVBUF = 12
|
27
|
+
RCVMORE = 13
|
28
|
+
|
29
|
+
# Send/recv options
|
30
|
+
NOBLOCK = 1
|
31
|
+
SNDMORE = 2
|
32
|
+
|
33
|
+
# I/O multiplexing
|
34
|
+
|
35
|
+
POLL = 1
|
36
|
+
POLLIN = 1
|
37
|
+
POLLOUT = 2
|
38
|
+
POLLERR = 4
|
39
|
+
|
40
|
+
# Socket errors
|
41
|
+
EAGAIN = Errno::EAGAIN::Errno
|
42
|
+
EINVAL = Errno::EINVAL::Errno
|
43
|
+
ENOMEM = Errno::ENOMEM::Errno
|
44
|
+
ENODEV = Errno::ENODEV::Errno
|
45
|
+
EFAULT = Errno::EFAULT::Errno
|
46
|
+
|
47
|
+
# ZMQ errors
|
48
|
+
HAUSNUMERO = 156384712
|
49
|
+
EMTHREAD = (HAUSNUMERO + 50)
|
50
|
+
EFSM = (HAUSNUMERO + 51)
|
51
|
+
ENOCOMPATPROTO = (HAUSNUMERO + 52)
|
52
|
+
ETERM = (HAUSNUMERO + 53)
|
53
|
+
|
54
|
+
# Rescue unknown constants and use the ZeroMQ defined values
|
55
|
+
# Usually only happens on Windows though some don't resolve on
|
56
|
+
# OSX too (ENOTSUP)
|
57
|
+
ENOTSUP = Errno::ENOTSUP::Errno rescue (HAUSNUMERO + 1)
|
58
|
+
EPROTONOSUPPORT = Errno::EPROTONOSUPPORT::Errno rescue (HAUSNUMERO + 2)
|
59
|
+
ENOBUFS = Errno::ENOBUFS::Errno rescue (HAUSNUMERO + 3)
|
60
|
+
ENETDOWN = Errno::ENETDOWN::Errno rescue (HAUSNUMERO + 4)
|
61
|
+
EADDRINUSE = Errno::EADDRINUSE::Errno rescue (HAUSNUMERO + 5)
|
62
|
+
EADDRNOTAVAIL = Errno::EADDRNOTAVAIL::Errno rescue (HAUSNUMERO + 6)
|
63
|
+
ECONNREFUSED = Errno::ECONNREFUSED::Errno rescue (HAUSNUMERO + 7)
|
64
|
+
EINPROGRESS = Errno::EINPROGRESS::Errno rescue (HAUSNUMERO + 8)
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
# These methods don't belong to any specific context. They get included
|
69
|
+
# in the #Context, #Socket and #Poller classes.
|
70
|
+
#
|
71
|
+
module Util
|
72
|
+
|
73
|
+
# Returns the +errno+ as set by the libzmq library.
|
74
|
+
#
|
75
|
+
def errno
|
76
|
+
LibZMQ.zmq_errno
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns a string corresponding to the currently set #errno. These
|
80
|
+
# error strings are defined by libzmq.
|
81
|
+
#
|
82
|
+
def error_string
|
83
|
+
LibZMQ.zmq_strerror(errno).read_string
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns an array of the form [major, minor, patch] to represent the
|
87
|
+
# version of libzmq.
|
88
|
+
#
|
89
|
+
def version
|
90
|
+
major = FFI::MemoryPointer.new :int
|
91
|
+
minor = FFI::MemoryPointer.new :int
|
92
|
+
patch = FFI::MemoryPointer.new :int
|
93
|
+
LibZMQ.zmq_version major, minor, patch
|
94
|
+
[major.read_int, minor.read_int, patch.read_int]
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# :doc:
|
101
|
+
# Called by most library methods to verify there were no errors during
|
102
|
+
# operation. If any are found, raise the appropriate #ZeroMQError.
|
103
|
+
#
|
104
|
+
# When no error is found, this method returns +true+ which is behavior
|
105
|
+
# used internally by #send and #recv.
|
106
|
+
#
|
107
|
+
def error_check source, result_code
|
108
|
+
unless result_code.zero?
|
109
|
+
raise_error source, result_code
|
110
|
+
end
|
111
|
+
|
112
|
+
# used by Socket::send/recv, ignored by others
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
# :doc:
|
117
|
+
# Only called on sockets in non-blocking mode.
|
118
|
+
#
|
119
|
+
# Checks the #errno and +result_code+ values for a failed non-blocking
|
120
|
+
# send/recv. True only when #errno is EGAIN and +result_code+ is non-zero.
|
121
|
+
#
|
122
|
+
def error_check_nonblock result_code
|
123
|
+
queue_operation = eagain? && !result_code.zero? ? false : true
|
124
|
+
queue_operation
|
125
|
+
end
|
126
|
+
|
127
|
+
def raise_error source, result_code
|
128
|
+
case source
|
129
|
+
when ZMQ_SOCKET_STR, ZMQ_SETSOCKOPT_STR, ZMQ_GETSOCKOPT_STR, ZMQ_BIND_STR, ZMQ_CONNECT_STR, ZMQ_SEND_STR, ZMQ_RECV_STR
|
130
|
+
raise SocketError.new source, result_code, errno, error_string
|
131
|
+
when ZMQ_INIT_STR, ZMQ_TERM_STR
|
132
|
+
raise ContextError.new source, result_code, errno, error_string
|
133
|
+
when ZMQ_POLL_STR
|
134
|
+
raise PollError.new source, result_code, errno, error_string
|
135
|
+
else
|
136
|
+
raise ZeroMQError.new source, result_code, -1,
|
137
|
+
"Source [#{source}] does not match any zmq_* strings, rc [#{result_code}], errno [#{errno}], error_string [#{error_string}]"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def eagain?
|
142
|
+
EAGAIN == errno
|
143
|
+
end
|
144
|
+
|
145
|
+
end # module Util
|
146
|
+
|
147
|
+
end # module ZMQ
|
@@ -0,0 +1,96 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
3
|
+
|
4
|
+
module ZMQ
|
5
|
+
|
6
|
+
|
7
|
+
describe Context do
|
8
|
+
|
9
|
+
context "when initializing" do
|
10
|
+
include APIHelper
|
11
|
+
|
12
|
+
it "should raise an error for negative io threads" do
|
13
|
+
lambda { Context.new(-1) }.should raise_exception(ZMQ::ContextError)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should set the :pointer accessor to non-nil" do
|
17
|
+
ctx = Context.new 1
|
18
|
+
ctx.pointer.should_not be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should set the :context accessor to non-nil" do
|
22
|
+
ctx = Context.new 1
|
23
|
+
ctx.context.should_not be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should set the :pointer and :context accessors to the same value" do
|
27
|
+
ctx = Context.new 1
|
28
|
+
ctx.pointer.should == ctx.context
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should define a finalizer on this object" do
|
32
|
+
ObjectSpace.should_receive(:define_finalizer)
|
33
|
+
ctx = Context.new 1
|
34
|
+
end
|
35
|
+
end # context initializing
|
36
|
+
|
37
|
+
|
38
|
+
context "when terminating" do
|
39
|
+
it "should call zmq_term to terminate the library's context" do
|
40
|
+
ctx = Context.new 1
|
41
|
+
LibZMQ.should_receive(:zmq_term).with(ctx.pointer).and_return(0)
|
42
|
+
ctx.terminate
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should raise an exception when it fails" do
|
46
|
+
ctx = Context.new 1
|
47
|
+
LibZMQ.stub(:zmq_term => 1)
|
48
|
+
lambda { ctx.terminate }.should raise_error(ZMQ::ContextError)
|
49
|
+
end
|
50
|
+
end # context terminate
|
51
|
+
|
52
|
+
|
53
|
+
context "when allocating a socket" do
|
54
|
+
it "should return a ZMQ::Socket" do
|
55
|
+
ctx = Context.new 1
|
56
|
+
ctx.socket(ZMQ::REQ).should be_kind_of(ZMQ::Socket)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should raise an exception when allocation fails" do
|
60
|
+
ctx = Context.new 1
|
61
|
+
Socket.stub(:new => nil)
|
62
|
+
lambda { ctx.socket(ZMQ::REQ) }.should raise_error(ZMQ::SocketError)
|
63
|
+
end
|
64
|
+
end # context socket
|
65
|
+
|
66
|
+
|
67
|
+
# context "when allocating a device" do
|
68
|
+
# let(:ctx) { Context.new 1 }
|
69
|
+
# let(:sock1) { ctx.socket ZMQ::REQ }
|
70
|
+
# let(:sock2) { ctx.socket ZMQ::REP }
|
71
|
+
#
|
72
|
+
# it "should return a ZMQ::Forwarder" do
|
73
|
+
# device = ctx.device ZMQ::FORWARDER, sock1, sock2
|
74
|
+
# device.should be_kind_of(ZMQ::Forwarder)
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# it "should return a ZMQ::Queue" do
|
78
|
+
# device = ctx.device ZMQ::QUEUE, sock1, sock2
|
79
|
+
# device.should be_kind_of(ZMQ::Queue)
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# it "should return a ZMQ::Streamer" do
|
83
|
+
# device = ctx.device ZMQ::STREAMER, sock1, sock2
|
84
|
+
# device.should be_kind_of(ZMQ::Streamer)
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# it "should raise an exception when the requested device is unknown" do
|
88
|
+
# lambda { ctx.device(-1, sock1, sock2) }.should raise_error(ZMQ::DeviceError)
|
89
|
+
# end
|
90
|
+
# end # context device
|
91
|
+
|
92
|
+
|
93
|
+
end # describe Context
|
94
|
+
|
95
|
+
|
96
|
+
end # module ZMQ
|
data/spec/reqrep_spec.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
3
|
+
|
4
|
+
module ZMQ
|
5
|
+
|
6
|
+
|
7
|
+
describe Context do
|
8
|
+
|
9
|
+
context "when running ping pong" do
|
10
|
+
include APIHelper
|
11
|
+
|
12
|
+
let(:string) { "booga-booga" }
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
context = ZMQ::Context.new 1
|
16
|
+
@ping = context.socket ZMQ::REQ
|
17
|
+
@pong = context.socket ZMQ::REP
|
18
|
+
link = "tcp://127.0.0.1:#{random_port}"
|
19
|
+
@pong.bind link
|
20
|
+
@ping.connect link
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should receive an exact string copy of the string message sent" do
|
24
|
+
@ping.send_string string
|
25
|
+
received_message = @pong.recv_string
|
26
|
+
|
27
|
+
received_message.should == string
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should receive an exact copy of the sent message using Message objects directly" do
|
31
|
+
sent_message = Message.new string
|
32
|
+
received_message = Message.new
|
33
|
+
|
34
|
+
@ping.send sent_message
|
35
|
+
@pong.recv received_message
|
36
|
+
|
37
|
+
received_message.copy_out_string.should == string
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should receive an exact copy of the sent message using Message objects directly in non-blocking mode" do
|
41
|
+
sent_message = Message.new string
|
42
|
+
received_message = Message.new
|
43
|
+
|
44
|
+
@ping.send sent_message, ZMQ::NOBLOCK
|
45
|
+
sleep 0.001 # give it time for delivery
|
46
|
+
@pong.recv received_message, ZMQ::NOBLOCK
|
47
|
+
|
48
|
+
received_message.copy_out_string.should == string
|
49
|
+
end
|
50
|
+
end # context ping-pong
|
51
|
+
|
52
|
+
|
53
|
+
end # describe
|
54
|
+
|
55
|
+
|
56
|
+
end # module ZMQ
|
data/spec/socket_spec.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), %w[spec_helper])
|
3
|
+
|
4
|
+
module ZMQ
|
5
|
+
|
6
|
+
|
7
|
+
describe Socket do
|
8
|
+
|
9
|
+
context "when initializing" do
|
10
|
+
|
11
|
+
let(:ctx) { Context.new 1 }
|
12
|
+
|
13
|
+
it "should raise an error for a nil context" do
|
14
|
+
lambda { Socket.new(FFI::Pointer::NULL, ZMQ::REQ) }.should raise_exception(ZMQ::ContextError)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should not raise an error for a ZMQ::REQ socket type" do
|
18
|
+
lambda { Socket.new(ctx.pointer, ZMQ::REQ) }.should_not raise_error
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not raise an error for a ZMQ::REP socket type" do
|
22
|
+
lambda { Socket.new(ctx.pointer, ZMQ::REP) }.should_not raise_error
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not raise an error for a ZMQ::PUB socket type" do
|
26
|
+
lambda { Socket.new(ctx.pointer, ZMQ::PUB) }.should_not raise_error
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should not raise an error for a ZMQ::SUB socket type" do
|
30
|
+
lambda { Socket.new(ctx.pointer, ZMQ::SUB) }.should_not raise_error
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should not raise an error for a ZMQ::PAIR socket type" do
|
34
|
+
lambda { Socket.new(ctx.pointer, ZMQ::PAIR) }.should_not raise_error
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should not raise an error for a ZMQ::XREQ socket type" do
|
38
|
+
lambda { Socket.new(ctx.pointer, ZMQ::XREQ) }.should_not raise_error
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not raise an error for a ZMQ::XREP socket type" do
|
42
|
+
lambda { Socket.new(ctx.pointer, ZMQ::XREP) }.should_not raise_error
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should not raise an error for a ZMQ::DOWNSTREAM socket type" do
|
46
|
+
lambda { Socket.new(ctx.pointer, ZMQ::DOWNSTREAM) }.should_not raise_error
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should not raise an error for a ZMQ::UPSTREAM socket type" do
|
50
|
+
lambda { Socket.new(ctx.pointer, ZMQ::UPSTREAM) }.should_not raise_error
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should raise an error for an unknown socket type" do
|
54
|
+
lambda { Socket.new(ctx.pointer, 80) }.should raise_exception(ZMQ::SocketError)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should set the :socket accessor to non-nil" do
|
58
|
+
sock = Socket.new(Context.new(1).pointer, ZMQ::REQ)
|
59
|
+
sock.socket.should_not be_nil
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should define a finalizer on this object" do
|
63
|
+
ObjectSpace.should_receive(:define_finalizer)
|
64
|
+
ctx = Context.new 1
|
65
|
+
end
|
66
|
+
end # context initializing
|
67
|
+
|
68
|
+
|
69
|
+
context "identity=" do
|
70
|
+
it "should raise an exception for identities in excess of 255 bytes" do
|
71
|
+
ctx = Context.new 1
|
72
|
+
sock = Socket.new ctx.pointer, ZMQ::REQ
|
73
|
+
|
74
|
+
lambda { sock.identity = ('a' * 256) }.should raise_exception(ZMQ::SocketError)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should raise an exception for identities of length 0" do
|
78
|
+
ctx = Context.new 1
|
79
|
+
sock = Socket.new ctx.pointer, ZMQ::REQ
|
80
|
+
|
81
|
+
lambda { sock.identity = '' }.should raise_exception(ZMQ::SocketError)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should NOT raise an exception for identities of 1 byte" do
|
85
|
+
ctx = Context.new 1
|
86
|
+
sock = Socket.new ctx.pointer, ZMQ::REQ
|
87
|
+
|
88
|
+
lambda { sock.identity = 'a' }.should_not raise_exception(ZMQ::SocketError)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should NOT raise an exception for identities of 255 bytes" do
|
92
|
+
ctx = Context.new 1
|
93
|
+
sock = Socket.new ctx.pointer, ZMQ::REQ
|
94
|
+
|
95
|
+
lambda { sock.identity = ('a' * 255) }.should_not raise_exception(ZMQ::SocketError)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should convert numeric identities to strings" do
|
99
|
+
ctx = Context.new 1
|
100
|
+
sock = Socket.new ctx.pointer, ZMQ::REQ
|
101
|
+
|
102
|
+
sock.identity = 7
|
103
|
+
sock.identity.should == '7'
|
104
|
+
end
|
105
|
+
end # context identity=
|
106
|
+
|
107
|
+
|
108
|
+
end # describe Socket
|
109
|
+
|
110
|
+
|
111
|
+
end # module ZMQ
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# To run these specs using rake, make sure the 'bones' and 'bones-extras'
|
2
|
+
# gems are installed. Then execute 'rake spec' from the main directory
|
3
|
+
# to run all specs.
|
4
|
+
|
5
|
+
require File.expand_path(
|
6
|
+
File.join(File.dirname(__FILE__), %w[.. lib ffi-rzmq]))
|
7
|
+
|
8
|
+
# turns off all warnings; added so I don't have to see the warnings
|
9
|
+
# for included libraries like FFI.
|
10
|
+
$VERBOSE = false
|
11
|
+
|
12
|
+
Spec::Runner.configure do |config|
|
13
|
+
# == Mock Framework
|
14
|
+
#
|
15
|
+
# RSpec uses it's own mocking framework by default. If you prefer to
|
16
|
+
# use mocha, flexmock or RR, uncomment the appropriate line:
|
17
|
+
#
|
18
|
+
# config.mock_with :mocha
|
19
|
+
# config.mock_with :flexmock
|
20
|
+
# config.mock_with :rr
|
21
|
+
end
|
22
|
+
|
23
|
+
module APIHelper
|
24
|
+
def stub_libzmq
|
25
|
+
@err_str_mock = mock("error string")
|
26
|
+
|
27
|
+
LibZMQ.stub!(
|
28
|
+
:zmq_init => 0,
|
29
|
+
:zmq_errno => 0,
|
30
|
+
:zmq_sterror => @err_str_mock
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
# generate a random port between 10_000 and 65534
|
35
|
+
def random_port
|
36
|
+
rand(55534) + 10_000
|
37
|
+
end
|
38
|
+
end
|