zeevex_threadsafe 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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