ffi-rxs 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,169 @@
1
+ # encoding: utf-8
2
+
3
+ # throughput_measurement.rb
4
+
5
+ # Within a single process, we start up five threads. Main thread has a PUB (publisher)
6
+ # socket and the secondary threads have SUB (subscription) sockets. We measure the
7
+ # *throughput* between these sockets. A high-water mark (HWM) is *not* set, so the
8
+ # publisher queue is free to grow to the size of memory without dropping packets.
9
+ #
10
+ # This example also illustrates how a single context can be shared amongst several
11
+ # threads. Sharing a single context also allows a user to specify the "inproc"
12
+ # transport in addition to "tcp" and "ipc".
13
+ #
14
+ # % ruby throughput_measurement.rb tcp://127.0.0.1:5555 1024 1_000_000
15
+ #
16
+ # % ruby throughput_measurement.rb inproc://lm_sock 1024 1_000_000
17
+
18
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rxs')
19
+ require 'thread'
20
+
21
+ if ARGV.length < 3
22
+ puts "usage: ruby throughput_measurement.rb <connect-to> <message-size> <roundtrip-count>"
23
+ exit
24
+ end
25
+
26
+ link = ARGV[0]
27
+ message_size = ARGV[1].to_i
28
+ count = ARGV[2].to_i
29
+
30
+ def assert(rc)
31
+ raise "Last API call failed at #{caller(1)}" unless rc >= 0
32
+ end
33
+
34
+ begin
35
+ master_context = XS::Context.new
36
+ rescue ContextError => e
37
+ STDERR.puts "Failed to allocate context or socket!"
38
+ raise
39
+ end
40
+
41
+
42
+ class Receiver
43
+ def initialize context, link, size, count, stats
44
+ @context = context
45
+ @link = link
46
+ @size = size
47
+ @count = count
48
+ @stats = stats
49
+
50
+ begin
51
+ @socket = @context.socket(XS::SUB)
52
+ rescue ContextError => e
53
+ STDERR.puts "Failed to allocate SUB socket!"
54
+ raise
55
+ end
56
+
57
+ assert(@socket.setsockopt(XS::LINGER, 100))
58
+ assert(@socket.setsockopt(XS::SUBSCRIBE, ""))
59
+
60
+ assert(@socket.connect(@link))
61
+ end
62
+
63
+ def run
64
+ msg = XS::Message.new
65
+ assert(@socket.recvmsg(msg))
66
+
67
+ elapsed = elapsed_microseconds do
68
+ (@count - 1).times do
69
+ assert(@socket.recvmsg(msg))
70
+ end
71
+ end
72
+
73
+ @stats.record_elapsed(elapsed)
74
+ assert(@socket.close)
75
+ end
76
+
77
+ def elapsed_microseconds(&blk)
78
+ start = Time.now
79
+ yield
80
+ ((Time.now - start) * 1_000_000)
81
+ end
82
+ end
83
+
84
+ class Transmitter
85
+ def initialize context, link, size, count
86
+ @context = context
87
+ @link = link
88
+ @size = size
89
+ @count = count
90
+
91
+ begin
92
+ @socket = @context.socket(XS::PUB)
93
+ rescue ContextError => e
94
+ STDERR.puts "Failed to allocate PUB socket!"
95
+ raise
96
+ end
97
+
98
+ assert(@socket.setsockopt(XS::LINGER, 100))
99
+ assert(@socket.bind(@link))
100
+ end
101
+
102
+ def run
103
+ sleep 1
104
+ contents = "#{'0' * @size}"
105
+
106
+ i = 0
107
+ while i < @count
108
+ msg = XS::Message.new(contents)
109
+ assert(@socket.sendmsg(msg))
110
+ i += 1
111
+ end
112
+
113
+ end
114
+
115
+ def close
116
+ assert(@socket.close)
117
+ end
118
+ end
119
+
120
+ class Stats
121
+ def initialize size, count
122
+ @size = size
123
+ @count = count
124
+
125
+ @mutex = Mutex.new
126
+ @elapsed = []
127
+ end
128
+
129
+ def record_elapsed(elapsed)
130
+ @mutex.synchronize do
131
+ @elapsed << elapsed
132
+ end
133
+ end
134
+
135
+ def output
136
+ @elapsed.each do |elapsed|
137
+ throughput = @count * 1000000 / elapsed
138
+ megabits = throughput * @size * 8 / 1000000
139
+
140
+ puts "message size: %i [B]" % @size
141
+ puts "message count: %i" % @count
142
+ puts "mean throughput: %i [msg/s]" % throughput
143
+ puts "mean throughput: %.3f [Mb/s]" % megabits
144
+ puts
145
+ end
146
+ end
147
+ end
148
+
149
+ threads = []
150
+ stats = Stats.new message_size, count
151
+ transmitter = Transmitter.new(master_context, link, message_size, count)
152
+
153
+ threads << Thread.new do
154
+ transmitter.run
155
+ end
156
+
157
+ 1.times do
158
+ threads << Thread.new do
159
+ receiver = Receiver.new(master_context, link, message_size, count, stats)
160
+ receiver.run
161
+ end
162
+ end
163
+
164
+ threads.each {|t| t.join}
165
+ transmitter.close
166
+ stats.output
167
+
168
+ master_context.terminate
169
+
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ # weather_upd_client.rb
4
+
5
+ # This example is used in conjunction with weather_upd_server.rb.
6
+ # Run this program in a terminal/console window and then run
7
+ # weather_upd_server.rb in another terminal/console window and
8
+ # observe the output.
9
+ #
10
+ # This program subscribes to a feed of weather updates published
11
+ # by weather_upd_server.rb, collects the first 100 updates that
12
+ # match the subscription filter and displays the average temperature
13
+ # for that zipcode.
14
+ #
15
+ # Usage: ruby weather_upd_client.rb [zip code (default=10001)]
16
+ #
17
+ # If you supply a zip code argument then the maximum value that will
18
+ # be recognized is 11000.
19
+
20
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rxs')
21
+
22
+ COUNT = 100
23
+
24
+ context = XS::Context.create()
25
+
26
+ # Socket to talk to server
27
+ puts "Collecting updates from weather server..."
28
+ subscriber = context.socket(XS::SUB)
29
+ subscriber.connect("tcp://127.0.0.1:5556")
30
+
31
+ # Subscribe to zipcode, default is NYC, 10001
32
+ filter = ARGV.size > 0 ? ARGV[0] : "10001"
33
+ subscriber.setsockopt(XS::SUBSCRIBE, filter)
34
+
35
+ # Process 100 updates
36
+ total_temp = 0
37
+ 1.upto(COUNT) do |update_nbr|
38
+ s = ''
39
+ subscriber.recv_string(s)
40
+
41
+ zipcode, temperature, relhumidity = s.split.map(&:to_i)
42
+ total_temp += temperature
43
+ puts "Update #{update_nbr.to_s}: #{temperature.to_s}F"
44
+ end
45
+
46
+ puts "Average temperature for zipcode '#{filter}' was #{total_temp / COUNT}F"
47
+
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ # weather_upd_server.rb
4
+
5
+ # This example is used in conjunction with weather_upd_client.rb.
6
+ # Run this program in a terminal/console window and then run
7
+ # weather_upd_client.rb in another terminal/console window and
8
+ # observe the output.
9
+ #
10
+ # This program publishes a feed of random weather updates containing
11
+ # random zip codes up to a maximum value of 11000.
12
+ #
13
+ # Usage: ruby weather_upd_server.rb
14
+ #
15
+ # To stop the program terminate the process with Ctrl-C or another
16
+ # method of your choice.
17
+ #
18
+
19
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rxs')
20
+
21
+ context = XS::Context.create()
22
+ publisher = context.socket(XS::PUB)
23
+ publisher.bind("tcp://127.0.0.1:5556")
24
+
25
+ while true
26
+ # Get values that will fool the boss
27
+ zipcode = rand(11000)
28
+ temperature = rand(215) - 80
29
+ relhumidity = rand(50) + 10
30
+
31
+ update = "%05d %d %d" % [zipcode, temperature, relhumidity]
32
+ puts update
33
+ publisher.send_string(update)
34
+ end
35
+
@@ -0,0 +1,101 @@
1
+ # encoding: utf-8
2
+
3
+ # xreq_xrep_poll.rb
4
+ #
5
+ # It illustrates the use of xs_poll(), as wrapped by the Ruby library,
6
+ # for detecting and responding to read and write events recorded on sockets.
7
+ # It also shows how to use XS::NO_BLOCK/XS::DONTWAIT for non-blocking send
8
+ # and receive.
9
+
10
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'ffi-rxs')
11
+
12
+ def assert(rc)
13
+ raise "Last API call failed at #{caller(1)}" unless rc >= 0
14
+ end
15
+
16
+ link = "tcp://127.0.0.1:5555"
17
+
18
+
19
+ begin
20
+ ctx = XS::Context.new
21
+ s1 = ctx.socket(XS::XREQ)
22
+ s2 = ctx.socket(XS::XREP)
23
+ rescue ContextError => e
24
+ STDERR.puts "Failed to allocate context or socket"
25
+ raise
26
+ end
27
+
28
+ s1.identity = 'socket1.xreq'
29
+ s2.identity = 'socket2.xrep'
30
+
31
+ assert(s1.setsockopt(XS::LINGER, 100))
32
+ assert(s2.setsockopt(XS::LINGER, 100))
33
+
34
+ assert(s1.bind(link))
35
+ assert(s2.connect(link))
36
+
37
+ poller = XS::Poller.new
38
+ poller.register_readable(s2)
39
+ poller.register_writable(s1)
40
+
41
+ start_time = Time.now
42
+ @unsent = true
43
+
44
+ until @done do
45
+ assert(poller.poll_nonblock)
46
+
47
+ # send the message after 5 seconds
48
+ if Time.now - start_time > 5 && @unsent
49
+ puts "sending payload nonblocking"
50
+
51
+ 5.times do |i|
52
+ payload = "#{ i.to_s * 40 }"
53
+ assert(s1.send_string(payload, XS::NonBlocking))
54
+ end
55
+ @unsent = false
56
+ end
57
+
58
+ # check for messages after 1 second
59
+ if Time.now - start_time > 1
60
+ poller.readables.each do |sock|
61
+
62
+ if sock.identity =~ /xrep/
63
+ routing_info = ''
64
+ assert(sock.recv_string(routing_info, XS::NonBlocking))
65
+ puts "routing_info received [#{routing_info}] on socket.identity [#{sock.identity}]"
66
+ else
67
+ routing_info = nil
68
+ received_msg = ''
69
+ assert(sock.recv_string(received_msg, XS::NonBlocking))
70
+
71
+ # skip to the next iteration if received_msg is nil; that means we got an EAGAIN
72
+ next unless received_msg
73
+ puts "message received [#{received_msg}] on socket.identity [#{sock.identity}]"
74
+ end
75
+
76
+ while sock.more_parts? do
77
+ received_msg = ''
78
+ assert(sock.recv_string(received_msg, XS::NonBlocking))
79
+
80
+ puts "message received [#{received_msg}]"
81
+ end
82
+
83
+ puts "kick back a reply"
84
+ assert(sock.send_string(routing_info, XS::SNDMORE | XS::NonBlocking)) if routing_info
85
+ time = Time.now.strftime "%Y-%m-%dT%H:%M:%S.#{Time.now.usec}"
86
+ reply = "reply " + sock.identity.upcase + " #{time}"
87
+ puts "sent reply [#{reply}], #{time}"
88
+ assert(sock.send_string(reply))
89
+ @done = true
90
+ poller.register_readable(s1)
91
+ end
92
+ end
93
+ end
94
+
95
+ puts "executed in [#{Time.now - start_time}] seconds"
96
+
97
+ assert(s1.close)
98
+ assert(s2.close)
99
+
100
+ ctx.terminate
101
+
data/ffi-rxs.gemspec CHANGED
@@ -8,13 +8,11 @@ Gem::Specification.new do |s|
8
8
  s.authors = ["Chris Duncan"]
