zeevex_delayed 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +32 -0
- data/lib/zeevex_delayed/promise.rb +34 -0
- data/lib/zeevex_delayed/thread_promise.rb +45 -0
- data/lib/zeevex_delayed/version.rb +3 -0
- data/lib/zeevex_delayed.rb +6 -0
- data/spec/promise_spec.rb +27 -0
- data/spec/spec_helper.rb +95 -0
- data/spec/thread_promise_spec.rb +35 -0
- data/zeevex_delayed.gemspec +26 -0
- metadata +112 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:spec)
|
8
|
+
|
9
|
+
namespace :spec do
|
10
|
+
desc "Run on three Rubies"
|
11
|
+
task :platforms do
|
12
|
+
current = %x{rvm-prompt v}
|
13
|
+
|
14
|
+
fail = false
|
15
|
+
%w{1.8.7 1.9.3 ree}.each do |version|
|
16
|
+
puts "Switching to #{version}"
|
17
|
+
Bundler.with_clean_env do
|
18
|
+
system %{bash -c 'source ~/.rvm/scripts/rvm && rvm #{version} && bundle exec rake spec'}
|
19
|
+
end
|
20
|
+
if $?.exitstatus != 0
|
21
|
+
fail = true
|
22
|
+
break
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
system %{rvm #{current}}
|
27
|
+
|
28
|
+
exit (fail ? 1 : 0)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
task :default => 'spec:platforms'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'zeevex_proxy'
|
2
|
+
|
3
|
+
module ZeevexDelayed
|
4
|
+
class Promise < ZeevexProxy::Base
|
5
|
+
|
6
|
+
def initialize(obj=nil, &block)
|
7
|
+
super
|
8
|
+
@__promise_block = block
|
9
|
+
raise ArgumentError, "Must supply a block!" unless block
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, *args, &block)
|
13
|
+
__force__.__send__(name, *args, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def __force__
|
17
|
+
unless @__promise_forced
|
18
|
+
@obj = @__promise_block.call
|
19
|
+
@__promise_forced = true
|
20
|
+
end
|
21
|
+
@obj
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Kernel
|
27
|
+
def force_promise(promise)
|
28
|
+
promise.__force__
|
29
|
+
end
|
30
|
+
|
31
|
+
def promise(&block)
|
32
|
+
ZeevexDelayed::Promise.new &block
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'promise'))
|
2
|
+
require 'zeevex_proxy'
|
3
|
+
|
4
|
+
module ZeevexDelayed
|
5
|
+
#
|
6
|
+
# Like a Promise, but evaluate and cache values on a per-thread basis
|
7
|
+
#
|
8
|
+
# e.g. will act just like a Promise if only called from one thread, but if
|
9
|
+
# Thread A and Thread B both access a Promise created like so:
|
10
|
+
#
|
11
|
+
# promise { Thread.current.object_id }
|
12
|
+
#
|
13
|
+
# Then they will see different values. This does cache, and it does not
|
14
|
+
# GC its cache when threads exit. Thus if you expect to have a very
|
15
|
+
# long-lived Promise accessed from many different threads that come and
|
16
|
+
# go, don't use this.
|
17
|
+
#
|
18
|
+
|
19
|
+
class ThreadPromise < ZeevexProxy::Base
|
20
|
+
|
21
|
+
def initialize(obj=nil, &block)
|
22
|
+
raise ArgumentError, "Must supply a block!" unless block_given?
|
23
|
+
super
|
24
|
+
@__promise_thread_map = {}
|
25
|
+
@__promise_block = block
|
26
|
+
end
|
27
|
+
|
28
|
+
def method_missing(name, *args, &block)
|
29
|
+
__force__.__send__(name, *args, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def __force__
|
33
|
+
unless @__promise_thread_map.has_key?(Thread.current.object_id)
|
34
|
+
@__promise_thread_map[Thread.current.object_id] = @__promise_block.call
|
35
|
+
end
|
36
|
+
@__promise_thread_map[Thread.current.object_id]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module Kernel
|
42
|
+
def thread_promise(&block)
|
43
|
+
ZeevexDelayed::ThreadPromise.new &block
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper.rb')
|
4
|
+
|
5
|
+
describe ZeevexDelayed::Promise do
|
6
|
+
|
7
|
+
let :promise_class do
|
8
|
+
ZeevexDelayed::Promise
|
9
|
+
end
|
10
|
+
|
11
|
+
context "as a basic promise" do
|
12
|
+
it_should_behave_like "basic promise"
|
13
|
+
end
|
14
|
+
|
15
|
+
context "when called from different threads" do
|
16
|
+
it "should call the block once for all threads" do
|
17
|
+
canary = mock("canary")
|
18
|
+
canary.should_receive(:life).once.and_return(42)
|
19
|
+
|
20
|
+
@val = promise_class.new { canary.life }
|
21
|
+
|
22
|
+
Thread.new { @val.to_i }.join
|
23
|
+
Thread.new { @val.to_i }.join
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
$: << File.expand_path(File.dirname(__FILE__) + '../lib')
|
4
|
+
|
5
|
+
require 'rspec'
|
6
|
+
require 'zeevex_delayed'
|
7
|
+
|
8
|
+
puts "loading spec helper..."
|
9
|
+
|
10
|
+
# Be sure to include AuthenticatedTestHelper in spec/spec_helper.rb instead.
|
11
|
+
# Then, you can remove it from this and the functional test.
|
12
|
+
|
13
|
+
shared_examples_for "results caching" do
|
14
|
+
it "should only call the block once" do
|
15
|
+
@val = promise_class.new { canary.life }
|
16
|
+
@val.__force__
|
17
|
+
@val.__force__
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
shared_examples_for "basic promise" do
|
23
|
+
|
24
|
+
let :canary do
|
25
|
+
mock("canary").tap do |mock|
|
26
|
+
mock.stub(:doit).and_return("hello")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
let :test_promise do
|
31
|
+
promise_class.new { canary.doit }
|
32
|
+
end
|
33
|
+
|
34
|
+
subject { test_promise }
|
35
|
+
|
36
|
+
context "#initialize" do
|
37
|
+
it "should not invoke the block during initialization" do
|
38
|
+
canary = mock("canary")
|
39
|
+
canary.should_receive(:oops).never
|
40
|
+
|
41
|
+
@val = promise_class.new { canary.oops }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should require a block" do
|
46
|
+
expect { promise_class.new }.
|
47
|
+
to raise_exception(ArgumentError)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should return the value of the block on to_s" do
|
51
|
+
promise_class.new { "a" + "b" }.should == "ab"
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should call the block only when needed" do
|
55
|
+
foo = 1
|
56
|
+
@val = promise_class.new { foo }
|
57
|
+
foo = 2
|
58
|
+
@val.should == 2
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when forcing computation" do
|
62
|
+
it "should respond to __force__" do
|
63
|
+
# check here because respond_to? invoked on a promise would delegate
|
64
|
+
promise_class.instance_methods.should include("__force__")
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should be computed when Kernel#force_promise is called" do
|
68
|
+
canary.should_receive(:doit).and_return('abcde')
|
69
|
+
force_promise subject
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should return the block's value from __force__" do
|
74
|
+
subject.__force__.should == "hello"
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
context "for blocks that return false" do
|
79
|
+
let :canary do
|
80
|
+
canary = mock("canary")
|
81
|
+
canary.should_receive(:life).once.and_return(false)
|
82
|
+
canary
|
83
|
+
end
|
84
|
+
it_should_behave_like "results caching"
|
85
|
+
end
|
86
|
+
|
87
|
+
context "for blocks that return nil" do
|
88
|
+
let :canary do
|
89
|
+
canary = mock("canary")
|
90
|
+
canary.should_receive(:life).once.and_return(nil)
|
91
|
+
canary
|
92
|
+
end
|
93
|
+
it_should_behave_like "results caching"
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper.rb')
|
3
|
+
|
4
|
+
describe ZeevexDelayed::ThreadPromise do
|
5
|
+
|
6
|
+
let :promise_class do
|
7
|
+
ZeevexDelayed::ThreadPromise
|
8
|
+
end
|
9
|
+
|
10
|
+
context "as a basic promise" do
|
11
|
+
it_should_behave_like "basic promise"
|
12
|
+
end
|
13
|
+
|
14
|
+
context "when called from different threads" do
|
15
|
+
it "should call the block again for each new thread" do
|
16
|
+
canary = mock("canary")
|
17
|
+
canary.should_receive(:life).twice.and_return(42)
|
18
|
+
|
19
|
+
@val = promise_class.new { canary.life }
|
20
|
+
|
21
|
+
Thread.new { @val.to_i }.join
|
22
|
+
Thread.new { @val.to_i }.join
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should call the block only once for each thread" do
|
26
|
+
canary = mock("canary")
|
27
|
+
canary.should_receive(:life).twice.and_return(42)
|
28
|
+
|
29
|
+
@val = promise_class.new { canary.life }
|
30
|
+
|
31
|
+
Thread.new { @val.to_i; @val.to_i }.join
|
32
|
+
Thread.new { @val.to_i; @val.to_i }.join
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "zeevex_delayed/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "zeevex_delayed"
|
7
|
+
s.version = ZeevexDelayed::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Robert Sanders"]
|
10
|
+
s.email = ["robert@zeevex.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{Basic and per-thread promise classes.}
|
13
|
+
s.description = %q{This gem provides a couple of classes useful for deferred computation.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "zeevex_delayed"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency 'zeevex_proxy'
|
23
|
+
|
24
|
+
s.add_development_dependency 'rspec', '~> 2.9.0'
|
25
|
+
s.add_development_dependency 'rake'
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: zeevex_delayed
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 9
|
8
|
+
- 0
|
9
|
+
version: 0.9.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Robert Sanders
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-05-02 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: zeevex_proxy
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rspec
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
segments:
|
40
|
+
- 2
|
41
|
+
- 9
|
42
|
+
- 0
|
43
|
+
version: 2.9.0
|
44
|
+
type: :development
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
prerelease: false
|
49
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
type: :development
|
57
|
+
version_requirements: *id003
|
58
|
+
description: This gem provides a couple of classes useful for deferred computation.
|
59
|
+
email:
|
60
|
+
- robert@zeevex.com
|
61
|
+
executables: []
|
62
|
+
|
63
|
+
extensions: []
|
64
|
+
|
65
|
+
extra_rdoc_files: []
|
66
|
+
|
67
|
+
files:
|
68
|
+
- .gitignore
|
69
|
+
- Gemfile
|
70
|
+
- Rakefile
|
71
|
+
- lib/zeevex_delayed.rb
|
72
|
+
- lib/zeevex_delayed/promise.rb
|
73
|
+
- lib/zeevex_delayed/thread_promise.rb
|
74
|
+
- lib/zeevex_delayed/version.rb
|
75
|
+
- spec/promise_spec.rb
|
76
|
+
- spec/spec_helper.rb
|
77
|
+
- spec/thread_promise_spec.rb
|
78
|
+
- zeevex_delayed.gemspec
|
79
|
+
has_rdoc: true
|
80
|
+
homepage: ""
|
81
|
+
licenses: []
|
82
|
+
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
segments:
|
100
|
+
- 0
|
101
|
+
version: "0"
|
102
|
+
requirements: []
|
103
|
+
|
104
|
+
rubyforge_project: zeevex_delayed
|
105
|
+
rubygems_version: 1.3.6
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: Basic and per-thread promise classes.
|
109
|
+
test_files:
|
110
|
+
- spec/promise_spec.rb
|
111
|
+
- spec/spec_helper.rb
|
112
|
+
- spec/thread_promise_spec.rb
|