├── .gitignore
├── init.rb
├── .github
└── FUNDING.yml
├── Gemfile
├── Gemfile.lock
├── lib
├── brb
│ ├── exception.rb
│ ├── logger.rb
│ ├── service.rb
│ ├── event_machine.rb
│ ├── request.rb
│ ├── tunnel.rb
│ └── tunnel
│ │ └── shared.rb
└── brb.rb
├── CHANGELOG.rdoc
├── Rakefile
├── spec
├── brb
│ ├── brb_service_spec.rb
│ ├── brb_logger_spec.rb
│ ├── brb_massive_usage_spec.rb
│ └── brb_tunnel_spec.rb
└── spec_helper.rb
├── brb.gemspec
├── examples
├── simple_core.rb
└── simple_client.rb
├── MIT-LICENSE
└── README.rdoc
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | doc
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require File.join(File.dirname(__FILE__), 'lib', 'brb.rb')
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | custom: 'https://triplebyte.com/a/ZBAdDFG/brb'
4 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'http://rubygems.org'
2 | source 'http://gems.github.com'
3 |
4 | gem "rspec", "~> 1.3.2"
5 | gem "eventmachine"
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | remote: http://gems.github.com/
4 | specs:
5 | eventmachine (0.12.10)
6 | rspec (1.3.2)
7 |
8 | PLATFORMS
9 | ruby
10 |
11 | DEPENDENCIES
12 | eventmachine
13 | rspec (~> 1.3.2)
14 |
--------------------------------------------------------------------------------
/lib/brb/exception.rb:
--------------------------------------------------------------------------------
1 | # Future BrB custom exceptions will come here
2 | class BrBException < Exception
3 | end
4 |
5 | class BrBCallbackWithBlockingMethodException < BrBException
6 | def initialize
7 | super('Out request can not be blocking and have a callback at the same time !')
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/brb.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), 'brb', 'logger.rb')
2 | require File.join(File.dirname(__FILE__), 'brb', 'exception.rb')
3 | require File.join(File.dirname(__FILE__), 'brb', 'event_machine.rb')
4 | require File.join(File.dirname(__FILE__), 'brb', 'service.rb')
5 | require File.join(File.dirname(__FILE__), 'brb', 'tunnel.rb')
--------------------------------------------------------------------------------
/CHANGELOG.rdoc:
--------------------------------------------------------------------------------
1 | 0.3.0 (May 21, 2010)
2 |
3 | * Added Callback functionality through block
4 | * Added Callback example
5 | * Little spec refactoring
6 |
7 | 0.2.2 (Apr 21, 2010)
8 |
9 | * Change silent option to verbose
10 |
11 | 0.2.1 (Apr 16, 2010)
12 |
13 | * Automatically start EM if not started
14 | * Deprecate the usage of BrB::Service.instance => use BrB::Service instead
15 |
16 | 0.2.0 (Apr 16, 2010)
17 |
18 | * Releasing gem and wiki
19 |
20 | 0.1.0 (Feb 01, 2009)
21 |
22 | * Initial release
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'rake'
3 | require 'spec/rake/spectask'
4 |
5 | spec_files = Rake::FileList["spec/**/*_spec.rb"]
6 |
7 | desc "Run specs for current Rails version"
8 | Spec::Rake::SpecTask.new do |t|
9 | t.spec_files = spec_files
10 | t.spec_opts = ["-c --format specdoc"]
11 | end
12 |
13 | task :default => :spec
14 |
15 | desc "Run simple core"
16 | task :simple_core_example do
17 | require 'examples/simple_core'
18 | end
19 |
20 | desc "Run simple client (call simple_core_before)"
21 | task :simple_client_example do
22 | require 'examples/simple_client'
23 | end
--------------------------------------------------------------------------------
/spec/brb/brb_service_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe :brb_service do
4 | before(:all) do
5 | @brb = BrB::Service
6 | @brb.stop_service
7 | @brb_test = BrBTest.new
8 | open_service(self)
9 | end
10 |
11 | # Start the service
12 | it "should open a service on localhost:6200" do
13 | @brb.uri.should_not be_nil
14 | end
15 |
16 | # Finally, stop the service
17 | it "should stop the service" do
18 | @brb.stop_service
19 | @brb.uri.should be_nil
20 | end
21 |
22 | it "should start again the service after a stop" do
23 | open_service(self)
24 | @brb.stop_service
25 | open_service(self)
26 | @brb.uri.should_not be_nil
27 | end
28 | end
29 |
30 |
--------------------------------------------------------------------------------
/brb.gemspec:
--------------------------------------------------------------------------------
1 | Gem::Specification.new do |s|
2 | s.name = "brb"
3 | s.version = "0.3.1"
4 | s.author = "Guillaume Luccisano"
5 | s.email = "guillaume.luccisano@gmail.com"
6 | s.homepage = "http://github.com/kwi/BrB"
7 | s.summary = "BrB is a simple, fully transparent and extremely fast interface for doing simple distributed ruby"
8 | s.description = "BrB is a simple, fully transparent and extremely fast interface for doing simple distributed ruby and message passing"
9 | s.requirements << 'eventmachine'
10 |
11 | s.add_dependency('eventmachine', '> 0.12')
12 |
13 |
14 | s.files = Dir["{examples,lib,spec}/**/*", "[A-Z]*", "init.rb"]
15 | s.require_path = "lib"
16 |
17 | s.rubyforge_project = s.name
18 | s.required_rubygems_version = ">= 1.3.4"
19 | end
--------------------------------------------------------------------------------
/lib/brb/logger.rb:
--------------------------------------------------------------------------------
1 | require "logger"
2 |
3 | module BrB
4 | class << self
5 |
6 | # returns the default logger instance
7 | def default_logger
8 | Logger.new(STDOUT)
9 | end
10 |
11 | # set a custom logger instance
12 | def logger=(custom_logger)
13 | @@logger = custom_logger
14 | end
15 |
16 | # returns the logger instance
17 | def logger
18 | # use default logger if no custom logger is set
19 | @@logger = default_logger unless defined? @@logger
20 |
21 | # this overwrites the original method with a static definition
22 | eval %Q{
23 | def logger
24 | @@logger
25 | end
26 | }
27 | @@logger
28 | end
29 | end
30 |
31 | # alias to BrB.logger
32 | def logger
33 | BrB.logger
34 | end
35 | end
--------------------------------------------------------------------------------
/spec/brb/brb_logger_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | class CustomLogger
4 | attr_accessor :level, :history
5 | def initialize
6 | @history = []
7 | end
8 | def info(msg)
9 | @history << msg
10 | end
11 | alias :error :info
12 | alias :warn :info
13 | alias :debug :info
14 | end
15 |
16 | describe :brb_logger do
17 | before(:each) do
18 | @original_logger = BrB.logger
19 | end
20 |
21 | after(:each) do
22 | BrB.logger = @original_logger
23 | end
24 |
25 | it 'should be assigned a default logger' do
26 | BrB.logger.should_not be_nil
27 | BrB.logger.class.should == Logger
28 | end
29 |
30 | it 'should be possible to use a custom logger' do
31 | BrB.logger = CustomLogger.new
32 | BrB.logger.info('foo')
33 | BrB.logger.history.last.should == 'foo'
34 | end
35 | end
--------------------------------------------------------------------------------
/examples/simple_core.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), '../init.rb')
2 |
3 | class ExposedCoreObject
4 |
5 | def simple_api_method
6 | puts "#{Thread.current} > In simple api method, now sleeping"
7 | yield if block_given?
8 | sleep 1
9 | puts "#{Thread.current} > Done sleeping in simple api method, return"
10 | return 'OK'
11 | end
12 |
13 | def simple_long_api_method
14 | puts "#{Thread.current} > In simple long api method, now sleeping"
15 | sleep 10
16 | puts "#{Thread.current} > Done sleeping in long api method, return"
17 | return 'OK LONG'
18 | end
19 |
20 | end
21 |
22 | Thread.abort_on_exception = true
23 |
24 | port = 5555
25 | host = 'localhost'
26 |
27 | puts " > Starting the core on brb://#{host}:#{port}"
28 | BrB::Service.start_service(:object => ExposedCoreObject.new, :verbose => true, :host => host, :port => port)
29 | EM.reactor_thread.join
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 Guillaume Luccisano - g-mai|: guillaume.luccisano
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/examples/simple_client.rb:
--------------------------------------------------------------------------------
1 | require File.join(File.dirname(__FILE__), '../init.rb')
2 |
3 | port = 5555
4 | host = 'localhost'
5 |
6 | # Connecting to the core server, retrieving its interface object : core
7 | # We do not want to expose an object, so the first parameter is nil
8 | core = BrB::Tunnel.create(nil, "brb://#{host}:#{port}", :verbose => true)
9 |
10 | # Calling 10 times an non blocking method on the distant core server
11 | 10.times do
12 | core.simple_api_method # Do not wait for response
13 | end
14 |
15 | # Calling 10 times again long treatment time distant methods
16 | 10.times do
17 | core.simple_long_api_method # Do not wait for response
18 | end
19 |
20 | # Calling a blocking method with _block on the distant core server :
21 | puts " >> Calling 1s call, and wait for response..."
22 | r = core.simple_api_method_block
23 | puts " > Api response : #{r}"
24 |
25 | puts " >> Calling long call, and wait for response..."
26 | r = core.simple_long_api_method_block
27 | puts " > Api long response : #{r}"
28 |
29 | ## Calling method with a callback block for handling the return value
30 | core.simple_api_method do |r|
31 | puts " > Get the callback response : #{r}"
32 | end
33 |
34 | puts " >> Callback method has been called continue .."
35 | sleep 2
36 |
37 | core.stop_service
38 |
39 | # Our job is over, close event machine :
40 | EM.stop
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'spec'
3 |
4 | Thread.abort_on_exception = true
5 |
6 | require File.dirname(__FILE__) + '/../init.rb'
7 |
8 | def open_service(object, host = 'localhost', port = 6200)
9 | BrB::Service.start_service(:object => object, :verbose => false, :host => host, :port => port)
10 | end
11 |
12 | def connect_to_the_service(object_exposed, uri, &block)
13 | BrB::Tunnel.create(object_exposed, uri, :verbose => false, &block)
14 | end
15 |
16 | class BrBTest
17 | attr_reader :last_call
18 | attr_reader :last_args
19 | attr_reader :nb_call
20 |
21 | def increment_nb_call(call_name, *args)
22 | @last_call = call_name
23 | @last_args = args
24 | @nb_call ||= 0
25 | @nb_call += 1
26 | end
27 |
28 | def very_long(ar)
29 | increment_nb_call(:very_long, ar)
30 | end
31 |
32 | def fourargs(arg1, arg2, arg3, arg4)
33 | increment_nb_call(:fourargs, arg1, arg2, arg3, arg4)
34 | end
35 |
36 | def noarg
37 | increment_nb_call(:noarg)
38 | end
39 |
40 | def one_arg_with_return(ar)
41 | increment_nb_call(:one_arg_with_return)
42 | return ar
43 | end
44 |
45 | def return_same_value(val)
46 | increment_nb_call(:return_same_value)
47 | return val
48 | end
49 |
50 | def return_same_value_twice(val, val2)
51 | increment_nb_call(:return_same_value_twice)
52 | return val, val2
53 | end
54 | end
--------------------------------------------------------------------------------
/lib/brb/service.rb:
--------------------------------------------------------------------------------
1 | #
2 | # Brb Main class used to do basic distributed ruby, Simple but fast
3 | # Use two distinct canal, one for the command reception, and the other one for send return value
4 | #
5 | module BrB
6 | class Service
7 | @@uri = nil
8 | @@em_signature = nil
9 | @@verbose = false
10 |
11 | class << self
12 |
13 | public
14 |
15 | # Start a server hosted on the object given,
16 | # If an uri is given, automatcilay connect to the distant brb object
17 | def start_service(opts = {}, &block)
18 | return if @@em_signature
19 |
20 | @@verbose = opts[:verbose]
21 | BrB.logger.level = @@verbose ? Logger::INFO : Logger::WARN
22 |
23 | addr = opts[:uri] || "brb://#{opts[:host] || 'localhost'}:#{opts[:port] || 6200}"
24 |
25 | BrB.logger.info " [BrB] Start service on #{addr} ..."
26 | @@uri, @@em_signature = BrB::Protocol::open_server(addr, BrB::Tunnel::Handler, opts.merge(:block => block))
27 | BrB.logger.info " [BrB] Service started on #{@@uri}"
28 | end
29 |
30 | def uri
31 | @@uri
32 | end
33 |
34 | # Stop the Brb Service
35 | def stop_service
36 | return if !@@em_signature or !EM::reactor_running?
37 |
38 | BrB.logger.info " [BrB] Stop service on #{@@uri}"
39 | sign = @@em_signature
40 | q = Queue.new # Creation of a Queue for waiting server to stop
41 | EM::schedule do
42 | q << EM::stop_server(sign)
43 | end
44 | q.pop
45 | @@em_signature = nil
46 | @@uri = nil
47 | end
48 |
49 | # Deprecated old method
50 | def instance
51 | BrB.logger.warn "DEPRECATION WARNING: BrB::Service::instance is deprecated => Just use BrB::Service"
52 | self
53 | end
54 | end
55 | end
56 | end
--------------------------------------------------------------------------------
/lib/brb/event_machine.rb:
--------------------------------------------------------------------------------
1 | # Define a BrB::Protocol using event machine
2 | require 'eventmachine'
3 |
4 | module BrB
5 | class EventMachine
6 |
7 | class << self
8 |
9 | private
10 | # If EM::run has not been called yet, start the EM reactor in another thread.
11 | def ensure_em_is_started!
12 | if !EM::reactor_running?
13 | # Launch event machine reactor
14 | q = Queue.new
15 | Thread.new do
16 | EM::run do
17 | q << true # Set to the calling thread that the reactor is running
18 | #EM::set_quantum(20)
19 | #EventMachine::epoll
20 | end
21 | end
22 | # Wait for event machine running :
23 | q.pop
24 | end
25 |
26 | end
27 |
28 | public
29 | def open(uri, klass, opts = {})
30 | host, port = parse_uri(uri)
31 | begin
32 | ensure_em_is_started!
33 |
34 | q = Queue.new
35 | EM.schedule do
36 | q << EM::connect(host, port, klass, opts.merge(:uri => "brb://#{host}:#{port}"))
37 | end
38 |
39 | # Wait for socket connection with the q.pop
40 | return q.pop
41 |
42 | rescue Exception => e
43 | BrB.logger.error e.backtrace.join("\n")
44 | raise "#{e} - #{uri}"
45 | end
46 | end
47 |
48 | def open_server(uri, klass, opts = {})
49 | host, port = parse_uri(uri)
50 | max = 80 # Nb try before giving up
51 | begin
52 | uri = "brb://#{host}:#{port}"
53 | ensure_em_is_started!
54 |
55 | # Schedule server creation for thread safety
56 | q = Queue.new
57 | EM.schedule do
58 | q << EM::start_server(host, port, klass, opts.merge(:uri => uri))
59 | end
60 |
61 | # Wait for server creation with the q.pop
62 | return uri, q.pop
63 |
64 | rescue Exception => e
65 | max -= 1
66 | port += 1
67 | retry if max > 0
68 | BrB.logger.error e.backtrace.join("\n")
69 | raise "#{e} - BrB Tcp Event machine Can not bind on #{host}:#{port}"
70 | end
71 |
72 | end
73 | end
74 | end
75 |
76 | class Protocol < EventMachine
77 |
78 | class << self
79 |
80 | def parse_uri(uri)
81 | if /^brb:\/\/(.+):([0-9]+)$/ =~ uri
82 | [$1, $2.to_i]
83 | else
84 | raise "Bad tcp BrB url: '#{uri}'"
85 | end
86 | end
87 |
88 | end
89 | end
90 | end
91 |
92 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = BrB - Easy and Fast distributed ruby
2 |
3 | BrB is a simple, fully transparent and extremely fast interface for doing simple distributed Ruby.
4 | The core of the architecture is provided by EventMachine (a fast and reliable IO event library).
5 |
6 | BrB was built in order to achieve these 4 main goals :
7 | * Simple and fast message passing between distant Ruby processes.
8 | * Message passing with of return values when needed.
9 | * Being extremely fast in order to handle more than a few thousand messages per second.
10 | * Being completely transparent for developer.
11 |
12 | The principle is simple and inspired from Drb (standard distributed ruby library) :
13 | A process exposes an object over the network and any ruby process (after having established a connection tunnel) can directly call a method on the exposed object. BrB handles that part, so it’s fully transparent in the Ruby code.
14 |
15 | BrB only support message passing with Marshable dumpable object : String, symbol, Array, hash, Number, Object etc...
16 | That mean you can not send file descriptor, Thread or another funky things like that :)
17 |
18 | For any question, use the Ruby BrB google group: http://groups.google.com/group/ruby-brb
19 |
20 | == Main Functionalities
21 |
22 | * Unlimited objects exposed
23 | * Processes can expose object and be client to exposed object too at the same time
24 | * Do not wait for return by default : just do simple message passing
25 | * Handle return values without blocking with the usage of a simple block
26 | * Blocking wait for a return value if needed by simply adding _block at the end of the method name
27 | * Transmission of Exception when blocking call
28 | * Thread safe if used correctly with Event Machine
29 |
30 | == How it works
31 |
32 | First of all, a process declare himself as a sever and expose any object on a given address and port.
33 | Then, any number of distant processes can create a Tunnel with that server and can expose an object in exchange too.
34 | After connection are ready, just call method on the tunnel. It will just act like normal method calling on the exposed object !
35 |
36 | == What BrB is designed for ?
37 |
38 | * Doing Simple message passing between ruby process.
39 | * Connecting hundred of ruby process transparently.
40 | * Building a real-time scalable (game) server
41 | * Taking important load on a server easily just by distributing the load on multiple BrB instance.
42 | * Taking advantage of multi-core and multi-threaded systems.
43 |
44 | == TODO
45 | * Writing more examples
46 | * Publish Benchmarks VS drb
47 | * Improve logging mechanism
48 | * Clean up
49 |
50 | == Contributors
51 |
52 | * kwi (Guillaume Luccisano)
53 | * bwalton (Brian Walton)
54 | * dpree (Jens Bissinger)
55 |
56 |
57 | Copyright (c) 2009-2010 Guillaume Luccisano - g-mai|: guillaume.luccisano, released under the MIT license
58 |
--------------------------------------------------------------------------------
/lib/brb/request.rb:
--------------------------------------------------------------------------------
1 | module BrB
2 | module Request
3 |
4 | MessageRequestCode = :s
5 | CallbackRequestCode = :c
6 | ReturnCode = :r
7 |
8 | def is_brb_request_blocking?(meth)
9 | if m = meth.to_s and m.rindex('_block') == (m.size - 6)
10 | return true
11 | end
12 | nil
13 | end
14 |
15 | # Execute a request on a distant object
16 | def new_brb_out_request(meth, *args, &blck)
17 | Thread.current[:brb_nb_out] ||= 0
18 | Thread.current[:brb_nb_out] += 1
19 |
20 | raise BrBCallbackWithBlockingMethodException.new if is_brb_request_blocking?(meth) and block_given?
21 |
22 | block = (is_brb_request_blocking?(meth) or block_given?) ? Thread.current.to_s.to_sym : nil
23 | if block
24 | args << block
25 | args << Thread.current[:brb_nb_out]
26 | end
27 |
28 | if block_given?
29 | # Simulate a method with _block in order to make BrB send the answer
30 | meth = "#{meth}_block".to_sym
31 | end
32 |
33 | args.size > 0 ? brb_send([MessageRequestCode, meth, args]) : brb_send([MessageRequestCode, meth])
34 |
35 | if block_given?
36 | # Declare the callback
37 | declare_callback(block, Thread.current[:brb_nb_out], &blck)
38 |
39 | elsif block # Block until the request return
40 |
41 | #TimeMonitor.instance.watch_thread!(@timeout_rcv_value || 45)
42 | begin
43 | r = recv(block, Thread.current[:brb_nb_out], &blck)
44 | rescue Exception => e
45 | raise e
46 | ensure
47 | #TimeMonitor.instance.remove_thread!
48 | end
49 | if r.kind_of? Exception
50 | raise r
51 | end
52 | return r
53 | end
54 |
55 | nil
56 | end
57 |
58 | # Execute a request on the local object
59 | def new_brb_in_request(meth, *args)
60 |
61 | if is_brb_request_blocking?(meth)
62 |
63 | m = meth.to_s
64 | m = m[0, m.size - 6].to_sym
65 |
66 | idrequest = args.pop
67 | thread = args.pop
68 | begin
69 | r = ((args.size > 0) ? @object.send(m, *args) : @object.send(m))
70 | brb_send([ReturnCode, r, thread, idrequest])
71 | rescue Exception => e
72 | brb_send([ReturnCode, e, thread, idrequest])
73 | BrB.logger.error e.to_s
74 | BrB.logger.error e.backtrace.join("\n")
75 | #raise e
76 | end
77 | else
78 |
79 | begin
80 | (args.size > 0) ? @object.send(meth, *args) : @object.send(meth)
81 | rescue Exception => e
82 | BrB.logger.error "#{e.to_s} => By calling #{meth} on #{@object.class} with args : #{args.inspect}"
83 | BrB.logger.error e.backtrace.join("\n")
84 | raise e
85 | end
86 |
87 | end
88 |
89 | end
90 |
91 | end
92 | end
--------------------------------------------------------------------------------
/lib/brb/tunnel.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'eventmachine'
3 | require File.join(File.dirname(__FILE__), 'request.rb')
4 | require File.join(File.dirname(__FILE__), 'tunnel', 'shared.rb')
5 |
6 | module BrB
7 | module Tunnel
8 |
9 | # Create a BrB Tunnel by connecting to a distant BrB service
10 | # Pass a block if you want to get register and unregister events
11 | # The first parameter object is the object you want to expose in the BrB tunnel
12 | def self.create(object, uri = nil, opts = {}, &block)
13 | BrB::Protocol.open(uri, BrB::Tunnel::Handler, opts.merge(:object => object, :block => block))
14 | end
15 |
16 | # Brb interface Handler for Tunnel over Event machine
17 | class Handler < ::EventMachine::Connection
18 | attr_reader :uri
19 |
20 | include BrB::Request
21 | include BrB::Tunnel::Shared
22 |
23 | def initialize(opts = {})
24 | super
25 | @object = opts[:object]
26 | @verbose = opts[:verbose]
27 | BrB.logger.level = @verbose ? Logger::INFO : Logger::WARN
28 | @timeout_rcv_value = opts[:timeout] || 30 # Currently not implemented due to the lack of performance of ruby Timeout
29 | @close_after_timeout = opts[:close_after_timeout] || false
30 | @uri = opts[:uri]
31 | @replock = Mutex.new
32 | @responses = {}
33 | @block = opts[:block]
34 |
35 | @queue = Queue.new
36 | @buffer = ''
37 |
38 | # Callbacks handling :
39 | @callbacks = {}
40 | @callbacks_mutex = Mutex.new
41 | end
42 |
43 | # EventMachine Callback, called after connection has been initialized
44 | def post_init
45 | BrB.logger.info " [BrB] Tunnel initialized on #{@uri}"
46 | @active = true
47 | if @block
48 | EM.defer do
49 | @block.call(:register, self)
50 | end
51 | end
52 | end
53 |
54 | def close_connection(after_writing = false)
55 | @active = false
56 | super
57 | end
58 |
59 | # EventMachine unbind event
60 | # The connection has been closed
61 | def unbind
62 | BrB.logger.info ' [BrB] Tunnel service closed'
63 | @active = false
64 | if @block
65 | EM.defer do
66 | @block.call(:unregister, self)
67 | end
68 | end
69 | end
70 |
71 | # Stop the service
72 | def stop_service
73 | BrB.logger.info ' [BrB] Stopping Tunnel service...'
74 | @active = false
75 | EM.schedule do
76 | close_connection
77 | end
78 | end
79 |
80 | # Return true if the tunnel is currently active
81 | def active?
82 | @active
83 | end
84 |
85 | # When no method is found on tunnel interface, create an brb out request
86 | def method_missing(meth, *args, &block)
87 | return nil if !@active
88 | new_brb_out_request(meth, *args, &block)
89 | end
90 | end
91 | end
92 |
93 | end
--------------------------------------------------------------------------------
/spec/brb/brb_massive_usage_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe :brb_massive_usage do
4 | before(:all) do
5 | @brb = BrB::Service
6 | @brb.stop_service
7 | @brb_test = BrBTest.new
8 | open_service(@brb_test)
9 | @clients = []
10 | 20.times do
11 | @clients << connect_to_the_service(self, @brb.uri) do |type, tunnel|
12 | end
13 | end
14 | end
15 |
16 | def random_client
17 | @clients[rand(@clients.size)]
18 | end
19 |
20 | # Start the service
21 | it "should the service be started" do
22 | @clients.each do |cl|
23 | cl.should_not be_nil
24 | cl.active?.should be_true
25 | cl.uri.should == @brb.uri
26 | end
27 | end
28 |
29 | it "should works with massive simple messaging" do
30 | nb_call_before = @brb_test.nb_call || 0
31 | nb_call_to_do = 500
32 |
33 | @clients.each do |cl|
34 | nb_call_to_do.times do
35 | cl.noarg
36 | end
37 | end
38 |
39 | sleep 5
40 | # Wait a little in order to be sure all the stack is processed
41 | @brb_test.last_call.should == :noarg
42 | @brb_test.nb_call.should == (nb_call_to_do * @clients.size) + nb_call_before
43 | end
44 |
45 | it "should works with massive callbacks" do
46 | block_called = 0
47 | nb_callbacks = 1000
48 | nb_callbacks.times do |i|
49 | random_client.return_same_value(i) do |callback_return_value|
50 | callback_return_value.should == i
51 | block_called += 1
52 | end
53 | end
54 |
55 | sleep 2
56 | # Wait a little in order to be sure the method is called
57 | @brb_test.last_call.should == :return_same_value
58 | block_called.should == nb_callbacks
59 | end
60 |
61 | it "should works with massive simple messaging including blocking messaging and callbacks" do
62 | nb_call_before = @brb_test.nb_call || 0
63 | nb_call_to_do = 500
64 | nb_call_blocking_to_do = 50
65 |
66 | t = Thread.new do
67 | @clients.each do |cl|
68 | nb_call_blocking_to_do.times do
69 | val = Time.now.to_f
70 | cl.return_same_value_block(val).should == val
71 | end
72 | end
73 | end
74 |
75 | block_called = 0
76 | nb_callbacks = 1000
77 | nb_callbacks.times do |i|
78 | random_client.return_same_value(i) do |callback_return_value|
79 | callback_return_value.should == i
80 | block_called += 1
81 | end
82 | end
83 |
84 |
85 | @clients.each do |cl|
86 | nb_call_to_do.times do
87 | cl.noarg
88 | end
89 | end
90 |
91 | sleep 5
92 | block_called.should == nb_callbacks
93 | t.join
94 | # Wait a little in order to be sure all the stack is processed
95 | @brb_test.nb_call.should == nb_callbacks + (nb_call_to_do * @clients.size + nb_call_blocking_to_do * @clients.size) + nb_call_before
96 | end
97 |
98 | # Finally, stop the service
99 | it "should stop the service after usage" do
100 | @brb.stop_service
101 | @brb.uri.should be_nil
102 | end
103 | end
104 |
--------------------------------------------------------------------------------
/lib/brb/tunnel/shared.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | module BrB
4 | module Tunnel
5 | module Shared
6 | def make_proxy(r)
7 | if r.is_a?(Array)
8 | t = []
9 | r.each do |obj|
10 | t << if obj.is_a? Array
11 | make_proxy(obj)
12 | elsif !obj.is_a?(Symbol) and !obj.is_a?(String) and obj and !(Marshal::dump(obj) rescue nil)
13 | #BrB.logger.debug " - > Make proxy for : #{obj.class}"
14 | obj.to_s.to_sym
15 | else
16 | obj
17 | end
18 | end
19 | return t
20 | else
21 | return r.to_s
22 | end
23 | end
24 |
25 | def brb_send(r)
26 | return nil if !@active
27 | s = Marshal::dump(r) rescue Marshal::dump(make_proxy(r))
28 |
29 | s = [s.size].pack('N') + s
30 | EM.schedule do
31 | send_data s
32 | end
33 | end
34 |
35 | SizeOfPackedInt = [1].pack('N').size
36 |
37 | def load_request
38 | return nil if @buffer.size < SizeOfPackedInt
39 | len = @buffer.unpack('N').first + SizeOfPackedInt
40 | if @buffer.size < len
41 | return nil
42 | end
43 |
44 | obj = Marshal::load(@buffer[SizeOfPackedInt, len])
45 | @buffer.slice!(0,len)
46 | return obj
47 | end
48 |
49 | def receive_data(data)
50 | @buffer << data
51 |
52 | while obj = load_request
53 | if obj[0] == BrB::Request::ReturnCode
54 |
55 | # Return if we have a callback handling the return :
56 | next if treat_callback_return(obj[1], obj[2], obj[3])
57 |
58 | # No callback, so blocking thread is waiting :
59 | @replock.lock
60 | @responses[obj[2]] ||= Queue.new
61 | @replock.unlock
62 | @responses[obj[2]] << [obj[1], obj[3]]
63 | else
64 | @queue << obj
65 |
66 | EM.defer do
67 | treat_request(@queue.pop)
68 | end
69 |
70 | end
71 | end
72 | end
73 |
74 | def treat_request(obj)
75 | if obj.size == 2
76 | new_brb_in_request(obj[1])
77 | else
78 | new_brb_in_request(obj[1], *(obj.last))
79 | end
80 | end
81 |
82 | # Declare a new callback to call for a given request
83 | # Thread safe code
84 | def declare_callback(key, nb_out, &block)
85 | @callbacks_mutex.lock
86 |
87 | @callbacks[key] ||= {}
88 | @callbacks[key][nb_out] = block
89 |
90 | ensure
91 | @callbacks_mutex.unlock
92 | end
93 |
94 | # Return associated callback if present
95 | # And if present, delete the associate callback from the table
96 | # Thread safe code
97 | def get_callback(key, nb_out)
98 | @callbacks_mutex.lock
99 |
100 | if @callbacks[key] and b = @callbacks[key].delete(nb_out)
101 | return b
102 | end
103 |
104 | ensure
105 | @callbacks_mutex.unlock
106 | end
107 |
108 | # Call a callback if present, return true if exists
109 | # Non blocking action, use EM.defer
110 | def treat_callback_return(ret, key, nb_out)
111 |
112 | if b = get_callback(key, nb_out)
113 | EM.defer do
114 | # With arity, handle multiple block arguments or no arguments
115 | b.arity == 1 ? b.call(ret) : (b.arity == 0 ? b.call : b.call(*ret))
116 | end
117 |
118 | # A callback has been found and called, return true
119 | return true
120 | end
121 |
122 | # No callback, do nothing
123 | return nil
124 | end
125 |
126 | # Blocking method that wait on the @responses table an answer
127 | def recv(key, nb_out)
128 | begin
129 | @replock.lock
130 | r = @responses[key] ||= Queue.new
131 | @replock.unlock
132 | while rep = r.pop
133 | if rep[1] == nb_out # On check ke c'est bien la réponse que l'on attend
134 |
135 | # Call the callback
136 | if block_given?
137 | yield(rep[0])
138 | end
139 |
140 | return rep[0]
141 | end
142 | if rep[1] > nb_out
143 | return nil
144 | end
145 | end
146 | rescue Exception => e
147 | if @close_after_timeout == true
148 | stop_service
149 | sleep 1
150 | raise e
151 | else
152 | raise e
153 | end
154 | end
155 | end
156 | end
157 | end
158 | end
--------------------------------------------------------------------------------
/spec/brb/brb_tunnel_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | $last_unregistered = nil
4 | $last_registered = nil
5 |
6 | describe :brb_tunnel do
7 | before(:all) do
8 | @brb = BrB::Service
9 | @brb.stop_service
10 | @brb_test = BrBTest.new
11 | open_service(@brb_test)
12 | @client = connect_to_the_service(self, @brb.uri) do |type, tunnel|
13 | if type == :unregister
14 | $last_unregistered = tunnel
15 | elsif type == :register
16 | $last_registered = tunnel
17 | end
18 | end
19 | end
20 |
21 | # Start the service
22 | it "should the service be started" do
23 | @client.should_not be_nil
24 | @client.active?.should be_true
25 | @client.uri.should == @brb.uri
26 | end
27 |
28 | it "should have get the register message" do
29 | sleep 0.2 # Be sure to get the message
30 | $last_registered.class.should == @client.class
31 | end
32 |
33 | it "should correctly call simple distant method without args and without return" do
34 | @client.noarg
35 | sleep 0.2
36 | # Wait a little in order to be sure the method is called
37 | @brb_test.last_call.should == :noarg
38 | end
39 |
40 | it "should correctly call simple distant method without args and without return multipe times" do
41 | nb_call_before = @brb_test.nb_call
42 | nb_call_to_do = 50
43 |
44 | nb_call_to_do.times do
45 | @client.noarg
46 | end
47 | sleep 0.2
48 | # Wait a little in order to be sure the method is called
49 | @brb_test.last_call.should == :noarg
50 | @brb_test.nb_call.should == nb_call_to_do + nb_call_before
51 | end
52 |
53 | it "should correctly call distant method with one argument" do
54 | @client.very_long(:hello)
55 | sleep 0.2
56 | # Wait a little in order to be sure the method is called
57 | @brb_test.last_args.should == [:hello]
58 | end
59 |
60 | it "should correctly call distant method with multiple arguments" do
61 | args = [:one, :two, 3, "four"]
62 | @client.fourargs(*args)
63 | sleep 0.2
64 | # Wait a little in order to be sure the method is called
65 | @brb_test.last_args.should == args
66 | end
67 |
68 | it "should correctly return arguments symbol value" do
69 | @client.one_arg_with_return_block(:hello).should == :hello
70 | end
71 |
72 | it "should correctly return arguments string value" do
73 | @client.one_arg_with_return_block('hello').should == 'hello'
74 | end
75 |
76 | it "should correctly return arguments Fixnum value" do
77 | @client.one_arg_with_return_block(42).should == 42
78 | end
79 |
80 | it "should correctly return arguments Float value" do
81 | @client.one_arg_with_return_block(42.42).should == 42.42
82 | end
83 |
84 | it "should correctly return arguments Table value" do
85 | @client.one_arg_with_return_block([:one, :two, 3, "four"]).should == [:one, :two, 3, "four"]
86 | end
87 |
88 | it "should correctly return arguments Hash value" do
89 | h = {:yoyo => :titi, "salut" => 45}
90 | @client.one_arg_with_return_block(h).should == h
91 | end
92 |
93 | it "should correctly return multiple values" do
94 | r1, r2 = @client.return_same_value_twice_block(:ret, :ret2)
95 | r1.should == :ret
96 | r2.should == :ret2
97 | end
98 |
99 | it "should dump to symbol undumpable value by using the proxy" do
100 | @client.return_same_value_block(Thread.current).class.should == Symbol
101 | @client.return_same_value_block(Thread.current).should == Thread.current.to_s.to_sym
102 | end
103 |
104 | it "should transmit with success exception when blocking" do
105 | e = nil
106 | begin
107 | @client.notavalidmeth_block
108 | rescue Exception => e
109 | end
110 | e.should be_a NameError
111 | end
112 |
113 | it "should use block as non blocking callback with return value" do
114 | block_called = nil
115 | @client.return_same_value(:arg) do |v|
116 | v.should == :arg
117 | block_called = true
118 | end
119 | sleep 0.2
120 | # Wait a little in order to be sure the method is called
121 | @brb_test.last_call.should == :return_same_value
122 | block_called.should == true
123 | end
124 |
125 | it "should correctly handle multiple values return with callbacks" do
126 | block_called = nil
127 | @client.return_same_value_twice(:ret, :ret2) do |r1, r2|
128 | r1.should == :ret
129 | r2.should == :ret2
130 | block_called = true
131 | end
132 | sleep 0.2
133 | # Wait a little in order to be sure the method is called
134 | @brb_test.last_call.should == :return_same_value_twice
135 | block_called.should == true
136 | end
137 |
138 | it "should correctly handle no block args return with callbacks" do
139 | block_called = nil
140 | @client.return_same_value_twice(:ret, :ret2) do
141 | block_called = true
142 | end
143 | sleep 0.2
144 | # Wait a little in order to be sure the method is called
145 | @brb_test.last_call.should == :return_same_value_twice
146 | block_called.should == true
147 | end
148 |
149 | it "should raise an exception when calling a blocking method with a callback" do
150 | e = nil
151 | begin
152 | @client.return_same_value_block(:arg) do |v|
153 | end
154 | rescue Exception => e
155 | end
156 | e.should_not be_nil
157 | end
158 |
159 | # Finally, stop the service
160 | it "should stop the service after usage" do
161 | @brb.stop_service
162 | @brb.uri.should be_nil
163 | end
164 |
165 | # Finally, stop the client tunnel
166 | it "should stop the tunnel after usage" do
167 | @client.stop_service
168 | @client.active?.should_not be_true
169 | end
170 |
171 | it "should have get the unregister message" do
172 | sleep 0.2 # Be sure to get the message
173 | $last_unregistered.class.should == @client.class
174 | end
175 | end
176 |
177 |
--------------------------------------------------------------------------------