ffi-rzmq 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|