Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial rev

  • Loading branch information...
commit 760b3e0e53d3a9eb1a8ad0cabd94ca5b97e88d5f 0 parents
@schoefmax authored
20 LICENSE
@@ -0,0 +1,20 @@
+The MIT license:
+Copyright (c) 2009 Maximilian Schöfmann
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
80 README.rdoc
@@ -0,0 +1,80 @@
+= Thread-local accessors for your classes
+
+Yet another tiny library to tackle this problem.
+
+=== Example
+
+ require 'rubygems'
+ require 'tlattr_accessors'
+
+ class ThreadExample
+ extend ThreadLocalAccessors
+ tlattr_accessor :foo
+
+ def test
+ self.foo = "bla"
+ Thread.new {
+ puts foo # prints "nil"
+ self.foo = "blubb"
+ puts foo # prints "blubb"
+ }.join
+ puts foo # prints "bla"
+ end
+ end
+
+ ThreadExample.new.test
+
+If you want to enable them globally, add this somewhere (e.g. an initializer in Rails)
+
+ Object.send :extend, ThreadLocalAccessors
+
+=== Default values
+
+Adding +true+ as last parameter will cause the first value set on the
+attribute to act as default value for all other threads:
+
+ tlattr_accessor :yeah, :baby, true
+
+ def test_default
+ self.yeah = "bla"
+ Thread.new {
+ puts yeah # prints "bla"
+ puts baby # prints "nil"
+ self.baby = "blubb"
+ self.yeah = "blabla"
+ }.join
+ puts yeah # prints "bla"
+ puts baby # prints "blubb"
+ end
+
+=== Note about JRuby
+
+As JRuby doesn't support finalizers, using this gem with JRuby in an long running
+app that spins up many threads will cause a memory leak.
+
+In this case, use the gem from Mathias Meyer, which does the same (and more)
+but is slower if you need to access the values frequently (it uses the
+<tt>Thread.current</tt>-Hash with dynamically generated keys to avoid clashes
+in the global namespace of <tt>Thread.current</tt>):
+
+http://github.com/mattmatt/threadlocal-attr-accessor
+
+=== Getters and Setters
+
+This gem doesn't support <tt>tlattr</tt> or <tt>tlattr_reader|writer</tt> for
+the simple reason that they don't make any sense here (you don't have an "instance
+variable", so you need both methods).
+If you want to hide one of them from your API, you can always make them private:
+
+ tlattr_accessor :foo
+ private :foo= # hide the setter
+
+=== Running specs
+
+If you haven't already, install the rspec gem, then run:
+
+ spec spec
+
+
+(c) 2009, Max Schoefmann <max (a) pragmatic-it de>
+Released under the MIT license
44 lib/tlattr_accessors.rb
@@ -0,0 +1,44 @@
+module ThreadLocalAccessors
+ # Creates thread-local accessors for the given attribute name.
+ #
+ # === Example:
+ #
+ # tlattr_accessor :my_attr, :another_attr
+ #
+ # === Default values
+ #
+ # You can make the attribute inherit the first value that was set on it in
+ # any thread:
+ #
+ # tlattr_accessor :my_attr, true
+ #
+ # def initialize
+ # self.my_attr = "foo"
+ # Thread.new do
+ # puts self.my_attr # => "foo" (instead of nil)
+ # end.join
+ # end
+ def tlattr_accessor(*names)
+ first_is_default = names.pop if [true, false].include?(names.last)
+ names.each do |name|
+ ivar = "@_tlattr_#{name}"
+ class_eval %Q{
+ def #{name}
+ if #{ivar}
+ #{ivar}[Thread.current.object_id]
+ else
+ nil
+ end
+ end
+
+ def #{name}=(val)
+ #{ivar} = Hash.new #{'{|h, k| h[k] = val}' if first_is_default} unless #{ivar}
+ unless #{ivar}.has_key?(Thread.current)
+ ObjectSpace.define_finalizer(Thread.current, lambda {|id| #{ivar}.delete(id) })
+ end
+ #{ivar}[Thread.current.object_id] = val
+ end
+ }, __FILE__, __LINE__
+ end
+ end
+end
78 spec/thread_local_accessors_spec.rb
@@ -0,0 +1,78 @@
+require 'rubygems'
+require 'spec'
+require File.join(File.dirname(__FILE__), '..', 'lib', 'tlattr_accessors')
+
+describe ThreadLocalAccessors do
+
+ class Foo
+ extend ThreadLocalAccessors
+ tlattr_accessor :bar, true
+ tlattr_accessor :foo, :baz
+ end
+
+ class Bar
+ def initialize(value)
+ @value = value
+ end
+ end
+
+ it 'should allow defining multiple attributes at once' do
+ x = Foo.new
+ [:foo, :foo=, :baz=, :baz].each do |method|
+ x.should respond_to(method)
+ end
+ end
+
+ it "should store values local to the thread" do
+ x = Foo.new
+ x.baz = 2
+ Thread.new do
+ x.baz = 3
+ Thread.new do
+ x.baz = 5
+ end.join
+ x.baz.should == 3
+ end.join
+ x.baz.should == 2
+ end
+
+ it 'should, by default, not return a default value' do
+ x = Foo.new
+ x.baz = 2
+ Thread.new do
+ x.baz.should be_nil
+ end.join
+ end
+
+ it 'should, if told to, use the first value as default for subsequent threads' do
+ # Foo#bar is defined with +true+ as last param
+ x = Foo.new
+ x.bar = 2
+ Thread.new do
+ x.bar.should == 2
+ x.bar = 3
+ Thread.new do
+ x.bar.should == 2
+ x.bar = 5
+ end.join
+ x.bar.should == 3
+ end.join
+ x.bar.should == 2
+ end
+
+ # This will epically FAIL under JRuby, as JRuby doesn't support finalizers
+ it 'should not leak memory' do
+ x = Foo.new
+ n = 6000
+ # create many thread-local values to make sure GC is invoked
+ n.times do
+ Thread.new do
+ x.bar = Bar.new(rand)
+ end.join
+ end
+ hash = x.send :instance_variable_get, '@_tlattr_bar'
+ hash.size.should < (n / 2) # it should be a lot lower than n!
+ end
+
+end
+
19 tlattr_accessors.gemspec
@@ -0,0 +1,19 @@
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{tlattr_accessors}
+ s.version = "0.0.1"
+ s.authors = ["Maximilian Sch\303\266fmann"]
+ s.date = %q{2009-03-10}
+ s.description = "thread-local accessors for your classes"
+ s.email = "max@pragmatic-it.de"
+ s.extra_rdoc_files = ["LICENSE", "README.rdoc"]
+ s.files = ["lib/tlattr_accessors.rb", "LICENSE", "README.rdoc", "spec/thread_local_accessors_spec.rb", "tlattr_accessors.gemspec"]
+ s.has_rdoc = true
+ s.homepage = "http://github.com/schoefmax/tlattr_accessors"
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "tlattr_accessors", "--main", "README.rdoc"]
+ s.require_paths = ["lib"]
+ s.rubyforge_project = "tlattr_accessors"
+ s.rubygems_version = %q{1.3.1}
+ s.summary = "thread-local accessors for your classes"
+end
Please sign in to comment.
Something went wrong with that request. Please try again.