kontext 0.1.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/.rspec +1 -0
- data/Gemfile +6 -0
- data/README.md +48 -0
- data/Rakefile +4 -0
- data/kontext.gemspec +20 -0
- data/lib/kontext/version.rb +3 -0
- data/lib/kontext.rb +67 -0
- data/spec/kontext_spec.rb +169 -0
- metadata +66 -0
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color --order random
|
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Thread and Fiber specific global state
|
|
2
|
+
======================================
|
|
3
|
+
|
|
4
|
+
Know DataMapper's repository contexts? Need request-specific global state in an
|
|
5
|
+
evented webserver? Wanna do complex AOP but care about concurrency? Kontext
|
|
6
|
+
comes to your rescue. It provides you with nestable per-thread, per-fiber
|
|
7
|
+
context stacks.
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
require "kontext"
|
|
11
|
+
|
|
12
|
+
context = Kontext.new
|
|
13
|
+
|
|
14
|
+
context.with "foo" do
|
|
15
|
+
p context.last
|
|
16
|
+
# => "foo"
|
|
17
|
+
|
|
18
|
+
context.with "bar" do
|
|
19
|
+
p context.stack
|
|
20
|
+
# => ["foo", "bar"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
Fiber.new do
|
|
24
|
+
p context.last
|
|
25
|
+
# => nil
|
|
26
|
+
|
|
27
|
+
context.with "different context!" do
|
|
28
|
+
p context.last
|
|
29
|
+
# => "different context!"
|
|
30
|
+
|
|
31
|
+
Thread.start do
|
|
32
|
+
context.with "again different" do
|
|
33
|
+
p context.stack
|
|
34
|
+
# => ["again different"]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
sleep 0.1
|
|
39
|
+
end
|
|
40
|
+
end.resume
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
To do
|
|
45
|
+
-----
|
|
46
|
+
|
|
47
|
+
* Fix memory leak in `#store`
|
|
48
|
+
* Make new threads and fibers inherit a copy of the previous context stack
|
data/Rakefile
ADDED
data/kontext.gemspec
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "kontext/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "kontext"
|
|
7
|
+
s.version = Kontext::VERSION
|
|
8
|
+
s.platform = Gem::Platform::RUBY
|
|
9
|
+
s.authors = ["Lars Gierth"]
|
|
10
|
+
s.email = ["lars.gierth@gmail.com"]
|
|
11
|
+
s.homepage = "https://rubygems.org/gems/kontext"
|
|
12
|
+
s.summary = %q{Thread and Fiber specific global state}
|
|
13
|
+
s.description = s.summary
|
|
14
|
+
|
|
15
|
+
s.add_development_dependency "rspec"
|
|
16
|
+
|
|
17
|
+
s.files = `git ls-files`.split("\n") - [".gitignore", ".travis.yml"]
|
|
18
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
end
|
data/lib/kontext.rb
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require "kontext/version"
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require "fiber"
|
|
5
|
+
rescue LoadError => ex
|
|
6
|
+
raise(ex) if ex.message =~ "fiber"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class Kontext
|
|
10
|
+
RootFiber = defined?(::Fiber) ? Fiber.current : nil
|
|
11
|
+
|
|
12
|
+
def thread?
|
|
13
|
+
Thread.current != Thread.main
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def fiber?
|
|
17
|
+
defined?(::Fiber) && Fiber.current != RootFiber
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def store
|
|
21
|
+
if fiber?
|
|
22
|
+
# TODO over time this will leak massively
|
|
23
|
+
Thread.current[Fiber.current.object_id.to_s] ||= {}
|
|
24
|
+
elsif thread?
|
|
25
|
+
Thread.current
|
|
26
|
+
else
|
|
27
|
+
@store ||= {}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def stack
|
|
32
|
+
store[self] ||= []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def with(obj)
|
|
36
|
+
old_size = size
|
|
37
|
+
push(obj)
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
result = yield(obj)
|
|
41
|
+
ensure
|
|
42
|
+
truncate(old_size)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
result
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def push(obj)
|
|
49
|
+
stack.push(obj)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def pop
|
|
53
|
+
stack.pop
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def last
|
|
57
|
+
stack.last
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def size
|
|
61
|
+
stack.length
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def truncate(new_size)
|
|
65
|
+
pop until size == new_size
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
require "awesome_print"
|
|
2
|
+
require "kontext"
|
|
3
|
+
|
|
4
|
+
describe Kontext do
|
|
5
|
+
describe Kontext::RootFiber do
|
|
6
|
+
it "is a reference to the root fiber" do
|
|
7
|
+
Kontext::RootFiber.should equal(Fiber.current)
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
let :kontext do
|
|
12
|
+
Kontext.new
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "#thread?" do
|
|
16
|
+
it "returns true if the current thread is not the main thread" do
|
|
17
|
+
result = nil
|
|
18
|
+
Thread.new { result = kontext.thread? }
|
|
19
|
+
sleep 0.01
|
|
20
|
+
|
|
21
|
+
result.should be_true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "returns false if we're in the main thread" do
|
|
25
|
+
kontext.thread?.should be_false
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe "#fiber?" do
|
|
30
|
+
it "returns true if the current fiber is not the root fiber" do
|
|
31
|
+
result = nil
|
|
32
|
+
Fiber.new { result = kontext.fiber? }.resume
|
|
33
|
+
|
|
34
|
+
result.should be_true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "returns false if we're in the root fiber" do
|
|
38
|
+
kontext.fiber?.should be_false
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "#store" do
|
|
43
|
+
describe "when in a fiber" do
|
|
44
|
+
before do
|
|
45
|
+
kontext.stub :fiber? => true, :thread? => false
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "returns a fiber specific store" do
|
|
49
|
+
key = Fiber.current.object_id.to_s
|
|
50
|
+
kontext.store.should equal(Thread.current[key])
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
describe "when in a thread" do
|
|
55
|
+
before do
|
|
56
|
+
kontext.stub :fiber? => false, :thread? => true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "returns a thread specific hash store" do
|
|
60
|
+
kontext.store.should equal(Thread.current)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
describe "when neither in thread nor in fiber" do
|
|
65
|
+
before do
|
|
66
|
+
kontext.stub :fiber? => false, :thread? => false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it "returns a hash" do
|
|
70
|
+
kontext.store.should be_a(Hash)
|
|
71
|
+
kontext.store.should be_empty
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
describe "#stack" do
|
|
77
|
+
it "returns the instance's stack" do
|
|
78
|
+
kontext.stack.should be_an(Array)
|
|
79
|
+
kontext.stack.should be_empty
|
|
80
|
+
kontext.stack.should equal(kontext.store[kontext])
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe "#with(obj)" do
|
|
85
|
+
let(:obj) { stub "obj" }
|
|
86
|
+
|
|
87
|
+
it "pushes the object to the stack" do
|
|
88
|
+
kontext.with obj do
|
|
89
|
+
kontext.last.should equal(obj)
|
|
90
|
+
|
|
91
|
+
kontext.with obj do
|
|
92
|
+
kontext.stack.should == [obj, obj]
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "yields the object" do
|
|
98
|
+
kontext.with obj do |o|
|
|
99
|
+
o.should equal(obj)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "correctly updates the stack when the block raises" do
|
|
104
|
+
begin
|
|
105
|
+
kontext.with obj do
|
|
106
|
+
begin
|
|
107
|
+
kontext.with(obj) { raise }
|
|
108
|
+
ensure
|
|
109
|
+
kontext.stack.should == [obj]
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
rescue
|
|
113
|
+
kontext.stack.should be_empty
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "expects a block" do
|
|
118
|
+
proc { kontext.with obj }.should raise_error(LocalJumpError)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
describe "#push(obj)" do
|
|
123
|
+
it "adds an object to the stack" do
|
|
124
|
+
kontext.push :foo
|
|
125
|
+
kontext.push :bar
|
|
126
|
+
kontext.last.should equal(:bar)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
describe "#pop" do
|
|
131
|
+
it "removes an object from the top of the stack" do
|
|
132
|
+
kontext.pop.should be_nil
|
|
133
|
+
|
|
134
|
+
kontext.push :foo
|
|
135
|
+
kontext.pop.should equal(:foo)
|
|
136
|
+
kontext.size.should be_zero
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
describe "#last" do
|
|
141
|
+
it "returns the object at the top of the stack" do
|
|
142
|
+
kontext.last.should be_nil
|
|
143
|
+
|
|
144
|
+
kontext.push :foo
|
|
145
|
+
kontext.last.should equal(:foo)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe "#size" do
|
|
150
|
+
it "is zero initially" do
|
|
151
|
+
kontext.size.should be_zero
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "increases by 1 for each nesting level" do
|
|
155
|
+
kontext.with :foo do
|
|
156
|
+
kontext.size.should equal(1)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
describe "#truncate(new_size)" do
|
|
162
|
+
it "pops objects until there are $new_size object left" do
|
|
163
|
+
[:foo, :bar, :baz].each &kontext.method(:push)
|
|
164
|
+
|
|
165
|
+
kontext.truncate 1
|
|
166
|
+
kontext.last.should equal(:foo)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kontext
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Lars Gierth
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-02-20 00:00:00.000000000Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: rspec
|
|
16
|
+
requirement: &85309510 !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '0'
|
|
22
|
+
type: :development
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: *85309510
|
|
25
|
+
description: Thread and Fiber specific global state
|
|
26
|
+
email:
|
|
27
|
+
- lars.gierth@gmail.com
|
|
28
|
+
executables: []
|
|
29
|
+
extensions: []
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- .rspec
|
|
33
|
+
- Gemfile
|
|
34
|
+
- README.md
|
|
35
|
+
- Rakefile
|
|
36
|
+
- kontext.gemspec
|
|
37
|
+
- lib/kontext.rb
|
|
38
|
+
- lib/kontext/version.rb
|
|
39
|
+
- spec/kontext_spec.rb
|
|
40
|
+
homepage: https://rubygems.org/gems/kontext
|
|
41
|
+
licenses: []
|
|
42
|
+
post_install_message:
|
|
43
|
+
rdoc_options: []
|
|
44
|
+
require_paths:
|
|
45
|
+
- lib
|
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
|
+
none: false
|
|
48
|
+
requirements:
|
|
49
|
+
- - ! '>='
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '0'
|
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
|
+
none: false
|
|
54
|
+
requirements:
|
|
55
|
+
- - ! '>='
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '0'
|
|
58
|
+
requirements: []
|
|
59
|
+
rubyforge_project:
|
|
60
|
+
rubygems_version: 1.8.16
|
|
61
|
+
signing_key:
|
|
62
|
+
specification_version: 3
|
|
63
|
+
summary: Thread and Fiber specific global state
|
|
64
|
+
test_files:
|
|
65
|
+
- spec/kontext_spec.rb
|
|
66
|
+
has_rdoc:
|