9
9
  s.email = ["celldee@gmail.com"]
10
10
  s.homepage = "http://github.com/celldee/ffi-rxs"
11
- s.summary = %q{This gem wraps the Crossroads I/O networking library using Ruby FFI (foreign function interface).}
12
- s.description = %q{This gem wraps the Crossroads I/O networking library using the ruby FFI (foreign
13
- function interface). It's a pure ruby wrapper so this gem can be loaded
14
- and run by any ruby runtime that supports FFI. That's all of them:
15
- MRI 1.9.x, Rubinius and JRuby.}
11
+ s.summary = %q{Ruby FFI bindings for Crossroads I/O networking library.}
12
+ s.description = %q{Ruby FFI bindings for Crossroads I/O networking library.}
16
13
 
17
14
  s.files = `git ls-files`.split("\n")
15
+ s.files = s.files.reject{ |f| f.include?('ext/libxs.so') }
18
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
18
  s.require_paths = ["lib"]
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module XS
2
4
  # Set up all of the constants
3
5
 
@@ -5,7 +7,7 @@ module XS
5
7
  MAX_SOCKETS = 1
6
8
  IO_THREADS = 2
7
9
 
8
- # Socket types
10
+ # Socket types
9
11
  PAIR = 0
10
12
  PUB = 1
