ffi-rxs 1.0.0 → 1.0.1

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.
@@ -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