oleganza-emrpc 0.3

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.
Files changed (67) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README +129 -0
  3. data/Rakefile +156 -0
  4. data/TODO +47 -0
  5. data/bin/emrpc +4 -0
  6. data/lib/emrpc.rb +15 -0
  7. data/lib/emrpc/archive/reference_savior.rb +48 -0
  8. data/lib/emrpc/archive/ring.rb +44 -0
  9. data/lib/emrpc/blocking_api.rb +3 -0
  10. data/lib/emrpc/blocking_api/method_proxy.rb +57 -0
  11. data/lib/emrpc/blocking_api/multithreaded_client.rb +52 -0
  12. data/lib/emrpc/blocking_api/singlethreaded_client.rb +68 -0
  13. data/lib/emrpc/client.rb +28 -0
  14. data/lib/emrpc/console.rb +32 -0
  15. data/lib/emrpc/evented_api.rb +14 -0
  16. data/lib/emrpc/evented_api/connection_mixin.rb +14 -0
  17. data/lib/emrpc/evented_api/debug_connection.rb +52 -0
  18. data/lib/emrpc/evented_api/debug_pid_callbacks.rb +39 -0
  19. data/lib/emrpc/evented_api/default_callbacks.rb +40 -0
  20. data/lib/emrpc/evented_api/evented_wrapper.rb +28 -0
  21. data/lib/emrpc/evented_api/local_connection.rb +48 -0
  22. data/lib/emrpc/evented_api/pid.rb +198 -0
  23. data/lib/emrpc/evented_api/protocol_mapper.rb +57 -0
  24. data/lib/emrpc/evented_api/reconnecting_pid.rb +105 -0
  25. data/lib/emrpc/evented_api/remote_connection.rb +73 -0
  26. data/lib/emrpc/evented_api/remote_pid.rb +38 -0
  27. data/lib/emrpc/evented_api/subscribable.rb +56 -0
  28. data/lib/emrpc/evented_api/timer.rb +23 -0
  29. data/lib/emrpc/protocols.rb +2 -0
  30. data/lib/emrpc/protocols/fast_message_protocol.rb +99 -0
  31. data/lib/emrpc/protocols/marshal_protocol.rb +33 -0
  32. data/lib/emrpc/server.rb +17 -0
  33. data/lib/emrpc/util.rb +7 -0
  34. data/lib/emrpc/util/blank_slate.rb +25 -0
  35. data/lib/emrpc/util/codec.rb +114 -0
  36. data/lib/emrpc/util/combine_modules.rb +11 -0
  37. data/lib/emrpc/util/em2rev.rb +48 -0
  38. data/lib/emrpc/util/em_start_stop_timeouts.rb +62 -0
  39. data/lib/emrpc/util/parsed_uri.rb +15 -0
  40. data/lib/emrpc/util/safe_run.rb +23 -0
  41. data/lib/emrpc/util/timers.rb +17 -0
  42. data/lib/emrpc/version.rb +3 -0
  43. data/spec/blocking_api/method_proxy_spec.rb +33 -0
  44. data/spec/blocking_api/multithreaded_client_spec.rb +52 -0
  45. data/spec/blocking_api/scenario_spec.rb +35 -0
  46. data/spec/blocking_api/singlethreaded_client_spec.rb +63 -0
  47. data/spec/blocking_api/spec_helper.rb +1 -0
  48. data/spec/blocking_api_test.rb +98 -0
  49. data/spec/evented_api/connection_mixin_spec.rb +34 -0
  50. data/spec/evented_api/default_callbacks_spec.rb +26 -0
  51. data/spec/evented_api/evented_wrapper_spec.rb +50 -0
  52. data/spec/evented_api/pid_spec.rb +194 -0
  53. data/spec/evented_api/reconnecting_pid_spec.rb +76 -0
  54. data/spec/evented_api/remote_connection_spec.rb +147 -0
  55. data/spec/evented_api/remote_pid_spec.rb +84 -0
  56. data/spec/evented_api/scenario_spec.rb +138 -0
  57. data/spec/evented_api/spec_helper.rb +10 -0
  58. data/spec/evented_api/subscribable_spec.rb +53 -0
  59. data/spec/server_spec.rb +7 -0
  60. data/spec/spec_helper.rb +96 -0
  61. data/spec/util/blank_slate_spec.rb +7 -0
  62. data/spec/util/codec_spec.rb +183 -0
  63. data/spec/util/fast_message_protocol_spec.rb +60 -0
  64. data/spec/util/marshal_protocol_spec.rb +50 -0
  65. data/spec/util/parsed_uri_spec.rb +19 -0
  66. data/spec/util/spec_helper.rb +1 -0
  67. metadata +164 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Oleg Andreev <oleganza@gmail.com>
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 ADDED
@@ -0,0 +1,129 @@
1
+ EMRPC is a EventMachine-based remote procedure call library.
2
+ It looks like DRb, but is much more efficient and provides
3
+ asynchronous erlang-like interface along with blocking synchronous interface.
4
+
5
+ Author: Oleg Andreev <oleganza@gmail.com>
6
+
7
+
8
+ FEATURES
9
+
10
+ 0. Object-oriented evented API based on lightweight processes called Pids.
11
+ 1. Automatically reconnecting clients (blocking and evented).
12
+ 2. Support for both TCP and Unix sockets. (Use "emrpc://host:port" or "unix:///path/to/my.sock")
13
+ 3. Modularity and good specs coverage: EMRPC is easy to learn, extend and optimize.
14
+
15
+
16
+ VERSION AND STATUS
17
+
18
+ This is v0.3 alpha. It is being used in several projects of my own, but is not tested on many interesting cases.
19
+ It has also several issues with performance and specs stability (see TODO file).
20
+
21
+
22
+ THANKS TO
23
+
24
+ * linkfeed.ru for the real-world EMRPC-based application.
25
+ * pierlis.com for the table/chair/wifi in Paris.
26
+
27
+
28
+ EXAMPLES
29
+
30
+ You may try out examples using bin/emrpc IRB session.
31
+ Just open the console and paste the code.
32
+
33
+ -------------------------------------------------------------------------------
34
+
35
+ HELLO WORLD (BLOCKING API)
36
+
37
+ class HelloWorld
38
+ def action
39
+ "Hello!"
40
+ end
41
+ end
42
+
43
+ server = EMRPC::Server.new(:address => 'emrpc://localhost:4000/',
44
+ :backend => HelloWorld.new)
45
+ EM::safe_run(:background) { }
46
+
47
+ server.start
48
+
49
+ client = EMRPC::Client.new('emrpc://localhost:4000/')
50
+ client.action == "Hello!" #=> true
51
+
52
+
53
+ -------------------------------------------------------------------------------
54
+
55
+ HELLO WORLD (EVENTED API)
56
+
57
+ class HelloWorld
58
+ include Pid
59
+ def action(sender)
60
+ puts "HelloWorld replies to the sender #{sender}..."
61
+ sender.reply(self, "Hello!")
62
+ end
63
+ end
64
+
65
+ class User
66
+ include Pid
67
+ def connected(pid)
68
+ puts "Pid #{pid} connected with the user."
69
+ pid.action(self)
70
+ end
71
+ def reply(pid, msg)
72
+ puts "Pid #{pid} replied: #{msg}"
73
+ end
74
+ end
75
+
76
+ EM::run do
77
+ hw = HelloWorld.new
78
+ hw.bind('emrpc://localhost:4000/') # bind a pid to the address
79
+
80
+ oleg = User.new
81
+ oleg.connect('emrpc://localhost:4000/') # connect to that address
82
+ end
83
+
84
+ # Output:
85
+ Pid #<EMRPC::RemotePid:0x143b740> connected with the user.
86
+ HelloWorld replies to the sender #<User:0x143b4d4>...
87
+ Pid #<HelloWorld:0x143d540> replied: Hello!
88
+
89
+ -------------------------------------------------------------------------------
90
+
91
+ HELLO WORLD (EVENTED WRAPPER, MIXED API)
92
+
93
+ # in first process:
94
+ class HelloWorld
95
+ def action
96
+ "Hello!"
97
+ end
98
+ end
99
+
100
+ server = EMRPC::Server.new(:address => 'emrpc://localhost:4000/',
101
+ :backend => HelloWorld.new)
102
+
103
+ EM::run do
104
+ server.start
105
+ end
106
+
107
+ # in the other process (actually, this works in the same process with the server too):
108
+ class User
109
+ include Pid
110
+ def connected(pid)
111
+ puts "Pid #{pid} connected to the user."
112
+ pid.send(self, :action)
113
+ end
114
+ def on_return(pid, msg)
115
+ puts "Pid #{pid} replied: #{msg}"
116
+ end
117
+ end
118
+
119
+ EM::run do
120
+ oleg = User.new
121
+ oleg.connect('emrpc://localhost:4000/') # connect to that address
122
+ end
123
+
124
+ # Output:
125
+ Pid #<EMRPC::RemotePid:0x143b740> connected to the user.
126
+ Pid #<HelloWorld:0x143d540> replied: Hello!
127
+
128
+ -------------------------------------------------------------------------------
129
+
data/Rakefile ADDED
@@ -0,0 +1,156 @@
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
+
10
+ ##############################################################################
11
+ # Automated tests
12
+ ##############################################################################
13
+
14
+ desc "Run specs"
15
+ task :spec => :'specs:spec'
16
+ task :specs => :'specs:spec'
17
+
18
+ namespace :specs do
19
+
20
+ def spec_path
21
+ path = ENV['SPECS_PATH'] || "spec"
22
+ puts "Using #{path.inspect} path. (See SPECS_PATH environment variable.)"
23
+ path
24
+ end
25
+
26
+ desc "Run specs"
27
+ task :spec do
28
+ system("spec #{spec_path} -c")
29
+ end
30
+
31
+ desc "Runs specs set by SPECS_PATH (default is 'spec') in a loop detecting random errors"
32
+ task :loop do
33
+ def run_spec_iteration(path = "spec", counter = 0)
34
+ r = `spec #{path}`
35
+ if r =~ /(\A|[.PFE])[FE]/ || r =~ /[FE]([.PFE]|\z)/
36
+ puts r
37
+ counter + 1
38
+ else
39
+ counter
40
+ end
41
+ end
42
+ path = spec_path
43
+ iters = (ENV['SPECS_ITERS'] || 10_000).to_i
44
+ puts "#{iters} iterations. (See SPECS_ITERS environment variable.)"
45
+ fs = 0
46
+ iters.times do |i|
47
+ puts "Iterations: #{i} Failures: #{fs}"
48
+ fs = run_spec_iteration(path, fs)
49
+ end
50
+ end
51
+ end
52
+
53
+
54
+ ##############################################################################
55
+ # Packaging & Installation
56
+ ##############################################################################
57
+
58
+ def __DIR__
59
+ File.dirname(__FILE__)
60
+ end
61
+
62
+ include FileUtils
63
+
64
+ require "lib/emrpc/version"
65
+
66
+ def sudo
67
+ ENV['EMRPC_SUDO'] ||= "sudo"
68
+ sudo = windows? ? "" : ENV['EMRPC_SUDO']
69
+ end
70
+
71
+ def windows?
72
+ (PLATFORM =~ /win32|cygwin/) rescue nil
73
+ end
74
+
75
+ def install_home
76
+ ENV['GEM_HOME'] ? "-i #{ENV['GEM_HOME']}" : ""
77
+ end
78
+
79
+ CLEAN.include ["**/.*.sw?", "pkg", "lib/*.bundle", "*.gem", "doc/rdoc", ".config", "coverage", "cache"]
80
+
81
+ desc "Run the specs."
82
+ task :default => :specs
83
+
84
+ task :emrpc => [:clean, :rdoc, :package]
85
+
86
+ RUBY_FORGE_PROJECT = "emrpc"
87
+ PROJECT_URL = "http://oleganza.com/"
88
+ PROJECT_SUMMARY = "Efficient RPC library with evented and blocking APIs. In all ways better than DRb."
89
+ PROJECT_DESCRIPTION = PROJECT_SUMMARY
90
+
91
+ AUTHOR = "Oleg Andreev"
92
+ EMAIL = "oleganza@gmail.com"
93
+
94
+ GEM_NAME = "emrpc"
95
+ PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
96
+ GEM_VERSION = EMRPC::VERSION + PKG_BUILD
97
+
98
+ RELEASE_NAME = "REL #{GEM_VERSION}"
99
+
100
+ require "extlib/tasks/release"
101
+
102
+ spec = Gem::Specification.new do |s|
103
+ s.name = GEM_NAME
104
+ s.version = GEM_VERSION
105
+ s.platform = Gem::Platform::RUBY
106
+ s.author = AUTHOR
107
+ s.email = EMAIL
108
+ s.homepage = PROJECT_URL
109
+ s.summary = PROJECT_SUMMARY
110
+ s.bindir = "bin"
111
+ s.description = s.summary
112
+ s.executables = %w( emrpc )
113
+ s.require_path = "lib"
114
+ s.files = %w( README Rakefile TODO MIT-LICENSE ) + Dir["{docs,bin,spec,lib,examples,script}/**/*"]
115
+
116
+ # rdoc
117
+ s.has_rdoc = true
118
+ s.extra_rdoc_files = %w( README TODO MIT-LICENSE )
119
+ #s.rdoc_options += RDOC_OPTS + ["--exclude", "^(app|uploads)"]
120
+
121
+ # Dependencies
122
+ s.add_dependency "eventmachine"
123
+ s.add_dependency "rake"
124
+ s.add_dependency "rspec"
125
+
126
+ # See sources on github.com/oleganza
127
+ s.add_dependency "gem_console"
128
+
129
+ # Requirements
130
+ s.requirements << "You need to install the json (or json_pure), yaml, rack gems to use related features."
131
+ s.required_ruby_version = ">= 1.8.4"
132
+ end
133
+
134
+ Rake::GemPackageTask.new(spec) do |package|
135
+ package.gem_spec = spec
136
+ end
137
+
138
+ desc "Generate *.gemspec file"
139
+ task :gemspec do
140
+ gemspec = "spec = " + spec.to_ruby
141
+ path = File.join(File.expand_path(File.dirname(__FILE__)), "emrpc.gemspec")
142
+ puts %{Writing "#{path}"}
143
+ File.open(path, "w") do |f|
144
+ f.write(gemspec)
145
+ end
146
+ end
147
+
148
+ desc "Run :package and install the resulting .gem"
149
+ task :install => :package do
150
+ sh %{#{sudo} gem install #{install_home} --local pkg/#{GEM_NAME}-#{GEM_VERSION}.gem --no-rdoc --no-ri}
151
+ end
152
+
153
+ desc "Run :clean and uninstall the .gem"
154
+ task :uninstall => :clean do
155
+ sh %{#{sudo} gem uninstall #{GEM_NAME}}
156
+ end
data/TODO ADDED
@@ -0,0 +1,47 @@
1
+
2
+ IMPROVEMENTS
3
+
4
+ * Rev:
5
+ * Rev::Server#on_connection: on_connect is called before @block.call:
6
+ this is not how EM behaves. The order is crucial because we would like to
7
+ pass some configuration with @block and use it inside on_connect/connection_completed callback.
8
+ TODO: suggest a patch to Toni Arciery.
9
+
10
+ * lib/rev/loop.rb:60 typo: EVFLAG_NOENV should be used instead of EVFLAG_NOEV
11
+ TODO: suggest a patch to Toni Arciery.
12
+
13
+ * When running $ spec spec/ servers sometimes are not unbinded and some specs fail.
14
+ * fixed issue with reconnecting pid.
15
+ * MultithreadedClient spec fails randomly with 0 timed out threads:
16
+ 'EMRPC::MultithreadedClient with PoolTimeout should raise ThreadTimeout' FAILED
17
+ expected: > 10,
18
+ got: 0
19
+ /Users/olegandreev/Work/emrpc.git/spec/blocking_api/multithreaded_client_spec.rb:49:
20
+ * Transactions for MultithreadedClient to enable scheduling the same backend for all messages in a transaction.
21
+ This can be implemented in two ways:
22
+ 1. Blocking: when transaction starts, backend is taken out of the queue while transaction is in process.
23
+ 2. Non-blocking: backend can be rescheduled to other messages while transaction is in process,
24
+ but when it is needed again, system waits for that particular backend.
25
+ Non-blocking option might seem more efficient, but it also might be less safe: what if you really should
26
+ not interleave communication with the backend with foreign messages?
27
+
28
+ * SinglethreadedClient creates a messaging thread for each Pid. We should use some kind of
29
+ shared channel for a number of pids.
30
+ * Try to use Thread.critical instead of Queue.new to achive better performance.
31
+
32
+ FEATURES
33
+
34
+ * filters for messages (erlang-like receive() pattern-matching)
35
+ * passing particular objects through TCP
36
+ * resource discovery
37
+ * ring/chord protocol
38
+ * rack http server & http client
39
+ * Async DNS support
40
+ * REv along with eventmachine backend
41
+
42
+ TASKS
43
+
44
+ * Documentation and examples!
45
+ * Benchmark against DRb.
46
+ * Publish gem somewhere.
47
+ * Total world domination.
data/bin/emrpc ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'gem_console'
4
+ gem_console('emrpc')
data/lib/emrpc.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+
4
+ # add current dir to the load path
5
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__))))
6
+
7
+ $DEBUG ||= ENV['DEBUG']
8
+
9
+ require 'emrpc/version'
10
+ require 'emrpc/util'
11
+ require 'emrpc/protocols'
12
+ require 'emrpc/evented_api'
13
+ require 'emrpc/blocking_api'
14
+ require 'emrpc/server'
15
+ require 'emrpc/client'
@@ -0,0 +1,48 @@
1
+ module EMRPC
2
+
3
+ class StaleReference < StandardError; end
4
+
5
+ class ReferenceSavior
6
+ attr_accessor :timeout
7
+ # :timeout - specifies a time interval for keeping a reference to a value
8
+ def initialize(options)
9
+ @timeout = options[:timeout] || 60
10
+ @timer = options[:timer] || Timers::EVENTED
11
+ @timeout_thread = @timer.call(@timeout, method(:swap_pages))
12
+ # We keep two previous pages to ensure, that the value lives for at least +timeout+ seconds.
13
+ @page1 = new_page
14
+ @page2 = new_page
15
+ end
16
+
17
+ def set(val)
18
+ oid = val.object_id
19
+ ping(oid, val)
20
+ MethodProxy.new(Wrapper.new(oid))
21
+ end
22
+
23
+ def get(oid)
24
+ oid = oid.to_i
25
+ val = @page1[oid] || @page2[oid]
26
+ raise StaleReference.new("Object id #{oid} was not found in a ReferenceSavior cache. It could have been garbage collected after #{@timeout} sec.") unless val
27
+ ping(oid, val)
28
+ val
29
+ end
30
+
31
+ def ping(oid, val)
32
+ @page1[oid] = val
33
+ end
34
+
35
+ # @page2 disappears and will be garbage collected.
36
+ # @page1 is copied to @page2 for another @timeout seconds
37
+ # and is replaced by a brand new cache page.
38
+ def swap_pages
39
+ @page2 = @page1
40
+ @page1 = new_page
41
+ end
42
+
43
+ def new_page
44
+ Hash.new
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,44 @@
1
+ module EMRPC
2
+ =begin
3
+
4
+ REPLICATION
5
+
6
+ Who does replication?
7
+
8
+ Master schedules replication streams:
9
+ + replication is centralized
10
+ - replicas' existance is not guaranteed.
11
+ - overcomplication (?)
12
+ Primary chunkserver replicates data to secondary chunkservers:
13
+ - every chunkserver must know about other chunkservers
14
+ -
15
+ Client streams data to all the chunkservers:
16
+ - bandwidth!
17
+ + most simple scheme
18
+
19
+ RELIABILITY PRINCIPLES
20
+
21
+ 1. Client should be guaranteed, that data is completely replicated.
22
+ Therefore, it can do replication on its own.
23
+ For optimization reasons, it can delegate some streams to
24
+ other nodes. Say, it sends data to node A and tells it to replicate data
25
+ to node B. A must report finish of both A and B streamings.
26
+ 2. Client may connect any node and try to upload the file there.
27
+
28
+ =end
29
+
30
+ module Ring
31
+
32
+ class Bucket
33
+ attr_accessor :range
34
+ def initialize
35
+
36
+ end
37
+ end
38
+
39
+ class Master
40
+
41
+ end
42
+
43
+ end
44
+ end