emrpc 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.
data/README ADDED
@@ -0,0 +1,48 @@
1
+ EMRPC is a EventMachine-based remote procedure call library.
2
+ It looks like DRb, but is much more efficient and provides
3
+ asynchronous interface along with blocking synchronous interface.
4
+
5
+ Author: Oleg Andreev <oleganza@gmail.com>
6
+
7
+ HELLO WORLD
8
+
9
+ class HelloWorld
10
+ def action
11
+ "Hello!"
12
+ end
13
+ end
14
+
15
+ server = EMRPC::Server.new(:host => '0.0.0.0',
16
+ :port => 9000,
17
+ :object => HelloWorld.new)
18
+
19
+ EM::run do
20
+ server.run
21
+ end
22
+
23
+ client = EMRPC::Client.new(:host => "localhost", :port => 9000)
24
+ client.action == "Hello!" #=> true
25
+
26
+
27
+ THE MIT LICENSE
28
+
29
+ Copyright (c) 2008, Oleg Andreev <oleganza@gmail.com>
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining
32
+ a copy of this software and associated documentation files (the
33
+ "Software"), to deal in the Software without restriction, including
34
+ without limitation the rights to use, copy, modify, merge, publish,
35
+ distribute, sublicense, and/or sell copies of the Software, and to
36
+ permit persons to whom the Software is furnished to do so, subject to
37
+ the following conditions:
38
+
39
+ The above copyright notice and this permission notice shall be
40
+ included in all copies or substantial portions of the Software.
41
+
42
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
43
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
45
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
46
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
47
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
48
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,96 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+ require "rake/gempackagetask"
4
+ require "rake/rdoctask"
5
+ require "rake/testtask"
6
+ require "spec/rake/spectask"
7
+ require "fileutils"
8
+
9
+ def __DIR__
10
+ File.dirname(__FILE__)
11
+ end
12
+
13
+ include FileUtils
14
+
15
+ require "lib/emrpc/version"
16
+
17
+ def sudo
18
+ ENV['EMRPC_SUDO'] ||= "sudo"
19
+ sudo = windows? ? "" : ENV['EMRPC_SUDO']
20
+ end
21
+
22
+ def windows?
23
+ (PLATFORM =~ /win32|cygwin/) rescue nil
24
+ end
25
+
26
+ def install_home
27
+ ENV['GEM_HOME'] ? "-i #{ENV['GEM_HOME']}" : ""
28
+ end
29
+
30
+ ##############################################################################
31
+ # Packaging & Installation
32
+ ##############################################################################
33
+ CLEAN.include ["**/.*.sw?", "pkg", "lib/*.bundle", "*.gem", "doc/rdoc", ".config", "coverage", "cache"]
34
+
35
+ desc "Run the specs."
36
+ task :default => :specs
37
+
38
+ task :emrpc => [:clean, :rdoc, :package]
39
+
40
+ RUBY_FORGE_PROJECT = "emrpc"
41
+ PROJECT_URL = "http://strokedb.com"
42
+ PROJECT_SUMMARY = "Efficient RPC library with evented and blocking APIs. In all ways better than DRb."
43
+ PROJECT_DESCRIPTION = PROJECT_SUMMARY
44
+
45
+ AUTHOR = "Oleg Andreev"
46
+ EMAIL = "oleganza@gmail.com"
47
+
48
+ GEM_NAME = "emrpc"
49
+ PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
50
+ GEM_VERSION = EMRPC::VERSION + PKG_BUILD
51
+
52
+ RELEASE_NAME = "REL #{GEM_VERSION}"
53
+
54
+ require "extlib/tasks/release"
55
+
56
+ spec = Gem::Specification.new do |s|
57
+ s.name = GEM_NAME
58
+ s.version = GEM_VERSION
59
+ s.platform = Gem::Platform::RUBY
60
+ s.author = AUTHOR
61
+ s.email = EMAIL
62
+ s.homepage = PROJECT_URL
63
+ s.summary = PROJECT_SUMMARY
64
+ s.bindir = "bin"
65
+ s.description = s.summary
66
+ s.executables = %w( )
67
+ s.require_path = "lib"
68
+ s.files = %w( README Rakefile TODO ) + Dir["{docs,bin,spec,lib,examples,script}/**/*"]
69
+
70
+ # rdoc
71
+ s.has_rdoc = true
72
+ s.extra_rdoc_files = %w( README TODO )
73
+ #s.rdoc_options += RDOC_OPTS + ["--exclude", "^(app|uploads)"]
74
+
75
+ # Dependencies
76
+ s.add_dependency "eventmachine"
77
+ s.add_dependency "rake"
78
+ s.add_dependency "rspec"
79
+ # Requirements
80
+ s.requirements << "You need to install the json (or json_pure), yaml, rack gems to use related features."
81
+ s.required_ruby_version = ">= 1.8.4"
82
+ end
83
+
84
+ Rake::GemPackageTask.new(spec) do |package|
85
+ package.gem_spec = spec
86
+ end
87
+
88
+ desc "Run :package and install the resulting .gem"
89
+ task :install => :package do
90
+ sh %{#{sudo} gem install #{install_home} --local pkg/#{NAME}-#{EMRPC::VERSION}.gem --no-rdoc --no-ri}
91
+ end
92
+
93
+ desc "Run :clean and uninstall the .gem"
94
+ task :uninstall => :clean do
95
+ sh %{#{sudo} gem uninstall #{NAME}}
96
+ end
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+ reconnecting client
2
+ rack http server & http client
3
+ object serialization:
4
+ self (special case, return reference to the running )
5
+ other
6
+ unix sockets interface
7
+ resource discovery
8
+ ring/chord protocol
@@ -0,0 +1,28 @@
1
+ module EMRPC
2
+ class Client
3
+ DEFAULT_TIMEOUT = 5 # 5 sec.
4
+ DEFAULT_PROTOCOL = :ClientProtocol # Default EventMachine connection protocol
5
+ DEFAULT_CONNECTIONS = 10 # 10 threads can operate concurrently, others will wait.
6
+
7
+ attr_reader :host, :port, :protocol, :timeout, :connections
8
+ # Create a regular object holding configuration,
9
+ # but returns a method proxy.
10
+ def self.new(*args, &blk)
11
+ client = super(*args)
12
+ backend = MultithreadedClient.new(:backends => client.connections,
13
+ :timeout => client.timeout)
14
+ MethodProxy.new(backend)
15
+ end
16
+
17
+ def initialize(options = {})
18
+ @host = options[:host] or raise ":host required!"
19
+ @port = options[:port] or raise ":port required!"
20
+ @timeout = options[:timeout] || DEFAULT_TIMEOUT
21
+ @protocol = options[:protocol] || DEFAULT_PROTOCOL
22
+ @connections = Array.new(options.delete(:connections) || DEFAULT_CONNECTIONS) do
23
+ ClientConnection.new(options)
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ module EMRPC
2
+ module EmConnection
3
+ # Connection initialization hook,
4
+ def post_init
5
+ end
6
+
7
+ # Connection being safely established (post_init was already called).
8
+ def connection_completed
9
+ end
10
+
11
+ # Connection was closed.
12
+ def unbind
13
+ end
14
+
15
+ # Raw receive data callback.
16
+ def receive_data(data)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,111 @@
1
+ module EMRPC
2
+ # Receives data with a 4-byte integer size prefix (network byte order).
3
+ # Underlying protocol must implement #send_data and invoke #receive_data.
4
+ # User's protocol must call #send_fast_message and listen to #receive_fast_message callback.
5
+ module FastMessageProtocol
6
+ def post_init
7
+ @fmp_size = 0 # if 0, we're waiting for a new message,
8
+ # else - accumulating data.
9
+ @fmp_size_chunk = "" # we store a part of size chunk here
10
+ @fmp_data = ""
11
+ super
12
+ end
13
+
14
+ LENGTH_FORMAT = "N".freeze
15
+ LENGTH_FORMAT_LENGTH = 4
16
+
17
+ def send_fast_message(data)
18
+ size = data.size
19
+ packed_size = [size].pack(LENGTH_FORMAT)
20
+ send_data packed_size
21
+ send_data data
22
+ end
23
+
24
+ def receive_data(next_chunk)
25
+ while true # helps fight deep recursion when receiving many messages in a single buffer.
26
+ data = next_chunk
27
+ # accumulate data
28
+ if @fmp_size > 0
29
+ left = @fmp_size - @fmp_data.size
30
+ now = data.size
31
+ log { "Need more #{left} bytes, got #{now} for now." }
32
+
33
+ if left > now
34
+ @fmp_data << data
35
+ break
36
+ elsif left == now
37
+ @fmp_data << data
38
+ data = @fmp_data
39
+ @fmp_data = ""
40
+ @fmp_size = 0
41
+ @fmp_size_chunk = ""
42
+ receive_fast_message(data)
43
+ break
44
+ else
45
+ # Received more, than expected.
46
+ # 1. Forward expected part
47
+ # 2. Put unexpected part into receive_data
48
+ @fmp_data << data[0, left]
49
+ next_chunk = data[left, now]
50
+ data = @fmp_data
51
+ @fmp_data = ""
52
+ @fmp_size = 0
53
+ @fmp_size_chunk = ""
54
+ log { "Returning #{data.size} bytes (#{data[0..32]})" }
55
+ receive_fast_message(data)
56
+ # (see while true: processing next chunk without recursive calls)
57
+ end
58
+
59
+ # get message size prefix
60
+ else
61
+ left = LENGTH_FORMAT_LENGTH - @fmp_size_chunk.size
62
+ now = data.size
63
+ log { "Need more #{left} bytes for size_chunk, got #{now} for now." }
64
+
65
+ if left > now
66
+ @fmp_size_chunk << data
67
+ break
68
+ elsif left == now
69
+ @fmp_size_chunk << data
70
+ @fmp_size = @fmp_size_chunk.unpack(LENGTH_FORMAT)[0]
71
+ log { "Ready to receive #{@fmp_size} bytes."}
72
+ break
73
+ else
74
+ # Received more, than expected.
75
+ # 1. Pick only expected part for length
76
+ # 2. Pass unexpected part into receive_data
77
+ @fmp_size_chunk << data[0, left]
78
+ next_chunk = data[left, now]
79
+ @fmp_size = @fmp_size_chunk.unpack(LENGTH_FORMAT)[0]
80
+ log { "Ready to receive #{@fmp_size} bytes."}
81
+ # (see while true) receive_data(next_chunk) # process next chunk
82
+ end # if
83
+ end # if
84
+ end # while true
85
+ end # def receive_data
86
+
87
+ def err
88
+ STDERR.write("FastMessageProtocol: #{yield}\n")
89
+ end
90
+ def log
91
+ puts("FastMessageProtocol: #{yield}")
92
+ end
93
+ # Switch logging off when not in debug mode.
94
+ unless $DEBUG || ENV['DEBUG']
95
+ def log
96
+ end
97
+ end
98
+ end # module FastMessageProtocol
99
+
100
+ # Allows to use send_message/receive_message without
101
+ # a knowledge of the particular messaging protocol.
102
+ module FastMessageProtocolAdapter
103
+ def send_message(msg)
104
+ send_fast_message(msg)
105
+ end
106
+ def receive_fast_message(msg)
107
+ receive_message(msg)
108
+ end
109
+ end
110
+
111
+ end # EMRPC
@@ -0,0 +1,30 @@
1
+ module EMRPC
2
+ module MarshalProtocol
3
+ # Creates new protocol using specified dump/load interface.
4
+ # Note: interface must be a constant! See examples below.
5
+ # Examples:
6
+ # 1. include MarshalProtocol.new(Marshal)
7
+ # 2. include MarshalProtocol.new(YAML)
8
+ # 3. include MarshalProtocol.new(JSON)
9
+ def self.new(marshal_const)
10
+ const_name = marshal_const.name
11
+ mod = Module.new
12
+ mod.class_eval <<-EOF, __FILE__, __LINE__
13
+ def send_marshalled_message(msg)
14
+ send_message(#{const_name}.dump(msg))
15
+ end
16
+ def receive_message(msg)
17
+ receive_marshalled_message(#{const_name}.load(msg))
18
+ rescue => e
19
+ rescue_marshal_error(e)
20
+ end
21
+ EOF
22
+ mod
23
+ end
24
+
25
+ # Prevent direct inclusion by accident.
26
+ def self.included(base)
27
+ raise "#{self} cannot be included directly! Create a module using #{self}.new(marshal_const)."
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,38 @@
1
+ require 'thread'
2
+ module EMRPC
3
+ # Sends all the messages to a specified backend
4
+ # FIXME: deal with Object's methods gracefully.
5
+ class MethodProxy
6
+ EMPTY_ARGS = [ ].freeze
7
+ attr_reader :__emrpc_backend
8
+ def initialize(backend)
9
+ @__emrpc_backend = backend
10
+ end
11
+
12
+ def method_missing(meth, *args, &blk)
13
+ @__emrpc_backend.send_message(meth, args, blk)
14
+ end
15
+
16
+ def id
17
+ @__emrpc_backend.send_message(:id, EMPTY_ARGS, nil)
18
+ end
19
+
20
+ def to_s
21
+ @__emrpc_backend.send_message(:to_s, EMPTY_ARGS, nil)
22
+ end
23
+
24
+ def to_str
25
+ @__emrpc_backend.send_message(:to_str, EMPTY_ARGS, nil)
26
+ end
27
+
28
+ alias :__class__ :class
29
+ def class
30
+ @__emrpc_backend.send_message(:class, EMPTY_ARGS, nil)
31
+ end
32
+
33
+ def inspect
34
+ "#<#{self.__class__.name}:0x#{__id__.to_s(16)} remote:#{@__emrpc_backend.send_message(:inspect, EMPTY_ARGS, nil)}>"
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ require 'thread'
2
+ module EMRPC
3
+ class PoolTimeout < StandardError; end
4
+ class MultithreadedClient
5
+ attr_reader :pool, :backends, :timeout
6
+ def initialize(options)
7
+ @backends = options[:backends] or raise "No backends supplied!"
8
+ @pool = options[:queue] || ::Queue.new
9
+ @timeout = options[:timeout] || 5
10
+ @timeout_thread = Thread.new { timer_action! }
11
+ @backends.each do |backend|
12
+ @pool.push(backend)
13
+ end
14
+ end
15
+
16
+ def send_message(meth, args, blk)
17
+ start = Time.now
18
+ # wait for the available connections here
19
+ while :timeout == (backend = @pool.pop)
20
+ seconds = Time.now - start
21
+ if seconds > @timeout
22
+ raise PoolTimeout, "Thread #{Thread.current} waited #{seconds} seconds for backend connection in a pool. Pool size is #{@backends.size}. Maybe too many threads are running concurrently. Increase the pool size or decrease the number of threads."
23
+ end
24
+ end
25
+ begin
26
+ backend.send_message(meth, args, blk)
27
+ ensure # Always push backend to a pool after using it!
28
+ @pool.push(backend)
29
+ end
30
+ end
31
+
32
+ # Pushes :timeout message to a queue for all
33
+ # the threads in a backlog every @timeout seconds.
34
+ def timer_action!
35
+ sleep @timeout
36
+ @pool.num_waiting.times { @pool.push(:timeout) }
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,46 @@
1
+ module EMRPC
2
+ # Reconnects when needed.
3
+ class ReconnectingClient
4
+ attr_reader :host, :port, :protocol
5
+ def initialize(options)
6
+ @host = options[:host]
7
+ @port = options[:port]
8
+ @protocol = options[:protocol]
9
+ @timeout = options[:timeout]
10
+
11
+ @connection = nil # diconnected at the start
12
+ end
13
+
14
+ def connection
15
+ return @connection if @connection
16
+
17
+ end
18
+
19
+ def send_data(data)
20
+ connection.send_data
21
+ end
22
+
23
+ def close_connection(after_writing = false)
24
+
25
+ end
26
+
27
+ module ReconnectingCallbacks
28
+ attr_accessor :reconnecting_client
29
+
30
+ def receive_data(data)
31
+
32
+ end
33
+
34
+ def connection_completed
35
+ @reconnecting_client.connection_completed
36
+ end
37
+
38
+ def unbind
39
+ @reconnecting_client.unbind
40
+ end
41
+ end
42
+ end # ReconnectingClient
43
+
44
+ # Raised when reconnection timeout is over.
45
+ class ReconnectionTimeout < StandardError; end
46
+ end
@@ -0,0 +1,18 @@
1
+ module EMRPC
2
+ class Server
3
+ attr_accessor :host, :port, :object, :protocol
4
+ def initialize(options = {})
5
+ @host = options[:host]
6
+ @port = options[:port]
7
+ @object = options[:object]
8
+ @protocol = options[:protocol] || ServerProtocol
9
+ end
10
+
11
+ def run
12
+ EventMachine.start_server(@host, @port, @protocol) do |conn|
13
+ conn.__served_object = @object
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ require 'thread'
2
+ module EMRPC
3
+ class ConnectionTimeout < StandardError; end
4
+ class SinglethreadedClient
5
+ def initialize(options)
6
+
7
+ end
8
+
9
+ def send_message(meth, args, blk)
10
+ start = Time.now
11
+ # TODO...
12
+
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module EMRPC
2
+ VERSION = "0.1"
3
+ end
data/lib/emrpc.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+
4
+ $LOAD_PATH.unshift( File.expand_path(File.join(File.dirname(__FILE__))))
5
+
6
+ require 'emrpc/version'
7
+ require 'emrpc/fast_message_protocol'
8
+ require 'emrpc/marshal_protocol'
9
+ require 'emrpc/server'
10
+ require 'emrpc/method_proxy'
11
+ require 'emrpc/multithreaded_client'
12
+ require 'emrpc/singlethreaded_client'
13
+ require 'emrpc/client'
@@ -0,0 +1,60 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe FastMessageProtocol do
4
+ before(:each) do
5
+ @peer_class = Class.new do
6
+ def initialize
7
+ post_init
8
+ end
9
+ def post_init
10
+ end
11
+ end
12
+ @oleganza = Class.new(@peer_class) do
13
+ include FastMessageProtocol
14
+ end
15
+ @yrashk = Class.new(@peer_class) do
16
+ include FastMessageProtocol
17
+ end
18
+ @oleganza.class_eval do
19
+ attr_accessor :messages
20
+ def post_init
21
+ @messages = []
22
+ super
23
+ end
24
+ def receive_fast_message(msg)
25
+ @messages << msg
26
+ end
27
+ end
28
+ @yrashk.class_eval do
29
+ attr_accessor :peer
30
+ def post_init
31
+ @buffer = ""
32
+ super
33
+ end
34
+ def send_data(data)
35
+ @buffer << data
36
+ flush_buffer if @buffer.size > (rand(300) + 1)
37
+ end
38
+ def flush_buffer
39
+ @peer.receive_data(@buffer.dup)
40
+ @buffer = ""
41
+ end
42
+ end
43
+ end
44
+
45
+ it "should receive all messages" do
46
+ messages = Array.new(1000) {|i| (i*(rand(100)+1)).to_s*(1+rand(100)) }
47
+ oleg = @oleganza.new
48
+ yr = @yrashk.new
49
+ yr.peer = oleg
50
+ messages.each {|m| yr.send_fast_message(m) }
51
+ yr.flush_buffer
52
+
53
+ oleg.messages.should == messages
54
+ end
55
+
56
+ it "should handle protocol errors" do
57
+ pending "Add message size limit!"
58
+ end
59
+
60
+ end
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ require 'json'
4
+ require 'yaml'
5
+
6
+ def create_marshal_protocol_instance(interface)
7
+ mod = MarshalProtocol.new(interface)
8
+
9
+ spec_module = Module.new do
10
+ def send_message(data)
11
+ data
12
+ end
13
+ def receive_marshalled_message(data)
14
+ data
15
+ end
16
+ def rescue_marshal_error(e)
17
+ [:error, e]
18
+ end
19
+ end
20
+
21
+ Class.new do
22
+ include mod
23
+ include spec_module
24
+ end.new
25
+ end
26
+
27
+ describe "Generic MarshalProtocol", :shared => true do
28
+ it "should pass data in and out" do
29
+ encoded = @instance.send_marshalled_message(@msg)
30
+ decoded = @instance.receive_message(encoded)
31
+ decoded.should == @msg
32
+ end
33
+
34
+ it "should report error when format is wrong" do
35
+ encoded = @instance.send_marshalled_message(@msg)
36
+ decoded = @instance.receive_message("blah-blah"+encoded)
37
+ decoded.first.should == :error
38
+ end
39
+ end
40
+
41
+ [Marshal, JSON, YAML].each do |interface|
42
+ describe MarshalProtocol, "with #{interface}" do
43
+ before(:each) do
44
+ # FIXME: fixture containing 3.1415 may cause floating point issues.
45
+ @msg = ["Hello", {"a" => "b", "arr" => [true, false, nil]}, 1, 3.1415]
46
+ @instance = create_marshal_protocol_instance(interface)
47
+ end
48
+ it_should_behave_like "Generic MarshalProtocol"
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe MethodProxy do
4
+ before(:each) do
5
+ @backend = [ 1, 2, 3 ]
6
+ class <<@backend
7
+ def send_message(meth, args, blk)
8
+ send(meth, *args, &blk)
9
+ end
10
+ def id
11
+ object_id
12
+ end
13
+ end
14
+ @proxy = MethodProxy.new(@backend)
15
+ end
16
+
17
+ it "should proxy regular methods" do
18
+ @proxy.size.should == @backend.size
19
+ (@proxy*2).should == (@backend*2)
20
+ @proxy.join(':').should == @backend.join(':')
21
+ end
22
+
23
+ it "should proxy Object's methods" do
24
+ @proxy.to_s.should == @backend.to_s
25
+ lambda { @proxy.to_str.should == @backend.to_str }.should raise_error(NoMethodError) # to_str is undefined
26
+ @proxy.class.should == @backend.class
27
+ @proxy.id.should == @backend.id
28
+ end
29
+
30
+ it "should inspect" do
31
+ @proxy.inspect == %{#<#{MethodProxy}:0x#{@proxy.__id__.to_s(16)} remote:#{@backend.inspect}>}
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe MultithreadedClient, " with no timeouts" do
4
+ include ThreadHelpers
5
+ before(:each) do
6
+ @mod = Module.new do
7
+ def send_message(meth, args, blk)
8
+ send(meth, *args, &blk)
9
+ end
10
+ end
11
+ @backends = [ [ 1 ], [ 2 ], [ 3 ] ]
12
+ @backends.each{|b| b.extend(@mod) }
13
+
14
+ @client = MultithreadedClient.new(:backends => @backends, :timeout => 1)
15
+ end
16
+ it "should work with all backends" do
17
+ ts = create_threads(10) do
18
+ loop { @client.send_message(:[], [ 0 ], nil) }
19
+ end
20
+ sleep 3
21
+ ts.each {|t| t.kill}
22
+ end
23
+ end
24
+
25
+ describe MultithreadedClient, " with PoolTimeout" do
26
+ include ThreadHelpers
27
+ before(:each) do
28
+ @long_backend = Object.new
29
+ class <<@long_backend
30
+ def send_message(meth, args, blk)
31
+ sleep 0.5
32
+ end
33
+ end
34
+ @long_client = MultithreadedClient.new(:backends => [ @long_backend ], :timeout => 1)
35
+ end
36
+
37
+ it "should raise ThreadTimeout" do
38
+ ts = create_threads(50) do # some of them will die
39
+ @long_client.send_message(:some_meth, nil, nil)
40
+ end
41
+ create_threads(10, true) do
42
+ lambda {
43
+ @long_client.send_message(:some_meth, nil, nil)
44
+ }.should raise_error(PoolTimeout)
45
+ end
46
+ sleep 3
47
+ ts.each {|t| t.kill}
48
+ end
49
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe SinglethreadedClient do
4
+ include ThreadHelpers
5
+ before(:each) do
6
+ @backend= [1, 2, 3]
7
+ class <<@backend
8
+ def send_message(meth, args, blk)
9
+ send(meth, *args, &blk)
10
+ end
11
+ end
12
+ @client = SinglethreadedClient.new(:backend => @backend, :timeout => 1)
13
+ end
14
+
15
+ it "should work without errors with regular methods" do
16
+ @client.send_message(:size, nil, nil).should == 3
17
+ @client.send_message(:join, [":"], nil).should == "1:2:3"
18
+ @client.send_message(:map, nil, proc{|e| e.to_s}).should == %w[1 2 3]
19
+ end
20
+
21
+
22
+
23
+ end
@@ -0,0 +1,64 @@
1
+ $LOAD_PATH.unshift( File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) )
2
+
3
+ require 'emrpc'
4
+ include EMRPC
5
+
6
+ EM_HOST = ENV['EM_HOST'] || "127.0.0.1"
7
+ EM_PORT = (ENV['EM_PORT'] || 4567).to_i
8
+
9
+ module Fixtures
10
+ class Person
11
+ attr_accessor :name
12
+ def initialize(options = {})
13
+ @name = options[:name]
14
+ end
15
+ end
16
+
17
+ class Paris
18
+ attr_accessor :people, :options
19
+
20
+ def initialize(ppl, options = {})
21
+ @options = options
22
+ @people = Array.new(ppl){ Person.new }
23
+ @name = "Paris"
24
+ end
25
+
26
+ def translate(english_word)
27
+ "le #{english_word}" # :-)
28
+ end
29
+
30
+ def visit(person)
31
+ people << person
32
+ people.size
33
+ end
34
+
35
+ def run_exception
36
+ raise SomeException, "paris message"
37
+ end
38
+ class SomeException < Exception; end
39
+ end
40
+ end
41
+
42
+ module ThreadHelpers
43
+ def create_threads(n, abort_on_exception = false, &blk)
44
+ Array.new(n) do
45
+ t = Thread.new(&blk)
46
+ t.abort_on_exception = abort_on_exception
47
+ t
48
+ end
49
+ end
50
+ end
51
+
52
+ # Runs eventmachine reactor in a child thread,
53
+ # waits 0.5 sec. in a current thread.
54
+ # Returns child thread.
55
+ #
56
+ module EventMachine
57
+ def self.run_in_thread(delay = 0.5, &blk)
58
+ t = Thread.new do
59
+ EventMachine.run(&blk)
60
+ end
61
+ sleep delay
62
+ t
63
+ end
64
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Sync client" do
4
+
5
+ # Run server
6
+ before(:all) do
7
+ @em_thread = EM.run_in_thread do
8
+ paris = Fixtures::Paris.new(4, :cafes => ["quai-quai", "2 moulins"])
9
+ EMRPC::Server.new(:host => EM_HOST, :port => EM_PORT, :object => paris).run
10
+ end
11
+ end
12
+ after(:all) do
13
+ begin
14
+ EM.stop_event_loop
15
+ @em_thread.kill
16
+ rescue => e
17
+ puts "Exception after specs:"
18
+ puts e.inspect
19
+ end
20
+ end
21
+
22
+ # Run client
23
+ before(:each) do
24
+ @paris = EMRPC::Client.new(:host => EM_HOST, :port => EM_PORT)
25
+ # wrong port
26
+ @berlin = EMRPC::Client.new(:host => EM_HOST, :port => EM_PORT + 1)
27
+ # wrong host
28
+ @prague = EMRPC::Client.new(:host => "192.254.254.254", :port => EM_PORT)
29
+ end
30
+
31
+ it "should get serialized data" do
32
+ @paris.options.should == { :cafes => ["quai-quai", "2 moulins"] }
33
+ end
34
+
35
+ it "should issue connection error" do
36
+ lambda { @berlin.options }.should raise_error(EMRPC::ConnectionError)
37
+ end
38
+
39
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: emrpc
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Oleg Andreev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-18 00:00:00 +04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: eventmachine
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rake
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: rspec
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: Efficient RPC library with evented and blocking APIs. In all ways better than DRb.
46
+ email: oleganza@gmail.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README
53
+ - TODO
54
+ files:
55
+ - README
56
+ - Rakefile
57
+ - TODO
58
+ - spec/fast_message_protocol_spec.rb
59
+ - spec/marshal_protocol_spec.rb
60
+ - spec/method_proxy_spec.rb
61
+ - spec/multithreaded_client_spec.rb
62
+ - spec/singlethreaded_client_spec.rb
63
+ - spec/spec_helper.rb
64
+ - spec/sync_client_spec.rb
65
+ - lib/emrpc
66
+ - lib/emrpc/client.rb
67
+ - lib/emrpc/em_connection.rb
68
+ - lib/emrpc/fast_message_protocol.rb
69
+ - lib/emrpc/marshal_protocol.rb
70
+ - lib/emrpc/method_proxy.rb
71
+ - lib/emrpc/multithreaded_client.rb
72
+ - lib/emrpc/reconnecting_client.rb
73
+ - lib/emrpc/server.rb
74
+ - lib/emrpc/singlethreaded_client.rb
75
+ - lib/emrpc/version.rb
76
+ - lib/emrpc.rb
77
+ has_rdoc: true
78
+ homepage: http://strokedb.com
79
+ post_install_message:
80
+ rdoc_options: []
81
+
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: 1.8.4
89
+ version:
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ version:
96
+ requirements:
97
+ - You need to install the json (or json_pure), yaml, rack gems to use related features.
98
+ rubyforge_project:
99
+ rubygems_version: 1.2.0
100
+ signing_key:
101
+ specification_version: 2
102
+ summary: Efficient RPC library with evented and blocking APIs. In all ways better than DRb.
103
+ test_files: []
104
+