tlattr_accessors 0.0.3
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/LICENSE +20 -0
- data/README.rdoc +81 -0
- data/lib/tlattr_accessors.rb +45 -0
- data/spec/thread_local_accessors_spec.rb +87 -0
- data/tlattr_accessors.gemspec +19 -0
- metadata +65 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT license:
|
2
|
+
Copyright (c) 2009 Maximilian Schöfmann
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
6
|
+
in the Software without restriction, including without limitation the rights
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
9
|
+
furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in
|
12
|
+
all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
= Thread-local accessors for your classes
|
2
|
+
|
3
|
+
Yet another tiny library to tackle this problem.
|
4
|
+
|
5
|
+
Install with: <tt>gem install schoefmax-tlattr_accessors --source=http://gems.github.com</tt>
|
6
|
+
|
7
|
+
=== Example
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'tlattr_accessors'
|
11
|
+
|
12
|
+
class ThreadExample
|
13
|
+
extend ThreadLocalAccessors
|
14
|
+
tlattr_accessor :foo
|
15
|
+
|
16
|
+
def test
|
17
|
+
self.foo = "bla"
|
18
|
+
Thread.new {
|
19
|
+
puts foo # prints "nil"
|
20
|
+
self.foo = "blubb"
|
21
|
+
puts foo # prints "blubb"
|
22
|
+
}.join
|
23
|
+
puts foo # prints "bla"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
ThreadExample.new.test
|
28
|
+
|
29
|
+
If you want to enable them globally, add this somewhere (e.g. an initializer in Rails)
|
30
|
+
|
31
|
+
Object.send :extend, ThreadLocalAccessors
|
32
|
+
|
33
|
+
=== Default values
|
34
|
+
|
35
|
+
Adding +true+ as last parameter will cause the first value set on the
|
36
|
+
attribute to act as default value for all other threads:
|
37
|
+
|
38
|
+
tlattr_accessor :yeah, :baby, true
|
39
|
+
|
40
|
+
def test_default
|
41
|
+
self.yeah = "bla"
|
42
|
+
Thread.new {
|
43
|
+
puts yeah # prints "bla"
|
44
|
+
puts baby # prints "nil"
|
45
|
+
self.baby = "blubb"
|
46
|
+
self.yeah = "blabla"
|
47
|
+
}.join
|
48
|
+
puts yeah # prints "bla"
|
49
|
+
puts baby # prints "blubb"
|
50
|
+
end
|
51
|
+
|
52
|
+
=== Getters and Setters
|
53
|
+
|
54
|
+
This gem doesn't support <tt>tlattr</tt> or <tt>tlattr_reader|writer</tt> for
|
55
|
+
the simple reason that they don't make any sense here (you don't have an "instance
|
56
|
+
variable", so you need both methods).
|
57
|
+
If you want to hide one of them from your API, you can always make them private:
|
58
|
+
|
59
|
+
tlattr_accessor :foo
|
60
|
+
private :foo= # hide the setter
|
61
|
+
|
62
|
+
=== Performance
|
63
|
+
|
64
|
+
The <tt>Thread.current</tt>-Hash is a global namespace. Using it to store
|
65
|
+
thread-local variables safely requires carefully crafted keys, which tend to
|
66
|
+
be rather expensive to compute. This hurts performance if the attribute is
|
67
|
+
accessed frequently.
|
68
|
+
Therefore, this library uses a different approach, which is a lot faster:
|
69
|
+
The values are stored in a separate hash which is keyed by the object_id of
|
70
|
+
the thread. Finalizers make sure no memory is leaked when threads finish
|
71
|
+
(see the spec).
|
72
|
+
|
73
|
+
=== Running specs
|
74
|
+
|
75
|
+
If you haven't already, install the rspec gem, then run:
|
76
|
+
|
77
|
+
spec spec
|
78
|
+
|
79
|
+
|
80
|
+
(c) 2009, Max Schoefmann <max (a) pragmatic-it de>
|
81
|
+
Released under the MIT license
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ThreadLocalAccessors
|
2
|
+
# Creates thread-local accessors for the given attribute name.
|
3
|
+
#
|
4
|
+
# === Example:
|
5
|
+
#
|
6
|
+
# tlattr_accessor :my_attr, :another_attr
|
7
|
+
#
|
8
|
+
# === Default values
|
9
|
+
#
|
10
|
+
# You can make the attribute inherit the first value that was set on it in
|
11
|
+
# any thread:
|
12
|
+
#
|
13
|
+
# tlattr_accessor :my_attr, true
|
14
|
+
#
|
15
|
+
# def initialize
|
16
|
+
# self.my_attr = "foo"
|
17
|
+
# Thread.new do
|
18
|
+
# puts self.my_attr # => "foo" (instead of nil)
|
19
|
+
# end.join
|
20
|
+
# end
|
21
|
+
def tlattr_accessor(*names)
|
22
|
+
first_is_default = names.pop if [true, false].include?(names.last)
|
23
|
+
names.each do |name|
|
24
|
+
ivar = "@_tlattr_#{name}"
|
25
|
+
class_eval %Q{
|
26
|
+
def #{name}
|
27
|
+
if #{ivar}
|
28
|
+
#{ivar}[Thread.current.object_id]
|
29
|
+
else
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def #{name}=(val)
|
35
|
+
#{ivar} = Hash.new #{'{|h, k| h[k] = val}' if first_is_default} unless #{ivar}
|
36
|
+
thread_id = Thread.current.object_id
|
37
|
+
unless #{ivar}.has_key?(thread_id)
|
38
|
+
ObjectSpace.define_finalizer(Thread.current, lambda { #{ivar}.delete(thread_id) })
|
39
|
+
end
|
40
|
+
#{ivar}[thread_id] = val
|
41
|
+
end
|
42
|
+
}, __FILE__, __LINE__
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'tlattr_accessors')
|
4
|
+
|
5
|
+
describe ThreadLocalAccessors do
|
6
|
+
|
7
|
+
class Foo
|
8
|
+
extend ThreadLocalAccessors
|
9
|
+
tlattr_accessor :bar, true
|
10
|
+
tlattr_accessor :foo, :baz
|
11
|
+
end
|
12
|
+
|
13
|
+
class Bar
|
14
|
+
def initialize(value)
|
15
|
+
@value = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should allow defining multiple attributes at once' do
|
20
|
+
x = Foo.new
|
21
|
+
[:foo, :foo=, :baz=, :baz].each do |method|
|
22
|
+
x.should respond_to(method)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should store values local to the thread" do
|
27
|
+
x = Foo.new
|
28
|
+
x.baz = 2
|
29
|
+
Thread.new do
|
30
|
+
x.baz = 3
|
31
|
+
Thread.new do
|
32
|
+
x.baz = 5
|
33
|
+
end.join
|
34
|
+
x.baz.should == 3
|
35
|
+
end.join
|
36
|
+
x.baz.should == 2
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should, by default, not return a default value' do
|
40
|
+
x = Foo.new
|
41
|
+
x.baz = 2
|
42
|
+
Thread.new do
|
43
|
+
x.baz.should be_nil
|
44
|
+
end.join
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should, if told to, use the first value as default for subsequent threads' do
|
48
|
+
# Foo#bar is defined with +true+ as last param
|
49
|
+
x = Foo.new
|
50
|
+
x.bar = 2
|
51
|
+
Thread.new do
|
52
|
+
x.bar.should == 2
|
53
|
+
x.bar = 3
|
54
|
+
Thread.new do
|
55
|
+
x.bar.should == 2
|
56
|
+
x.bar = 5
|
57
|
+
end.join
|
58
|
+
x.bar.should == 3
|
59
|
+
end.join
|
60
|
+
x.bar.should == 2
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should not leak memory' do
|
64
|
+
x = Foo.new
|
65
|
+
n = 6000
|
66
|
+
# create many thread-local values to make sure GC is invoked
|
67
|
+
n.times do
|
68
|
+
Thread.new do
|
69
|
+
x.bar = Bar.new(rand)
|
70
|
+
end.join
|
71
|
+
end
|
72
|
+
hash = x.send :instance_variable_get, '@_tlattr_bar'
|
73
|
+
hash.size.should < (n / 2) # it should be a lot lower than n!
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should define only one finalizer per thread' do
|
77
|
+
ObjectSpace.should_receive(:define_finalizer).exactly(:twice)
|
78
|
+
x = Foo.new
|
79
|
+
2.times do
|
80
|
+
Thread.new do
|
81
|
+
10.times { x.foo = "bar" }
|
82
|
+
end.join
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{tlattr_accessors}
|
5
|
+
s.version = "0.0.3"
|
6
|
+
s.authors = ["Maximilian Sch\303\266fmann"]
|
7
|
+
s.date = %q{2009-03-10}
|
8
|
+
s.description = "thread-local accessors for your classes"
|
9
|
+
s.email = "max@pragmatic-it.de"
|
10
|
+
s.extra_rdoc_files = ["LICENSE", "README.rdoc"]
|
11
|
+
s.files = ["lib/tlattr_accessors.rb", "LICENSE", "README.rdoc", "spec/thread_local_accessors_spec.rb", "tlattr_accessors.gemspec"]
|
12
|
+
s.has_rdoc = true
|
13
|
+
s.homepage = "http://github.com/schoefmax/tlattr_accessors"
|
14
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "tlattr_accessors", "--main", "README.rdoc"]
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
s.rubyforge_project = "tlattr_accessors"
|
17
|
+
s.rubygems_version = %q{1.3.1}
|
18
|
+
s.summary = "thread-local accessors for your classes"
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tlattr_accessors
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Maximilian Sch\xC3\xB6fmann"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-10 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: thread-local accessors for your classes
|
17
|
+
email: max@pragmatic-it.de
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- lib/tlattr_accessors.rb
|
27
|
+
- LICENSE
|
28
|
+
- README.rdoc
|
29
|
+
- spec/thread_local_accessors_spec.rb
|
30
|
+
- tlattr_accessors.gemspec
|
31
|
+
has_rdoc: true
|
32
|
+
homepage: http://github.com/schoefmax/tlattr_accessors
|
33
|
+
licenses: []
|
34
|
+
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options:
|
37
|
+
- --line-numbers
|
38
|
+
- --inline-source
|
39
|
+
- --title
|
40
|
+
- tlattr_accessors
|
41
|
+
- --main
|
42
|
+
- README.rdoc
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project: tlattr_accessors
|
60
|
+
rubygems_version: 1.3.5
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: thread-local accessors for your classes
|
64
|
+
test_files: []
|
65
|
+
|