zeevex_delayed 0.9.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/.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
|