11
13
  SUB = 2
@@ -36,7 +38,7 @@ module XS
36
38
  XSUB => "XSUB"
37
39
  }
38
40
 
39
- # Socket options
41
+ # Socket options
40
42
  AFFINITY = 4
41
43
  IDENTITY = 5
42
44
  SUBSCRIBE = 6
@@ -59,21 +61,23 @@ module XS
59
61
  MULTICAST_HOPS = 25
60
62
  RCVTIMEO = 27
61
63
  SNDTIMEO = 28
64
+ IPV4ONLY = 31
65
+ KEEPALIVE = 32
66
+
67
+ # Message options
68
+ MORE = 1
62
69
 
63
- # Send/recv options
70
+ # Send/recv options
64
71
  DONTWAIT = 1
65
72
  SNDMORE = 2
66
- SNDLABEL = 4
67
73
  NonBlocking = DONTWAIT
68
74
 
69
- # I/O multiplexing
70
-
71
- POLL = 1
75
+ # I/O multiplexing
72
76
  POLLIN = 1
73
77
  POLLOUT = 2
74
78
  POLLERR = 4
75
79
 
76
- # Socket errors
80
+ # Socket errors
77
81
  EAGAIN = Errno::EAGAIN::Errno
78
82
  EFAULT = Errno::EFAULT::Errno
