vitobotta-brb 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,22 @@
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
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'http://rubygems.org'
2
+ source 'http://gems.github.com'
3
+
4
+ gem "rspec", "~> 1.3.2"
5
+ gem "eventmachine"
data/Gemfile.lock ADDED
@@ -0,0 +1,13 @@
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)
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
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.
data/README.rdoc ADDED
@@ -0,0 +1,57 @@
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 <em>_block</em> 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 <em>Tunnel</em> 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
data/Rakefile ADDED
@@ -0,0 +1,23 @@
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
@@ -0,0 +1,40 @@
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
@@ -0,0 +1,29 @@
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
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ # encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), 'lib', 'brb.rb')
data/lib/brb.rb ADDED
@@ -0,0 +1,5 @@
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')
@@ -0,0 +1,91 @@
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
+
@@ -0,0 +1,9 @@
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
data/lib/brb/logger.rb ADDED
@@ -0,0 +1,35 @@
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
@@ -0,0 +1,98 @@
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
+ client_info = {
61
+ :ip_address => @ip_address,
62
+ :port => @port
63
+ }
64
+
65
+ if is_brb_request_blocking?(meth)
66
+
67
+ m = meth.to_s
68
+ m = m[0, m.size - 6].to_sym
69
+
70
+ idrequest = args.pop
71
+ thread = args.pop
72
+
73
+ args << client_info
74
+ begin
75
+ # r = ((args.size > 0) ? @object.send(m, *args) : @object.send(m))
76
+ r = @object.send(m, *args)
77
+ brb_send([ReturnCode, r, thread, idrequest])
78
+ rescue Exception => e
79
+ brb_send([ReturnCode, e, thread, idrequest])
80
+ BrB.logger.error e.to_s
81
+ BrB.logger.error e.backtrace.join("\n")
82
+ #raise e
83
+ end
84
+ else
85
+ args << client_info
86
+
87
+ begin
88
+ # (args.size > 0) ? @object.send(meth, *args) : @object.send(meth)
89
+ @object.send(meth, *args)
90
+ rescue Exception => e
91
+ BrB.logger.error "#{e.to_s} => By calling #{meth} on #{@object.class} with args : #{args.inspect}"
92
+ BrB.logger.error e.backtrace.join("\n")
93
+ raise e
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,56 @@
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
data/lib/brb/tunnel.rb ADDED
@@ -0,0 +1,95 @@
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
+ @port, @ip_address = Socket.unpack_sockaddr_in(get_peername)
46
+
47
+ BrB.logger.info " [BrB] Tunnel initialized on #{@uri}"
48
+ @active = true
49
+ if @block
50
+ EM.defer do
51
+ @block.call(:register, self)
52
+ end
53
+ end
54
+ end
55
+
56
+ def close_connection(after_writing = false)
57
+ @active = false
58
+ super
59
+ end
60
+
61
+ # EventMachine unbind event
62
+ # The connection has been closed
63
+ def unbind
64
+ BrB.logger.info ' [BrB] Tunnel service closed'
65
+ @active = false
66
+ if @block
67
+ EM.defer do
68
+ @block.call(:unregister, self)
69
+ end
70
+ end
71
+ end
72
+
73
+ # Stop the service
74
+ def stop_service
75
+ BrB.logger.info ' [BrB] Stopping Tunnel service...'
76
+ @active = false
77
+ EM.schedule do
78
+ close_connection
79
+ end
80
+ end
81
+
82
+ # Return true if the tunnel is currently active
83
+ def active?
84
+ @active
85
+ end
86
+
87
+ # When no method is found on tunnel interface, create an brb out request
88
+ def method_missing(meth, *args, &block)
89
+ return nil if !@active
90
+ new_brb_out_request(meth, *args, &block)
91
+ end
92
+ end
93
+ end
94
+
95
+ end
@@ -0,0 +1,158 @@
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
@@ -0,0 +1,35 @@
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
@@ -0,0 +1,103 @@
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
@@ -0,0 +1,29 @@
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
+
@@ -0,0 +1,176 @@
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
+
@@ -0,0 +1,54 @@
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
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vitobotta-brb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Guillaume Luccisano
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-06-26 00:00:00.000000000 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: eventmachine
17
+ requirement: &2156366080 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>'
21
+ - !ruby/object:Gem::Version
22
+ version: '0.12'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2156366080
26
+ description: BrB is a simple, fully transparent and extremely fast interface for doing
27
+ simple distributed ruby and message passing
28
+ email: guillaume.luccisano@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - examples/simple_client.rb
34
+ - examples/simple_core.rb
35
+ - lib/brb/event_machine.rb
36
+ - lib/brb/exception.rb
37
+ - lib/brb/logger.rb
38
+ - lib/brb/request.rb
39
+ - lib/brb/service.rb
40
+ - lib/brb/tunnel/shared.rb
41
+ - lib/brb/tunnel.rb
42
+ - lib/brb.rb
43
+ - spec/brb/brb_logger_spec.rb
44
+ - spec/brb/brb_massive_usage_spec.rb
45
+ - spec/brb/brb_service_spec.rb
46
+ - spec/brb/brb_tunnel_spec.rb
47
+ - spec/spec_helper.rb
48
+ - CHANGELOG.rdoc
49
+ - Gemfile
50
+ - Gemfile.lock
51
+ - MIT-LICENSE
52
+ - Rakefile
53
+ - README.rdoc
54
+ - init.rb
55
+ has_rdoc: true
56
+ homepage: http://github.com/kwi/BrB
57
+ licenses: []
58
+ post_install_message:
59
+ rdoc_options: []
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: 1.3.4
74
+ requirements:
75
+ - eventmachine
76
+ rubyforge_project: vitobotta-brb
77
+ rubygems_version: 1.6.2
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: BrB is a simple, fully transparent and extremely fast interface for doing
81
+ simple distributed ruby
82
+ test_files: []