async-proxy 0.0.0

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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Angel Faus
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,83 @@
1
+ = async-proxy
2
+
3
+ An Async proxy is a wrapper around a real object that intercepts method calls and runs them
4
+ in an asynchronous way.
5
+
6
+ =Construction
7
+
8
+ Async proxies will be generally constructed calling .async on any object.
9
+
10
+ =Behaviour
11
+
12
+ Calling any method in async proxy creates a new thread in charge of running the actual computation
13
+ and control is immediately returned to the callee.
14
+
15
+ When the actual result of the method call is needed, call +sync+ on the returned value
16
+ and this will wait (if necessary) for the thread to finish and provide the real value.
17
+
18
+ Example:
19
+
20
+ async_recommendations = user.async.recommendations # computing the recommendations is really slow!
21
+
22
+ #do other stuff
23
+
24
+ print async_recommendations.sync
25
+
26
+ =Chaining
27
+
28
+ The result of a method call in an async proxy will itself be an async proxy. This allows for
29
+ chained computations:
30
+
31
+ my_async_result = object.async.some_slow_method.and_another_one(with_an_argument).and_a_third_one
32
+
33
+ currency = user.async.country.currency
34
+
35
+ Each chained method is run as soon as the previous level result is available.
36
+ Calling sync in any dependent value will automatically wait for all dependencies.
37
+
38
+ =Dependent computations
39
+
40
+ Sometimes you want to run some code on the returned value as soon as it is available. With
41
+ async proxy you do that with:
42
+
43
+ - register_callback: run the code but do not care about the return value
44
+
45
+ async_proxy.slow_method.register_callback{|return_value| puts return_value}
46
+
47
+ - when_ready: returns yet another async proxy that lets you recover the block's returned value
48
+
49
+ squared = async_proxy.slow_method.when_ready{|return_value| return_value * return_value }
50
+ ..
51
+ squared.sync
52
+
53
+ =Automatic synchronization
54
+
55
+ Some method calls automatically trigger synchronization:
56
+
57
+ - to_s
58
+ - inspect
59
+ - each
60
+
61
+ This means that in most cases you can treat an async object exactly in the same way that the
62
+ wrapped object. You generally don't need to make explicit +sync+ calls (although you can if
63
+ you want to control the exact moment the object will be "realized")
64
+
65
+ = Locking and thread safety
66
+
67
+ Async Proxy makes no attempt to guarantee thread safety. It can be considered as just syntactic
68
+ sugar to automate the creations of threads, so if your code is not thread-safe or uses libraries
69
+ that are not thread-safe mischief will happen.
70
+
71
+ == Note on Patches/Pull Requests
72
+
73
+ * Fork the project.
74
+ * Make your feature addition or bug fix.
75
+ * Add tests for it. This is important so I don't break it in a
76
+ future version unintentionally.
77
+ * Commit, do not mess with rakefile, version, or history.
78
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
79
+ * Send me a pull request. Bonus points for topic branches.
80
+
81
+ == Copyright
82
+
83
+ Copyright (c) 2010 Angel Faus. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "async-proxy"
8
+ gem.summary = %Q{run any method call asynchronously without caring about threads}
9
+ gem.description = %Q{turn any object into an async version of itself, so that all method calls will run asynchronously, including dependent computations}
10
+ gem.email = "angel@vlex.com"
11
+ gem.homepage = "http://github.com/angelf/async-proxy"
12
+ gem.authors = ["Angel Faus"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "async-proxy #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{async-proxy}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Angel Faus"]
12
+ s.date = %q{2010-07-04}
13
+ s.description = %q{turn any object into an async version of itself, so that all method calls will run asynchronously, including dependent computations}
14
+ s.email = %q{angel@vlex.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "async-proxy.gemspec",
27
+ "lib/async-proxy.rb",
28
+ "lib/computed_proxy.rb",
29
+ "lib/init.rb",
30
+ "lib/object_extensions.rb",
31
+ "lib/object_proxy.rb",
32
+ "spec/async-proxy_spec.rb",
33
+ "spec/spec.opts",
34
+ "spec/spec_helper.rb"
35
+ ]
36
+ s.homepage = %q{http://github.com/angelf/async-proxy}
37
+ s.rdoc_options = ["--charset=UTF-8"]
38
+ s.require_paths = ["lib"]
39
+ s.rubygems_version = %q{1.3.6}
40
+ s.summary = %q{run any method call asynchronously without caring about threads}
41
+ s.test_files = [
42
+ "spec/async-proxy_spec.rb",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
51
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
52
+ else
53
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
57
+ end
58
+ end
59
+
@@ -0,0 +1,4 @@
1
+ require File.join(File.dirname(__FILE__), 'object_extensions')
2
+ require File.join(File.dirname(__FILE__), 'object_proxy')
3
+ require File.join(File.dirname(__FILE__), 'computed_proxy')
4
+
@@ -0,0 +1,52 @@
1
+ module AsyncProxy
2
+
3
+ # a specialization of ObjectProxy to be used when the wraped value is dependent on another
4
+ # async proxy and a computation (a block)
5
+ #
6
+ # will be automatically created by calling a method or +when_ready+ in any async object
7
+ #
8
+ # the user should not directly create objects of this class
9
+
10
+ class ComputedProxy < ObjectProxy
11
+
12
+ attr_reader :callable
13
+ attr_reader :proc
14
+
15
+ def initialize(callable, proc)
16
+ @callable = callable
17
+ @proc = proc
18
+ @callbacks = []
19
+ end
20
+
21
+ def ready?
22
+ @ready
23
+ end
24
+
25
+ def async
26
+ self
27
+ end
28
+
29
+ def sync
30
+ wait_for_computation
31
+ @result
32
+ end
33
+
34
+ def launch_computation
35
+ @thread = Thread.new do
36
+ @result = @proc.call(@callable.sync)
37
+ @ready = true
38
+ run_callbacks
39
+ end
40
+ end
41
+
42
+ def wait_for_computation
43
+ callable.sync # ensures the callable has finished and run its callbacks
44
+ @thread.join if !@done
45
+ end
46
+
47
+ private
48
+ def run_callbacks
49
+ @callbacks.each { |block| block.call @result }
50
+ end
51
+ end
52
+ end
data/lib/init.rb ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,11 @@
1
+ class Object
2
+ # returns an async wrapper around this object
3
+ def async
4
+ AsyncProxy::ObjectProxy.new(self)
5
+ end
6
+
7
+ # ensures we are dealing with the "real" synchronous object
8
+ def sync
9
+ self # a no-op in Object, will be redefine in async proxies classes
10
+ end
11
+ end
@@ -0,0 +1,73 @@
1
+ module AsyncProxy
2
+
3
+ # a wrapper around an object that will intercept all method calls and run them asynchronously
4
+ class ObjectProxy
5
+ def initialize(object)
6
+ @object = object
7
+ @callbacks = []
8
+ end
9
+
10
+ def sync
11
+ @object
12
+ end
13
+
14
+ def async
15
+ self
16
+ end
17
+
18
+ def to_s
19
+ sync.to_s
20
+ end
21
+
22
+ def each
23
+ sync.each{yield}
24
+ end
25
+
26
+ # calling +map+ on a proxy will run the block in parallel for each element
27
+ # of the Enumerable
28
+ #
29
+ # async_array.map(&block).each {
30
+ # # starts processing the first element even if the other ones are not ready yet
31
+ # }
32
+ def map(&block)
33
+ when_ready do |enum|
34
+ enum.map do |item|
35
+ AsyncProxy::ResultProxy.new(item, block)
36
+ end
37
+ end
38
+ end
39
+
40
+ def ready?
41
+ true
42
+ end
43
+
44
+ # runs the block with the computation's result as the first argument
45
+ # as soon as the value is available
46
+ #
47
+ # returns an async proxy object, that can be used exactly to recover the block's return
48
+ # value or chain more computations
49
+ def when_ready(&block)
50
+ result_proxy = AsyncProxy::ComputedProxy.new(self, block)
51
+ register_callback {result_proxy.launch_computation}
52
+ result_proxy
53
+ end
54
+
55
+ # called with a block, runs it as soon the computation's result is available.
56
+ # The return value of the block is discarded: use +when_ready+ if you are interested in the
57
+ # block's return value
58
+ def register_callback(&proc)
59
+ if ready?
60
+ proc.call(sync)
61
+ else
62
+ @callbacks << proc
63
+ end
64
+ end
65
+
66
+ private
67
+ def method_missing(symbol, *args)
68
+ when_ready do |obj|
69
+ obj.send(symbol, *args)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,51 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ class Integer
4
+ def slow_inc
5
+ Kernel.sleep(1)
6
+ self + 1
7
+ end
8
+ end
9
+
10
+ describe "AsyncProxy" do
11
+
12
+ it "sync is idempotent" do
13
+ 1.sync.should == 1
14
+ end
15
+
16
+ it "async is idempotent" do
17
+ x = 1.async
18
+ x.async == x
19
+ end
20
+
21
+ it "simple call" do
22
+ 1.async.slow_inc.sync.should == 2
23
+ end
24
+
25
+ it "chained call" do
26
+ 1.async.slow_inc.div(2).sync.should == 1
27
+ end
28
+
29
+ it "chained call with intermediate object already synchronized" do
30
+ intermediate = 1.async.slow_inc
31
+ divided = intermediate.div(2)
32
+
33
+ intermediate.sync
34
+ divided.sync.should == 1
35
+ end
36
+
37
+ it "chained call + when ready" do
38
+ 1.async.slow_inc.div(2).when_ready{|one| one + 2}.sync.should == 3
39
+ end
40
+
41
+ it "shared intermediate object" do
42
+ intermediate = 0.async.slow_inc
43
+ intermediate_plus_1 = intermediate.when_ready{|x| x + 1}
44
+ intermediate_plus_2 = intermediate.when_ready{|x| x + 2}
45
+
46
+ intermediate.sync.should == 1
47
+ intermediate_plus_1.sync.should == 2
48
+ intermediate_plus_2.sync.should == 3
49
+ end
50
+
51
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'async-proxy'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: async-proxy
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 0
9
+ version: 0.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Angel Faus
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-07-04 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 2
30
+ - 9
31
+ version: 1.2.9
32
+ type: :development
33
+ version_requirements: *id001
34
+ description: turn any object into an async version of itself, so that all method calls will run asynchronously, including dependent computations
35
+ email: angel@vlex.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - LICENSE
42
+ - README.rdoc
43
+ files:
44
+ - .document
45
+ - .gitignore
46
+ - LICENSE
47
+ - README.rdoc
48
+ - Rakefile
49
+ - VERSION
50
+ - async-proxy.gemspec
51
+ - lib/async-proxy.rb
52
+ - lib/computed_proxy.rb
53
+ - lib/init.rb
54
+ - lib/object_extensions.rb
55
+ - lib/object_proxy.rb
56
+ - spec/async-proxy_spec.rb
57
+ - spec/spec.opts
58
+ - spec/spec_helper.rb
59
+ has_rdoc: true
60
+ homepage: http://github.com/angelf/async-proxy
61
+ licenses: []
62
+
63
+ post_install_message:
64
+ rdoc_options:
65
+ - --charset=UTF-8
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ requirements: []
83
+
84
+ rubyforge_project:
85
+ rubygems_version: 1.3.6
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: run any method call asynchronously without caring about threads
89
+ test_files:
90
+ - spec/async-proxy_spec.rb
91
+ - spec/spec_helper.rb