79
83
  EINVAL = Errno::EINVAL::Errno
@@ -88,9 +92,9 @@ module XS
88
92
  ENOCOMPATPROTO = (HAUSNUMERO + 52)
89
93
  ETERM = (HAUSNUMERO + 53)
90
94
 
91
- # Rescue unknown constants and use the Crossroads defined values
92
- # Usually only happens on Windows though some don't resolve on
93
- # OSX too (ENOTSUP)
95
+ # Rescue unknown constants and use the Crossroads defined values.
96
+ # Usually only happens on Windows although some do not resolve on
97
+ # OSX either _ENOTSUP_
94
98
  ENOTSUP = Errno::ENOTSUP::Errno rescue (HAUSNUMERO + 1)
95
99
  EPROTONOSUPPORT = Errno::EPROTONOSUPPORT::Errno rescue (HAUSNUMERO + 2)
96
100
  ENOBUFS = Errno::ENOBUFS::Errno rescue (HAUSNUMERO + 3)
@@ -1,20 +1,9 @@
1
+ # encoding: utf-8
1
2
 
2
3
  module XS
3
4
 
4
-
5
- # Recommended to use the default for +io_threads+
6
- # since most programs will not saturate I/O.
7
- #
8
- # The rule of thumb is to make +io_threads+ equal to the number
9
- # gigabits per second that the application will produce.
10
- #
11
- # The +io_threads+ number specifies the size of the thread pool
12
- # allocated by 0mq for processing incoming/outgoing messages.
13
- #
14
- # Returns a context object when allocation succeeds. It's necessary
15
- # for passing to the
16
- # #Socket constructor when allocating new sockets. All sockets
17
- # live within a context.
5
+ # All sockets exist within a context and a context is passed to the
6
+ # Socket constructor when allocating new sockets.
18
7
  #
