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 ADDED
@@ -0,0 +1 @@
1
+ --color --order random
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem "rake"
6
+ gem "awesome_print"
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
@@ -0,0 +1,4 @@
1
+ task :default => :spec
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new :spec
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
@@ -0,0 +1,3 @@
1
+ class Kontext
2
+ VERSION = "0.1.0"
3
+ 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: