zeevex_threadsafe 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/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in zeevex_threadsafe.gemspec
4
+ gemspec
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.2}.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'
@@ -0,0 +1,77 @@
1
+ #
2
+ # this is a copy of ActiveSupport::CoreExtensions::Module
3
+ #
4
+
5
+ module ZeevexThreadsafe
6
+ module Aliasing
7
+ # Encapsulates the common pattern of:
8
+ #
9
+ # alias_method :foo_without_feature, :foo
10
+ # alias_method :foo, :foo_with_feature
11
+ #
12
+ # With this, you simply do:
13
+ #
14
+ # alias_method_chain :foo, :feature
15
+ #
16
+ # And both aliases are set up for you.
17
+ #
18
+ # Query and bang methods (foo?, foo!) keep the same punctuation:
19
+ #
20
+ # alias_method_chain :foo?, :feature
21
+ #
22
+ # is equivalent to
23
+ #
24
+ # alias_method :foo_without_feature?, :foo?
25
+ # alias_method :foo?, :foo_with_feature?
26
+ #
27
+ # so you can safely chain foo, foo?, and foo! with the same feature.
28
+ def alias_method_chain(target, feature)
29
+ # Strip out punctuation on predicates or bang methods since
30
+ # e.g. target?_without_feature is not a valid method name.
31
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
32
+ yield(aliased_target, punctuation) if block_given?
33
+
34
+ with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}"
35
+
36
+ alias_method without_method, target
37
+ alias_method target, with_method
38
+
39
+ case
40
+ when public_method_defined?(without_method)
41
+ public target
42
+ when protected_method_defined?(without_method)
43
+ protected target
44
+ when private_method_defined?(without_method)
45
+ private target
46
+ end
47
+ end
48
+
49
+ # Allows you to make aliases for attributes, which includes
50
+ # getter, setter, and query methods.
51
+ #
52
+ # Example:
53
+ #
54
+ # class Content < ActiveRecord::Base
55
+ # # has a title attribute
56
+ # end
57
+ #
58
+ # class Email < Content
59
+ # alias_attribute :subject, :title
60
+ # end
61
+ #
62
+ # e = Email.find(1)
63
+ # e.title # => "Superstars"
64
+ # e.subject # => "Superstars"
65
+ # e.subject? # => true
66
+ # e.subject = "Megastars"
67
+ # e.title # => "Megastars"
68
+ def alias_attribute(new_name, old_name)
69
+ module_eval <<-STR, __FILE__, __LINE__ + 1
70
+ def #{new_name}; self.#{old_name}; end # def subject; self.title; end
71
+ def #{new_name}?; self.#{old_name}?; end # def subject?; self.title?; end
72
+ def #{new_name}=(v); self.#{old_name} = v; end # def subject=(v); self.title = v; end
73
+ STR
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,106 @@
1
+ require_dependency "weakref"
2
+
3
+ begin
4
+ require 'active_support/core_ext'
5
+ rescue LoadError
6
+ require 'zeevex_threadsafe/aliasing'
7
+ end
8
+
9
+ module ZeevexThreadsafe
10
+ module Rails
11
+ class RequestGlobals
12
+ class << self
13
+
14
+ REQUEST_VARNAME = "_zx_rg_request"
15
+ INSTANCE_VARNAME = "@_zx_request_globals"
16
+
17
+ def request
18
+ ref = Thread.current[REQUEST_VARNAME]
19
+ ref && ref.class == WeakRef && ref.weakref_alive? ? ref.__getobj__ : nil
20
+ end
21
+
22
+ def request=(request)
23
+ Thread.current[REQUEST_VARNAME] = (request ? WeakRef.new(request) : nil)
24
+ end
25
+
26
+ def [](name)
27
+ hash[name]
28
+ end
29
+
30
+ def []=(name, val)
31
+ hash(true)[name] = val
32
+ end
33
+
34
+ def reset
35
+ hash && hash.clear
36
+ Thread.current[REQUEST_VARNAME] = nil
37
+ end
38
+
39
+
40
+ def define_request_global_accessors(base, name, key = nil)
41
+ key ||= name
42
+ base.class_eval do
43
+ define_method name do
44
+ ZeevexThreadsafe::Rails::RequestGlobals[key]
45
+ end
46
+
47
+ define_method (name.to_s + "=").to_sym do |value|
48
+ ZeevexThreadsafe::Rails::RequestGlobals[key] = value
49
+ end
50
+ end
51
+ end
52
+
53
+ protected
54
+
55
+ def hash(strict = false)
56
+ req = request
57
+ unless req
58
+ if strict
59
+ raise "No current request scope for RequestGlobal"
60
+ else
61
+ return {}
62
+ end
63
+ end
64
+
65
+ hash = req.instance_variable_get(INSTANCE_VARNAME)
66
+ if !hash
67
+ hash = {}
68
+ req.instance_variable_set(INSTANCE_VARNAME, hash)
69
+ end
70
+ hash
71
+ end
72
+
73
+ end
74
+
75
+ module Controller
76
+ def self.included(klass)
77
+ klass.class_eval do
78
+ alias_method_chain :process, :request_globals
79
+ protected :process_without_request_globals, :process_with_request_globals
80
+ end
81
+ end
82
+
83
+ def process_with_request_globals(request, response, method = :perform_action, *arguments)
84
+ ZeevexThreadsafe::Rails::RequestGlobals.request = request
85
+ process_without_request_globals(request, response, method, *arguments)
86
+ ensure
87
+ ZeevexThreadsafe::Rails::RequestGlobals.reset
88
+ end
89
+ end
90
+
91
+ module Accessors
92
+ def self.included(klass)
93
+ klass.extend ClassMethods
94
+ end
95
+
96
+ module ClassMethods
97
+ def request_global_accessor(name)
98
+ ZeevexThreadsafe::Rails::RequestGlobals.define_request_global_accessors(self.class, name)
99
+ end
100
+ end
101
+ end
102
+
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,12 @@
1
+ class << Thread
2
+ alias orig_new new
3
+ def new
4
+ orig_new do
5
+ begin
6
+ yield
7
+ ensure
8
+ ActiveRecord::Base.connection_pool.release_connection
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ module ZeevexThreadsafe
2
+ module Rails
3
+ end
4
+ end
@@ -0,0 +1,90 @@
1
+ #
2
+ # Usage:
3
+ # class Foo
4
+ # include ThreadLocals
5
+ # thread_local :instance_var_name, :instance_var_name2 # , ...
6
+ # cthread_local :class_var_name, :class_var_name2 # , ...
7
+ # end
8
+ #
9
+ # Foo.class_var_name = "thread specific value on class"
10
+ # Foo.new.instance_var_name = "thread specific value on instance"
11
+ #
12
+ # A hash of options which may be passed in to control the visibility
13
+ # (public, private, protected) and default value of the variable.
14
+ #
15
+ # class Foo
16
+ # thread_local :example, :visibility => :protected, :default => 42
17
+ # end
18
+ # obj = Foo.new
19
+ # obj.example => 42
20
+ # obj.example = "non-default value"
21
+ # obj.example # => "non-default-value"
22
+ #
23
+ #
24
+ module ZeevexThreadsafe::ThreadLocals
25
+ def self.included(klass)
26
+ klass.extend ClassMethods
27
+ klass.send :include, InstanceMethods
28
+ end
29
+
30
+ module InstanceMethods
31
+ private
32
+
33
+ # remove the thread local maps for threads that are no longer active.
34
+ # likely to be painful if many threads are running.
35
+ #
36
+ # must be called manually; otherwise this object may accumulate lots of garbage
37
+ # if it is used from many different threads.
38
+ def _thread_local_clean
39
+ ids = Thread.list.map &:object_id
40
+ (@_thread_local_threads.keys - ids).each { |key| @_thread_local_threads.delete(key) }
41
+ end
42
+ end
43
+
44
+ module ClassMethods
45
+ def thread_local(*args, &block)
46
+ UtilityMethods.define_thread_local_accessors(self, args, block)
47
+ end
48
+
49
+ def cthread_local(*args, &block)
50
+ UtilityMethods.define_thread_local_accessors(self.class, args, block)
51
+ end
52
+ end
53
+
54
+ module UtilityMethods
55
+
56
+ def self.thread_local_hash(base, autocreate = false)
57
+ base.instance_eval do
58
+ if autocreate
59
+ @_thread_local_threads ||= {}
60
+ @_thread_local_threads[Thread.current.object_id] ||= {}
61
+ else
62
+ @_thread_local_threads ? @_thread_local_threads[Thread.current.object_id] : nil
63
+ end
64
+ end
65
+ end
66
+
67
+ def self.define_thread_local_accessors(base, args, block = nil)
68
+ options = args[-1].instance_of?(Hash) ? args.pop : {}
69
+ args.each do |name|
70
+ key = name.to_sym
71
+ base.class_eval do
72
+ define_method name.to_sym do
73
+ hash = ZeevexThreadsafe::ThreadLocals::UtilityMethods.thread_local_hash(self)
74
+ return hash[key] if hash && hash.key?(key)
75
+ return block.call(self, key) if block
76
+ return options[:default]
77
+ end
78
+
79
+ define_method (name.to_s + "=").to_sym do |value|
80
+ ZeevexThreadsafe::ThreadLocals::UtilityMethods.thread_local_hash(self, true)[key] = value
81
+ end
82
+
83
+ if options[:visibility]
84
+ send options[:visibility], name.to_sym, (name.to_s + "=").to_sym
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,115 @@
1
+ require 'thread'
2
+
3
+ begin
4
+ require 'active_support/core_ext'
5
+ rescue LoadError
6
+ require 'zeevex_threadsafe/aliasing'
7
+ end
8
+
9
+ module ZeevexThreadsafe
10
+ module ThreadSafer
11
+
12
+ def self.included base
13
+ base.extend ZeevexThreadsafe::ThreadSafer::ClassMethods
14
+ base.class_eval do
15
+ include ZeevexThreadsafe::ThreadSafer::InstanceMethods
16
+
17
+ @delayed_thread_safe_methods = []
18
+
19
+ if method_defined?(:method_added)
20
+ class << self
21
+ alias_method_chain :method_added, :thread_safe
22
+ end
23
+ else
24
+ class << self
25
+ alias_method :method_added, :method_added_with_thread_safe
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ module ClassMethods
32
+ def method_added_with_thread_safe(method)
33
+ if !method.to_s.match(/_without_mutex$/) && @delayed_thread_safe_methods.include?(method)
34
+ @delayed_thread_safe_methods.delete method
35
+ make_thread_safe(method)
36
+ end
37
+ method_added_without_thread_safe(method) if method_defined?(:method_added_without_thread_safe)
38
+ end
39
+
40
+ def make_thread_safe *methods
41
+ methods.each do |method|
42
+ method = method.to_sym
43
+ if method_defined?(method)
44
+ make_thread_safe_now method
45
+ else
46
+ @delayed_thread_safe_methods << method
47
+ end
48
+ end
49
+ end
50
+
51
+ def make_thread_safe_now *methods
52
+ methods.each do |method|
53
+ old_name = method.to_sym
54
+ new_name = (old_name.to_s + "_without_mutex").to_sym
55
+ alias_method new_name, old_name
56
+ myprox = lambda do |*args, &block|
57
+ _ts_mutex.synchronize do
58
+ @in_thread_safe_method = old_name
59
+ res = __send__ new_name, *args, &block
60
+ @in_thread_safe_method = nil
61
+ res
62
+ end
63
+ end
64
+ define_method old_name, myprox
65
+ end
66
+ end
67
+
68
+ end
69
+
70
+ module InstanceMethods
71
+ def _ts_mutex
72
+ @_ts_mutex ||= Mutex.new
73
+ end
74
+ end
75
+
76
+ end
77
+ end
78
+
79
+ #
80
+ # class Foo
81
+ # include ZeevexThreadsafe::ThreadSafer
82
+ #
83
+ # class << self
84
+ # include ZeevexThreadsafe::ThreadSafer
85
+ # end
86
+ # def foo_unsafe
87
+ # puts "unsafe: #{@in_thread_safe_method}"
88
+ # end
89
+ #
90
+ # def foo_safe
91
+ # puts "Safe: #{@in_thread_safe_method}"
92
+ # end
93
+ #
94
+ # make_thread_safe :foo_safe, :foo_safe_delayed, :hibbity, :hop
95
+ #
96
+ # def foo_safe_delayed
97
+ # puts "safe delayed: #{@in_thread_safe_method}"
98
+ # end
99
+ #
100
+ # class << self
101
+ # def cmethod
102
+ # puts "in cmethod: #{@in_thread_safe_method}"
103
+ # end
104
+ # make_thread_safe :cmethod
105
+ # end
106
+ #
107
+ # end
108
+ #
109
+ # Foo.new.foo_unsafe
110
+ # Foo.new.foo_safe
111
+ # Foo.new.foo_safe_delayed
112
+ # Foo.cmethod
113
+ # Foo.class_eval do
114
+ # puts "remaining delayed methods are #{@delayed_thread_safe_methods.inspect}"
115
+ # end
@@ -0,0 +1,3 @@
1
+ module ZeevexThreadsafe
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,3 @@
1
+ module ZeevexThreadsafe
2
+ # Your code goes here...
3
+ end
@@ -0,0 +1,6 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ $: << File.expand_path(File.dirname(__FILE__) + '../lib')
4
+
5
+ require 'rspec'
6
+ require 'zeevex_threadsafe'
@@ -0,0 +1,372 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
3
+
4
+ require 'zeevex_threadsafe/thread_locals'
5
+
6
+ class ThreadLocalTestClass
7
+ include ZeevexThreadsafe::ThreadLocals
8
+ thread_local(:block1, :block2) { |obj,key| "DEFAULT FOR #{obj.class}.#{key.to_s}" }
9
+ thread_local :plain
10
+ thread_local :mult1, :mult2
11
+ thread_local :default_var, :default => 100
12
+ thread_local :protected_var, :default => 200, :visibility => :protected
13
+ thread_local :private_var, :default => 200, :visibility => :private
14
+ thread_local :public_var, :default => 200, :visibility => :public
15
+
16
+ private
17
+
18
+ thread_local :contextually_private_var
19
+
20
+ public
21
+
22
+ thread_local :contextually_public_var
23
+ end
24
+
25
+ module ClassDuping
26
+ def class_name
27
+ @@class_name || self.class.to_s
28
+ end
29
+
30
+ def class_name=(name)
31
+ @@class_name = name
32
+ end
33
+
34
+ def dupe_for_testing(name = self.name)
35
+ self.dup.tap do |clazz|
36
+ clazz.class_name = name
37
+ end
38
+ end
39
+ end
40
+
41
+ class ThreadLocalClassLevel
42
+ include ZeevexThreadsafe::ThreadLocals
43
+ extend ClassDuping
44
+ cthread_local(:block1, :block2) { |obj,key| "DEFAULT FOR #{obj.class_name}.#{key.to_s}" }
45
+ cthread_local :plain
46
+ cthread_local :mult1, :mult2
47
+ cthread_local :default_var, :default => 100
48
+ cthread_local :protected_var, :default => 200, :visibility => :protected
49
+ cthread_local :private_var, :default => 200, :visibility => :private
50
+ cthread_local :public_var, :default => 200, :visibility => :public
51
+
52
+ private
53
+
54
+ cthread_local :contextually_private_var
55
+
56
+ public
57
+
58
+ cthread_local :contextually_public_var
59
+ end
60
+
61
+ class ThreadLocalClassLevel2
62
+ include ZeevexThreadsafe::ThreadLocals
63
+ extend ClassDuping
64
+
65
+ cthread_local :plain
66
+ end
67
+
68
+
69
+ describe ZeevexThreadsafe::ThreadLocals do
70
+
71
+ context "class inclusion" do
72
+ it "should not add class methods to a class unless included" do
73
+ Object.should_not respond_to(:thread_local)
74
+ Object.should_not respond_to(:cthread_local)
75
+ end
76
+
77
+ it "should add class methods to a class when included" do
78
+ ThreadLocalTestClass.should respond_to(:thread_local)
79
+ ThreadLocalTestClass.should respond_to(:cthread_local)
80
+ end
81
+ end
82
+
83
+
84
+ context "instance thread locals" do
85
+ let :instance do
86
+ ThreadLocalTestClass.new
87
+ end
88
+ let :instance2 do
89
+ ThreadLocalTestClass.new
90
+ end
91
+ subject { instance }
92
+ context "method definition" do
93
+
94
+ [:block1, :block2, :plain, :mult1, :mult2, :block1, :default_var,
95
+ :protected_var, :public_var, :contextually_private_var, :contextually_public_var].each do |key|
96
+ it "should have defined reader for #{key}" do
97
+ instance.should respond_to(key)
98
+ end
99
+
100
+ it "should have defined writer for #{key}" do
101
+ instance.should respond_to((key.to_s + "=").to_sym)
102
+ end
103
+ end
104
+
105
+ [:private_var].each do |key|
106
+ it "should have defined reader for #{key}" do
107
+ instance.private_methods.should include(key.to_s)
108
+ end
109
+
110
+ it "should have defined writer for #{key}" do
111
+ instance.private_methods.should include(key.to_s + "=")
112
+ end
113
+ end
114
+ end
115
+
116
+ context "default value" do
117
+ [:plain, :mult1, :mult2,
118
+ :contextually_private_var, :contextually_public_var].each do |key|
119
+ it "should returns nil unless configured otherwise" do
120
+ instance.send(key).should be_nil
121
+ end
122
+ end
123
+
124
+ it "should return constant value if :default option provided" do
125
+ instance.default_var.should == 100
126
+ end
127
+
128
+ it "should return computed value if :block option provided" do
129
+ instance.block1.should == "DEFAULT FOR ThreadLocalTestClass.block1"
130
+ end
131
+ end
132
+
133
+ context "when setting" do
134
+ it "should return the newly set value after being set" do
135
+ instance.block1 = "BLOCK!"
136
+ instance.block1.should == "BLOCK!"
137
+ end
138
+
139
+ it "should keep values separately for two instances" do
140
+ instance.block1 = "INSTANCE1 VALUE"
141
+ instance2.block1 = "INSTANCE2 VALUE"
142
+ instance.block1.should == "INSTANCE1 VALUE"
143
+ instance2.block1.should == "INSTANCE2 VALUE"
144
+ end
145
+ end
146
+
147
+ context "visibility" do
148
+ it "should define methods as public by default" do
149
+ instance.public_methods.should include("plain", "plain=", "mult1", "mult1=")
150
+ end
151
+
152
+ it "should define methods as protected when :visibility => :protected" do
153
+ instance.protected_methods.should include("protected_var", "protected_var=")
154
+ end
155
+
156
+ it "should define methods as private when :visibility => :private" do
157
+ instance.private_methods.should include("private_var", "private_var=")
158
+ end
159
+
160
+ it "should define methods as public when :visibility is not provided, even in a private context" do
161
+ instance.public_methods.should include("contextually_private_var", "contextually_private_var=")
162
+ end
163
+ end
164
+
165
+ context "thread locality" do
166
+ it "should return the same value in the same thread when called consecutively" do
167
+ instance.mult1 = "barbar"
168
+ instance.mult1.should == "barbar"
169
+ instance.mult1.should == "barbar"
170
+ end
171
+
172
+ it "should return the default value in a new thread" do
173
+ instance.default_var = "FOO"
174
+ instance.default_var.should == "FOO"
175
+ Thread.new { @res = instance.default_var }.join
176
+ @res.should == 100
177
+ end
178
+
179
+ it "should not affect the value in one thread when set in a different thread" do
180
+ instance.default_var = "FOO"
181
+ instance.default_var.should == "FOO"
182
+ Thread.new { instance.default_var = 2500 }.join
183
+ instance.default_var.should == "FOO"
184
+ end
185
+ end
186
+
187
+ context "per thread book-keeping" do
188
+ let :book do
189
+ instance.instance_variable_get("@_thread_local_threads")
190
+ end
191
+
192
+ it "should have no book-keeping data when first created" do
193
+ book.should == nil
194
+ end
195
+
196
+ it "should have no book-keeping data for default value access" do
197
+ instance.block1
198
+ book.should == nil
199
+ end
200
+
201
+ it "should have one thread's worth of book-keeping data when first variable is set" do
202
+ instance.mult1 = 3000
203
+ book.should be_instance_of(Hash)
204
+ book.should have(1).item
205
+ end
206
+
207
+ it "should have two thread's worth of book-keeping data when variable is set in two threads" do
208
+ instance.mult1 = 3000
209
+ Thread.new { instance.mult1 = 4000 }.join
210
+ book.should have(2).items
211
+ end
212
+
213
+ it "should have one thread's worth of data when var is set in two threads, one thread terminated, and then cleaned" do
214
+ instance.mult1 = 3000
215
+ Thread.new { instance.mult1 = 4000 }.join
216
+ instance.send :_thread_local_clean
217
+ book.should have(1).item
218
+ end
219
+ end
220
+
221
+ end
222
+
223
+
224
+ context "class-level thread locals" do
225
+
226
+ context "instance thread locals" do
227
+ let :instance do
228
+ ThreadLocalClassLevel.dupe_for_testing
229
+ end
230
+
231
+ let :instance2 do
232
+ ThreadLocalClassLevel2.dupe_for_testing
233
+ end
234
+
235
+ subject { instance }
236
+ context "method definition" do
237
+
238
+ [:block1, :block2, :plain, :mult1, :mult2, :block1, :default_var,
239
+ :protected_var, :public_var, :contextually_private_var, :contextually_public_var].each do |key|
240
+ it "should have defined reader for #{key}" do
241
+ instance.should respond_to(key)
242
+ end
243
+
244
+ it "should have defined writer for #{key}" do
245
+ instance.should respond_to((key.to_s + "=").to_sym)
246
+ end
247
+ end
248
+
249
+ [:private_var].each do |key|
250
+ it "should have defined reader for #{key}" do
251
+ instance.private_methods.should include(key.to_s)
252
+ end
253
+
254
+ it "should have defined writer for #{key}" do
255
+ instance.private_methods.should include(key.to_s + "=")
256
+ end
257
+ end
258
+ end
259
+
260
+ context "default value" do
261
+ subject { instance }
262
+ [:plain, :mult1, :mult2,
263
+ :contextually_private_var, :contextually_public_var].each do |key|
264
+ it "should return nil for #{key}, being configured without default" do
265
+ instance.send(key).should be_nil
266
+ end
267
+ end
268
+
269
+ it "should return constant value if :default option provided" do
270
+ instance.default_var.should == 100
271
+ end
272
+
273
+ it "should return computed value if :block option provided" do
274
+ instance.block1.should == "DEFAULT FOR ThreadLocalClassLevel.block1"
275
+ end
276
+ end
277
+
278
+ context "when setting" do
279
+ it "should return the newly set value after being set" do
280
+ instance.block1 = "BLOCK!"
281
+ instance.block1.should == "BLOCK!"
282
+ end
283
+
284
+
285
+ it "should keep values separately for two instances" do
286
+ instance.plain = "INSTANCE1 VALUE"
287
+ instance2.plain = "INSTANCE2 VALUE"
288
+ instance.plain.should == "INSTANCE1 VALUE"
289
+ instance2.plain.should == "INSTANCE2 VALUE"
290
+ end
291
+ end
292
+
293
+ context "visibility" do
294
+ it "should define methods as public by default" do
295
+ instance.public_methods.should include("plain", "plain=", "mult1", "mult1=")
296
+ end
297
+
298
+ it "should define methods as protected when :visibility => :protected" do
299
+ instance.protected_methods.should include("protected_var", "protected_var=")
300
+ end
301
+
302
+ it "should define methods as private when :visibility => :private" do
303
+ instance.private_methods.should include("private_var", "private_var=")
304
+ end
305
+
306
+ it "should define methods as public when :visibility is not provided, even in a private context" do
307
+ instance.public_methods.should include("contextually_private_var", "contextually_private_var=")
308
+ end
309
+ end
310
+
311
+ context "thread locality" do
312
+ it "should return the same value in the same thread when called consecutively" do
313
+ instance.mult1 = "barbar"
314
+ instance.mult1.should == "barbar"
315
+ instance.mult1.should == "barbar"
316
+ end
317
+
318
+ it "should return the default value in a new thread" do
319
+ instance.default_var = "FOO"
320
+ instance.default_var.should == "FOO"
321
+ Thread.new { @res = instance.default_var }.join
322
+ @res.should == 100
323
+ end
324
+
325
+ it "should not affect the value in one thread when set in a different thread" do
326
+ instance.default_var = "FOO"
327
+ instance.default_var.should == "FOO"
328
+ Thread.new { instance.default_var = 2500 }.join
329
+ instance.default_var.should == "FOO"
330
+ end
331
+ end
332
+ end
333
+ end
334
+
335
+ class DualThreadLocalTypes
336
+ include ZeevexThreadsafe::ThreadLocals
337
+ thread_local :foo
338
+ cthread_local :foo
339
+ end
340
+
341
+ context "same named thread locals at class and instance level" do
342
+ let :clazz do
343
+ DualThreadLocalTypes
344
+ end
345
+ let :instance do
346
+ DualThreadLocalTypes.new
347
+ end
348
+ it "should not set one when setting the other" do
349
+ clazz.foo = "classvalue"
350
+ instance.foo = "instancevalue"
351
+ clazz.foo.should == "classvalue"
352
+ instance.foo.should == "instancevalue"
353
+ end
354
+ end
355
+
356
+ class MetaClassThreadLocals
357
+ class << self
358
+ include ZeevexThreadsafe::ThreadLocals
359
+ thread_local :foo
360
+ end
361
+ end
362
+
363
+ context "defining thread_local on metaclass of a class" do
364
+ let :instance do
365
+ MetaClassThreadLocals
366
+ end
367
+ subject { instance }
368
+ it "should have a :foo method defined" do
369
+ instance.should respond_to(:foo)
370
+ end
371
+ end
372
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "zeevex_threadsafe/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "zeevex_threadsafe"
7
+ s.version = ZeevexThreadsafe::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Robert Sanders"]
10
+ s.email = ["robert@zeevex.com"]
11
+ s.homepage = "http://github.com/zeevex/zeevex_threadsafe"
12
+ s.summary = %q{Utilities to help in creating thread-safe apps}
13
+ s.description = %q{Utilities to help in creating thread-safe apps}
14
+
15
+ s.rubyforge_project = "zeevex_threadsafe"
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
+ s.add_dependency 'zeevex_delayed'
24
+
25
+ s.add_development_dependency 'rspec', '~> 2.9.0'
26
+ s.add_development_dependency 'rake'
27
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zeevex_threadsafe
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Robert Sanders
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-05-03 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: zeevex_delayed
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :runtime
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: rspec
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ~>
50
+ - !ruby/object:Gem::Version
51
+ segments:
52
+ - 2
53
+ - 9
54
+ - 0
55
+ version: 2.9.0
56
+ type: :development
57
+ version_requirements: *id003
58
+ - !ruby/object:Gem::Dependency
59
+ name: rake
60
+ prerelease: false
61
+ requirement: &id004 !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ type: :development
69
+ version_requirements: *id004
70
+ description: Utilities to help in creating thread-safe apps
71
+ email:
72
+ - robert@zeevex.com
73
+ executables: []
74
+
75
+ extensions: []
76
+
77
+ extra_rdoc_files: []
78
+
79
+ files:
80
+ - .gitignore
81
+ - Gemfile
82
+ - Rakefile
83
+ - lib/zeevex_threadsafe.rb
84
+ - lib/zeevex_threadsafe/aliasing.rb
85
+ - lib/zeevex_threadsafe/rails.rb
86
+ - lib/zeevex_threadsafe/rails/request_globals.rb
87
+ - lib/zeevex_threadsafe/rails/thread_pool_release.rb
88
+ - lib/zeevex_threadsafe/thread_locals.rb
89
+ - lib/zeevex_threadsafe/thread_safer.rb
90
+ - lib/zeevex_threadsafe/version.rb
91
+ - spec/spec_helper.rb
92
+ - spec/thread_locals_spec.rb
93
+ - zeevex_threadsafe.gemspec
94
+ has_rdoc: true
95
+ homepage: http://github.com/zeevex/zeevex_threadsafe
96
+ licenses: []
97
+
98
+ post_install_message:
99
+ rdoc_options: []
100
+
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ requirements: []
118
+
119
+ rubyforge_project: zeevex_threadsafe
120
+ rubygems_version: 1.3.6
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Utilities to help in creating thread-safe apps
124
+ test_files:
125
+ - spec/spec_helper.rb
126
+ - spec/thread_locals_spec.rb