19
8
  # Also, Sockets should *only* be accessed from the thread where they
20
9
  # were first created. Do *not* pass sockets between threads; pass
@@ -27,30 +16,29 @@ module XS
27
16
  # recommended technique for allowing sockets to communicate between
28
17
  # threads.
29
18
  #
30
- # context = XS::Context.create
31
- # if context
32
- # socket = context.socket(XS::REQ)
33
- # if socket
34
- # ...
35
- # else
36
- # STDERR.puts "Socket allocation failed"
37
- # end
38
- # else
39
- # STDERR.puts "Context allocation failed"
40
- # end
41
- #
42
- #
19
+ # @example Create context and socket
20
+ # context = XS::Context.create
21
+ # if context
22
+ # socket = context.socket(XS::REQ)
23
+ # if socket
24
+ # ...
25
+ # else
26
+ # STDERR.puts "Socket allocation failed"
27
+ # end
28
+ # else
29
+ # STDERR.puts "Context allocation failed"
30
+ # end
43
31
  class Context
44
32
  include XS::Util
45
33
 
46
34
  attr_reader :context, :pointer
47
35
 
36
+ # Factory method to instantiate contexts
48
37
  def self.create
49
38
  new() rescue nil
50
39
  end
51
40
 
52
- # Use the factory method Context#create to make contexts.
53
- #
41
+ # Initialize context object
54
42
  def initialize
55
43
  @sockets = []
56
44
  @context = LibXS.xs_init()
@@ -60,25 +48,36 @@ module XS
60
48
  define_finalizer
61
49
  end
62
50
 
63
- # Set options on this context.
51
+ # Sets options on a context.
52
+ #
53
+ # It is recommended to use the default for +io_threads+
54
+ # (which is 1) since most programs will not saturate I/O.
64
55
  #
65
- # Context options take effect only if set with setctxopt() prior to
66
- # creating the first socket in a given context with socket().
56
+ # The rule of thumb is to make io_threads equal to the number
57
+ # of gigabits per second that the application will produce.
67
58
  #
68
- # Valid +name+ values that take a numeric +value+ are:
69
- # XS::IO_THREADS
70
- # XS::MAX_SOCKETS
59
+ # The io_threads number specifies the size of the thread pool
60
+ # allocated by Crossroads for processing incoming/outgoing messages.
71
61
  #
72
- # Returns 0 when the operation completed successfully.
73
- # Returns -1 when this operation failed.
62
+ # The +max_sockets+ number specifies the number of concurrent
63
+ # sockets that can be used in the context. The default is 512.
74
64
  #
75
- # With a -1 return code, the user must check XS.errno to determine the
76
- # cause.
65
+ # Context options take effect only if set with **setctxopt()** prior to
66
+ # creating the first socket in a given context with **socket()**.
77
67
  #
78
- # rc = context.setctxopt(XS::IO_THREADS, 10)
79
- # XS::Util.resultcode_ok?(rc) ? puts("succeeded") : puts("failed")
68
+ # @param [Constant] name
69
+ # One of @XS::IO_THREADS@ or @XS::MAX_SOCKETS@.
70
+ # @param [Integer] value
71
+ #
72
+ # @return 0 when the operation completed successfully.
73
+ # @return -1 when this operation fails.
80
74
  #
81
- def setctxopt name, value, length = nil
75
+ # @example Set io_threads context option
76
+ # rc = context.setctxopt(XS::IO_THREADS, 10)
77
+ # unless XS::Util.resultcode_ok?(rc)
78
+ # XS::raise_error('xs_setctxopt', rc)
79
+ # end
80
+ def setctxopt name, value
82
81
  length = 4
