ffi-rxs 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,105 @@
1
+
2
+ module XS
3
+
4
+ # These methods don't belong to any specific class. They get included
5
+ # in the #Context, #Socket and #Poller classes.
6
+ #
7
+ module Util
8
+
9
+ # Returns true when +rc+ is greater than or equal to 0, false otherwise.
10
+ #
11
+ # We use the >= test because xs_poll() returns the number of sockets
12
+ # that had a read or write event triggered. So, a >= 0 result means
13
+ # it succeeded.
14
+ #
15
+ def self.resultcode_ok? rc
16
+ rc >= 0
17
+ end
18
+
19
+ # Returns the +errno+ as set by the libxs library.
20
+ #
21
+ def self.errno
22
+ LibXS.xs_errno
23
+ end
24
+
25
+ # Returns a string corresponding to the currently set #errno. These
26
+ # error strings are defined by libxs.
27
+ #
28
+ def self.error_string
29
+ LibXS.xs_strerror(errno).read_string
30
+ end
31
+
32
+ # Returns an array of the form [major, minor, patch] to represent the
33
+ # version of libxs.
34
+ #
35
+ # Class method! Invoke as: XS::Util.version
36
+ #
37
+ def self.version
38
+ major = FFI::MemoryPointer.new :int
39
+ minor = FFI::MemoryPointer.new :int
40
+ patch = FFI::MemoryPointer.new :int
41
+ LibXS.xs_version major, minor, patch
42
+ [major.read_int, minor.read_int, patch.read_int]
43
+ end
44
+
45
+ # Attempts to bind to a random tcp port on +host+ up to +max_tries+
46
+ # times. Returns the port number upon success or nil upon failure.
47
+ #
48
+ def self.bind_to_random_tcp_port host = '127.0.0.1', max_tries = 500
49
+ tries = 0
50
+ rc = -1
51
+
52
+ while !resultcode_ok?(rc) && tries < max_tries
53
+ tries += 1
54
+ random = random_port
55
+ rc = socket.bind "tcp://#{host}:#{random}"
56
+ end
57
+
58
+ resultcode_ok?(rc) ? random : nil
59
+ end
60
+
61
+
62
+ private
63
+
64
+ # generate a random port between 10_000 and 65534
65
+ def self.random_port
66
+ rand(55534) + 10_000
67
+ end
68
+
69
+ # :doc:
70
+ # Called by most library methods to verify there were no errors during
71
+ # operation. If any are found, raise the appropriate #XSError.
72
+ #
73
+ # When no error is found, this method returns +true+ which is behavior
74
+ # used internally by #send and #recv.
75
+ #
76
+ def error_check source, result_code
77
+ if -1 == result_code
78
+ raise_error source, result_code
79
+ end
80
+
81
+ # used by Socket::send/recv, ignored by others
82
+ true
83
+ end
84
+
85
+ def raise_error source, result_code
86
+ if 'xs_init' == source || 'xs_socket' == source
87
+ raise ContextError.new source, result_code, XS::Util.errno, XS::Util.error_string
88
+
89
+ elsif ['xs_msg_init', 'xs_msg_init_data', 'xs_msg_copy', 'xs_msg_move'].include?(source)
90
+ raise MessageError.new source, result_code, XS::Util.errno, XS::Util.error_string
91
+
92
+ else
93
+ puts "else"
94
+ raise XSError.new source, result_code, -1,
95
+ "Source [#{source}] does not match any xs_* strings, rc [#{result_code}], errno [#{XS::Util.errno}], error_string [#{XS::Util.error_string}]"
96
+ end
97
+ end
98
+
99
+ def eagain?
100
+ EAGAIN == XS::Util.errno
101
+ end
102
+
103
+ end # module Util
104
+
105
+ end # module XS
@@ -0,0 +1,105 @@
1
+
2
+ module XS
3
+
4
+ # These methods don't belong to any specific class. They get included
5
+ # in the #Context, #Socket and #Poller classes.
6
+ #
7
+ module Util
8
+
9
+ # Returns true when +rc+ is greater than or equal to 0, false otherwise.
10
+ #
11
+ # We use the >= test because xs_poll() returns the number of sockets
12
+ # that had a read or write event triggered. So, a >= 0 result means
13
+ # it succeeded.
14
+ #
15
+ def self.resultcode_ok? rc
16
+ rc >= 0
17
+ end
18
+
19
+ # Returns the +errno+ as set by the libxs library.
20
+ #
21
+ def self.errno
22
+ LibXS.xs_errno
23
+ end
24
+
25
+ # Returns a string corresponding to the currently set #errno. These
26
+ # error strings are defined by libxs.
27
+ #
28
+ def self.error_string
29
+ LibXS.xs_strerror(errno).read_string
30
+ end
31
+
32
+ # Returns an array of the form [major, minor, patch] to represent the
33
+ # version of libzmq.
34
+ #
35
+ # Class method! Invoke as: XS::Util.version
36
+ #
37
+ def self.version
38
+ major = FFI::MemoryPointer.new :int
39
+ minor = FFI::MemoryPointer.new :int
40
+ patch = FFI::MemoryPointer.new :int
41
+ LibXS.xs_version major, minor, patch
42
+ [major.read_int, minor.read_int, patch.read_int]
43
+ end
44
+
45
+ # Attempts to bind to a random tcp port on +host+ up to +max_tries+
46
+ # times. Returns the port number upon success or nil upon failure.
47
+ #
48
+ def self.bind_to_random_tcp_port host = '127.0.0.1', max_tries = 500
49
+ tries = 0
50
+ rc = -1
51
+
52
+ while !resultcode_ok?(rc) && tries < max_tries
53
+ tries += 1
54
+ random = random_port
55
+ rc = socket.bind "tcp://#{host}:#{random}"
56
+ end
57
+
58
+ resultcode_ok?(rc) ? random : nil
59
+ end
60
+
61
+
62
+ private
63
+
64
+ # generate a random port between 10_000 and 65534
65
+ def self.random_port
66
+ rand(55534) + 10_000
67
+ end
68
+
69
+ # :doc:
70
+ # Called by most library methods to verify there were no errors during
71
+ # operation. If any are found, raise the appropriate #XSError.
72
+ #
73
+ # When no error is found, this method returns +true+ which is behavior
74
+ # used internally by #send and #recv.
75
+ #
76
+ def error_check source, result_code
77
+ if -1 == result_code
78
+ raise_error source, result_code
79
+ end
80
+
81
+ # used by Socket::send/recv, ignored by others
82
+ true
83
+ end
84
+
85
+ def raise_error source, result_code
86
+ if 'xs_init' == source || 'xs_socket' == source
87
+ raise ContextError.new source, result_code, XS::Util.errno, XS::Util.error_string
88
+
89
+ elsif ['xs_msg_init', 'xs_msg_init_data', 'xs_msg_copy', 'xs_msg_move'].include?(source)
90
+ raise MessageError.new source, result_code, XS::Util.errno, XS::Util.error_string
91
+
92
+ else
93
+ puts "else"
94
+ raise XSError.new source, result_code, -1,
95
+ "Source [#{source}] does not match any xs_* strings, rc [#{result_code}], errno [#{XS::Util.errno}], error_string [#{XS::Util.error_string}]"
96
+ end
97
+ end
98
+
99
+ def eagain?
100
+ EAGAIN == XS::Util.errno
101
+ end
102
+
103
+ end # module Util
104
+
105
+ end # module XS
@@ -0,0 +1,3 @@
1
+ module XS
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,3 @@
1
+ module XS
2
+ VERSION = "0.1.0"
3
+ end
data/lib/ffi-rxs.rb ADDED
@@ -0,0 +1,74 @@
1
+
2
+ module XS
3
+
4
+ # :stopdoc:
5
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
6
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
7
+ # :startdoc:
8
+
9
+ # Returns the version string for the library.
10
+ #
11
+ def self.version
12
+ @version ||= File.read(path('version.txt')).strip
13
+ end
14
+
15
+ # Returns the library path for the module. If any arguments are given,
16
+ # they will be joined to the end of the libray path using
17
+ # <tt>File.join</tt>.
18
+ #
19
+ def self.libpath( *args, &block )
20
+ rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
21
+ if block
22
+ begin
23
+ $LOAD_PATH.unshift LIBPATH
24
+ rv = block.call
25
+ ensure
26
+ $LOAD_PATH.shift
27
+ end
28
+ end
29
+ return rv
30
+ end
31
+
32
+ # Returns the lpath for the module. If any arguments are given,
33
+ # they will be joined to the end of the path using
34
+ # <tt>File.join</tt>.
35
+ #
36
+ def self.path( *args, &block )
37
+ rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
38
+ if block
39
+ begin
40
+ $LOAD_PATH.unshift PATH
41
+ rv = block.call
42
+ ensure
43
+ $LOAD_PATH.shift
44
+ end
45
+ end
46
+ return rv
47
+ end
48
+
49
+ # Utility method used to require all files ending in .rb that lie in the
50
+ # directory below this file that has the same name as the filename passed
51
+ # in. Optionally, a specific _directory_ name can be passed in such that
52
+ # the _filename_ does not have to be equivalent to the directory.
53
+ #
54
+ def self.require_all_libs_relative_to( fname, dir = nil )
55
+ dir ||= ::File.basename(fname, '.*')
56
+ search_me = ::File.expand_path(
57
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
58
+
59
+ Dir.glob(search_me).sort.each {|rb| require rb}
60
+ end
61
+
62
+ end # module XS
63
+
64
+ # some code is conditionalized based upon what ruby engine we are
65
+ # executing
66
+
67
+ RBX = defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/ ? true : false
68
+
69
+ require 'ffi' unless RBX
70
+
71
+ # the order of files is important
72
+ %w(libc libxs constants util exceptions context message socket poll_items poll).each do |file|
73
+ require XS.libpath(['ffi-rxs', file])
74
+ end
@@ -0,0 +1,138 @@
1
+ $: << "." # added for ruby 1.9.2 compatibilty; it doesn't include the current directory on the load path anymore
2
+
3
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
4
+
5
+ module XS
6
+
7
+
8
+ describe Context do
9
+
10
+ context "when initializing with factory method #create" do
11
+ include APIHelper
12
+
13
+ it "should set the :pointer accessor to non-nil" do
14
+ ctx = Context.create
15
+ ctx.pointer.should_not be_nil
16
+ end
17
+
18
+ it "should set the :context accessor to non-nil" do
19
+ ctx = Context.create
20
+ ctx.context.should_not be_nil
21
+ end
22
+
23
+ it "should set the :pointer and :context accessors to the same value" do
24
+ ctx = Context.create
25
+ ctx.pointer.should == ctx.context
26
+ end
27
+
28
+ it "should define a finalizer on this object" do
29
+ ObjectSpace.should_receive(:define_finalizer)
30
+ ctx = Context.create
31
+ end
32
+ end # context initializing
33
+
34
+
35
+ context "when initializing with #new" do
36
+ include APIHelper
37
+
38
+ it "should set the :pointer accessor to non-nil" do
39
+ ctx = Context.new
40
+ ctx.pointer.should_not be_nil
41
+ end
42
+
43
+ it "should set the :context accessor to non-nil" do
44
+ ctx = Context.new
45
+ ctx.context.should_not be_nil
46
+ end
47
+
48
+ it "should set the :pointer and :context accessors to the same value" do
49
+ ctx = Context.new
50
+ ctx.pointer.should == ctx.context
51
+ end
52
+
53
+ it "should define a finalizer on this object" do
54
+ ObjectSpace.should_receive(:define_finalizer)
55
+ ctx = Context.new
56
+ end
57
+ end # context initializing
58
+
59
+ context "when setting context options" do
60
+ include APIHelper
61
+
62
+ it "gets EINVAL when option name is not recognized" do
63
+ ctx = Context.new
64
+ rc = ctx.setctxopt(XS::IDENTITY, 10)
65
+ Util.resultcode_ok?(rc).should be_false
66
+ XS::Util.errno.should == XS::EINVAL
67
+ end
68
+ end
69
+
70
+ context "when setting IO_THREADS context option" do
71
+ include APIHelper
72
+
73
+ it "should return unsuccessful code for zero io threads" do
74
+ ctx = Context.new
75
+ rc = ctx.setctxopt(XS::IO_THREADS, 0)
76
+ Util.resultcode_ok?(rc).should be_false
77
+ XS::Util.errno.should == XS::EINVAL
78
+ end
79
+
80
+ it "should return unsuccessful code for negative io threads" do
81
+ ctx = Context.new
82
+ rc = ctx.setctxopt(XS::IO_THREADS, -1)
83
+ Util.resultcode_ok?(rc).should be_false
84
+ XS::Util.errno.should == XS::EINVAL
85
+ end
86
+
87
+ it "should return successful code for positive io threads" do
88
+ ctx = Context.new
89
+ rc = ctx.setctxopt(XS::IO_THREADS, 10)
90
+ Util.resultcode_ok?(rc).should be_true
91
+ end
92
+ end # context set IO_THREADS
93
+
94
+
95
+ context "when setting MAX_SOCKETS context option" do
96
+ include APIHelper
97
+
98
+ it "should return successful code for zero max sockets" do
99
+ ctx = Context.new
100
+ rc = ctx.setctxopt(XS::MAX_SOCKETS, 0)
101
+ Util.resultcode_ok?(rc).should be_true
102
+ end
103
+
104
+ it "should return unsuccessful code for negative max sockets" do
105
+ ctx = Context.new
106
+ rc = ctx.setctxopt(XS::MAX_SOCKETS, -1)
107
+ Util.resultcode_ok?(rc).should be_false
108
+ XS::Util.errno.should == XS::EINVAL
109
+ end
110
+
111
+ it "should return successful code for positive max sockets" do
112
+ ctx = Context.new
113
+ rc = ctx.setctxopt(XS::MAX_SOCKETS, 100)
114
+ Util.resultcode_ok?(rc).should be_true
115
+ end
116
+ end # context set MAX_SOCKETS
117
+
118
+ context "when terminating" do
119
+ it "should call xs_term to terminate the library's context" do
120
+ ctx = Context.new # can't use a shared context here because we are terminating it!
121
+ LibXS.should_receive(:xs_term).with(ctx.pointer).and_return(0)
122
+ ctx.terminate
123
+ end
124
+ end # context terminate
125
+
126
+
127
+ context "when allocating a socket" do
128
+ it "should return nil when allocation fails" do
129
+ ctx = Context.new
130
+ LibXS.stub!(:xs_socket => nil)
131
+ ctx.socket(XS::REQ).should be_nil
132
+ end
133
+ end # context socket
134
+
135
+ end # describe Context
136
+
137
+
138
+ end # module XS
@@ -0,0 +1,128 @@
1
+
2
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
3
+
4
+ module XS
5
+
6
+
7
+ describe Message do
8
+
9
+ context "when initializing with an argument" do
10
+
11
+ it "calls xs_msg_init_data()" do
12
+ LibXS.should_receive(:xs_msg_init_data)
13
+ message = Message.new "text"
14
+ end
15
+
16
+ it "should *not* define a finalizer on this object" do
17
+ ObjectSpace.should_not_receive(:define_finalizer)
18
+ Message.new "text"
19
+ end
20
+ end # context initializing with arg
21
+
22
+ context "when initializing *without* an argument" do
23
+
24
+ it "calls xs_msg_init()" do
25
+ LibXS.should_receive(:xs_msg_init).and_return(0)
26
+ message = Message.new
27
+ end
28
+
29
+ it "should *not* define a finalizer on this object" do
30
+ ObjectSpace.should_not_receive(:define_finalizer)
31
+ Message.new "text"
32
+ end
33
+ end # context initializing with arg
34
+
35
+
36
+ context "#copy_in_string" do
37
+ it "calls xs_msg_init_data()" do
38
+ message = Message.new "text"
39
+
40
+ LibXS.should_receive(:xs_msg_init_data)
41
+ message.copy_in_string("new text")
42
+ end
43
+
44
+ it "correctly finds the length of binary data by ignoring encoding" do
45
+ message = Message.new
46
+ message.copy_in_string("\x83\x6e\x04\x00\x00\x44\xd1\x81")
47
+ message.size.should == 8
48
+ end
49
+ end
50
+
51
+
52
+ context "#copy" do
53
+ it "calls xs_msg_copy()" do
54
+ message = Message.new "text"
55
+ copy = Message.new
56
+
57
+ LibXS.should_receive(:xs_msg_copy)
58
+ copy.copy(message)
59
+ end
60
+ end # context copy
61
+
62
+
63
+ context "#move" do
64
+ it "calls xs_msg_move()" do
65
+ message = Message.new "text"
66
+ copy = Message.new
67
+
68
+ LibXS.should_receive(:xs_msg_move)
69
+ copy.move(message)
70
+ end
71
+ end # context move
72
+
73
+
74
+ context "#size" do
75
+ it "calls xs_msg_size()" do
76
+ message = Message.new "text"
77
+
78
+ LibXS.should_receive(:xs_msg_size)
79
+ message.size
80
+ end
81
+ end # context size
82
+
83
+
84
+ context "#data" do
85
+ it "calls xs_msg_data()" do
86
+ message = Message.new "text"
87
+
88
+ LibXS.should_receive(:xs_msg_data)
89
+ message.data
90
+ end
91
+ end # context data
92
+
93
+
94
+ context "#close" do
95
+ it "calls xs_msg_close() the first time" do
96
+ message = Message.new "text"
97
+
98
+ LibXS.should_receive(:xs_msg_close)
99
+ message.close
100
+ end
101
+
102
+ it "*does not* call xs_msg_close() on subsequent invocations" do
103
+ message = Message.new "text"
104
+ message.close
105
+
106
+ LibXS.should_not_receive(:xs_msg_close)
107
+ message.close
108
+ end
109
+ end # context close
110
+
111
+ end # describe Message
112
+
113
+
114
+ describe ManagedMessage do
115
+
116
+ context "when initializing with an argument" do
117
+
118
+ it "should define a finalizer on this object" do
119
+ ObjectSpace.should_receive(:define_finalizer)
120
+ ManagedMessage.new "text"
121
+ end
122
+ end # context initializing
123
+
124
+
125
+ end # describe ManagedMessage
126
+
127
+
128
+ end # module XS
@@ -0,0 +1,108 @@
1
+ require File.join(File.dirname(__FILE__), %w[spec_helper])
2
+
3
+ module XS
4
+ describe Socket do
5
+ context "multipart messages" do
6
+ before(:all) { @ctx = Context.new }
7
+ after(:all) { @ctx.terminate }
8
+
9
+ context "without identity" do
10
+ include APIHelper
11
+
12
+ before(:all) do
13
+ @rep = Socket.new(@ctx.pointer, XS::REP)
14
+ port = bind_to_random_tcp_port(@rep)
15
+
16
+ @req = Socket.new(@ctx.pointer, XS::REQ)
17
+ @req.connect("tcp://127.0.0.1:#{port}")
18
+ end
19
+
20
+ after(:all) do
21
+ @req.close
22
+ @rep.close
23
+ end
24
+
25
+ it "should be delivered between REQ and REP returning an array of strings" do
26
+ req_data, rep_data = [ "1", "2" ], [ "2", "3" ]
27
+
28
+ @req.send_strings(req_data)
29
+ strings = []
30
+ rc = @rep.recv_strings(strings)
31
+ strings.should == req_data
32
+
33
+ @rep.send_strings(rep_data)
34
+ strings = []
35
+ rc = @req.recv_strings(strings)
36
+ strings.should == rep_data
37
+ end
38
+
39
+ it "should be delivered between REQ and REP returning an array of messages" do
40
+ req_data, rep_data = [ "1", "2" ], [ "2", "3" ]
41
+
42
+ @req.send_strings(req_data)
43
+ messages = []
44
+ rc = @rep.recvmsgs(messages)
45
+ messages.each_with_index do |message, index|
46
+ message.copy_out_string.should == req_data[index]
47
+ end
48
+
49
+ @rep.send_strings(rep_data)
50
+ messages = []
51
+ rc = @req.recvmsgs(messages)
52
+ messages.each_with_index do |message, index|
53
+ message.copy_out_string.should == rep_data[index]
54
+ end
55
+ end
56
+ end
57
+
58
+ context "with identity" do
59
+ include APIHelper
60
+
61
+ before(:each) do # was :all
62
+ @rep = Socket.new(@ctx.pointer, XS::XREP)
63
+ port = bind_to_random_tcp_port(@rep)
64
+
65
+ @req = Socket.new(@ctx.pointer, XS::REQ)
66
+ @req.identity = 'foo'
67
+ @req.connect("tcp://127.0.0.1:#{port}")
68
+ end
69
+
70
+ after(:each) do # was :all
71
+ @req.close
72
+ @rep.close
73
+ end
74
+
75
+ it "should be delivered between REQ and REP returning an array of strings with an empty string as the envelope delimiter" do
76
+ req_data, rep_data = "hello", [ @req.identity, "", "ok" ]
77
+
78
+ @req.send_string(req_data)
79
+ strings = []
80
+ rc = @rep.recv_strings(strings)
81
+ strings.should == [ @req.identity, "", "hello" ]
82
+
83
+ @rep.send_strings(rep_data)
84
+ string = ''
85
+ rc = @req.recv_string(string)
86
+ string.should == rep_data.last
87
+ end
88
+
89
+ it "should be delivered between REQ and REP returning an array of messages with an empty string as the envelope delimiter" do
90
+ req_data, rep_data = "hello", [ @req.identity, "", "ok" ]
91
+
92
+ @req.send_string(req_data)
93
+ msgs = []
94
+ rc = @rep.recvmsgs(msgs)
95
+ msgs[0].copy_out_string.should == @req.identity
96
+ msgs[1].copy_out_string.should == ""
97
+ msgs[2].copy_out_string.should == "hello"
98
+
99
+ @rep.send_strings(rep_data)
100
+ msgs = []
101
+ rc = @req.recvmsgs(msgs)
102
+ msgs[0].copy_out_string.should == rep_data.last
103
+ end
104
+ end
105
+
106
+ end
107
+ end
108
+ end