83
82
  pointer = LibC.malloc length
84
83
  pointer.write_int value
@@ -88,13 +87,13 @@ module XS
88
87
  rc
89
88
  end
90
89
 
91
- # Call to release the context and any remaining data associated
90
+ # Releases the context and any remaining data associated
92
91
  # with past sockets. This will close any sockets that remain
93
92
  # open; further calls to those sockets will return -1 to indicate
94
93
  # the operation failed.
95
94
  #
96
- # Returns 0 for success, -1 for failure.
97
- #
95
+ # @return 0 for success
96
+ # @return -1 for failure
98
97
  def terminate
99
98
  unless @context.nil? || @context.null?
100
99
  remove_finalizer
@@ -107,22 +106,14 @@ module XS
107
106
  end
108
107
  end
109
108
 
110
- # Short-cut to allocate a socket for a specific context.
109
+ # Allocates a socket for context
111
110
  #
112
- # Takes several +type+ values:
113
- # #XS::REQ
114
- # #XS::REP
115
- # #XS::PUB
116
- # #XS::SUB
117
- # #XS::PAIR
118
- # #XS::PULL
119
- # #XS::PUSH
120
- # #XS::DEALER
121
- # #XS::ROUTER
122
- #
123
- # Returns a #XS::Socket when the allocation succeeds, nil
124
- # if it fails.
111
+ # @param [Constant] type
112
+ # One of @XS::REQ@, @XS::REP@, @XS::PUB@, @XS::SUB@, @XS::PAIR@,
113
+ # @XS::PULL@, @XS::PUSH@, @XS::DEALER@, or @XS::ROUTER@
125
114
  #
115
+ # @return [Socket] when the allocation succeeds
116
+ # @return nil when call fails
126
117
  def socket type
127
118
  sock = nil
128
119
  begin
@@ -137,14 +128,17 @@ module XS
137
128
 
138
129
  private
139
130
 
131
+ # Deletes native resources after object has been destroyed
140
132
  def define_finalizer
141
133
  ObjectSpace.define_finalizer(self, self.class.close(@context))
142
134
  end
143
-
135
+
136
+ # Removes all finalizers for object
144
137
  def remove_finalizer
145
138
  ObjectSpace.undefine_finalizer self
146
139
  end
147
140
 
141
+ # Closes the context
148
142
  def self.close context
149
143
  Proc.new { LibXS.xs_term context unless context.null? }
150
144
  end
@@ -1,6 +1,7 @@
1
+ # encoding: utf-8
1
2
 
2
3
  module XS
3
-
4
+ # General Crossroads error class
4
5
  class XSError < StandardError
5
6
  attr_reader :source, :result_code, :error_code, :message
6
7
 
@@ -8,12 +9,13 @@ module XS
8
9
  @source = source
9
10
  @result_code = result_code
10
11
  @error_code = error_code
11
- @message = "msg [#{message}], error code [#{error_code}], rc [#{result_code}]"
12
+ @message = "source [#{source}], msg [#{message}], " +
13
+ "error code [#{error_code}],rc [#{result_code}]"
12
14
  super message
13
15
  end
14
16
  end # call XSError
15
17
 
16
-
18
+ # Context error class
17
19
  class ContextError < XSError
18
20
  # True when the exception was raised due to the library
19
21
  # returning EINVAL.
@@ -33,12 +35,12 @@ module XS
33
35
 
34
36
  end # class ContextError
35
37
 
36
-
38
+ # Message error class
37
39
  class MessageError < XSError
38
40
  # True when the exception was raised due to the library
39
41
  # returning ENOMEM.
40
42
  #
41
- # Only ever raised by the #Message class when it fails
43
+ # Only ever raised by the Message class when it fails
42
44
  # to allocate sufficient memory to send a message.
43
45
  #
44
46
  def enomem?() ENOMEM == @error_code; end
data/lib/ffi-rxs/libc.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
 
2
3
  module LibC
3
4
  extend FFI::Library
data/lib/ffi-rxs/libxs.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module XS
2
4
 
3
5
  # Wraps the libxs library and attaches to the API functions