sync_attr 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -18,7 +18,7 @@ task :gem do |t|
18
18
  s.date = Date.today.to_s
19
19
  s.summary = "Thread safe accessors for Ruby class and instance attributes. Supports thread safe lazy loading of attributes"
20
20
  s.description = "SyncAttr is a mixin to read, write and lazy initialize both class and instance variables in a multi-threaded environment when the attribute could be modified by two threads at the same time, written in Ruby."
21
- s.files = FileList["./**/*"].exclude('*.gem', 'nbproject').map{|f| f.sub(/^\.\//, '')}
21
+ s.files = FileList['**/*'].exclude('*.gem', /nbproject/)
22
22
  s.has_rdoc = true
23
23
  end
24
24
  Gem::Builder.new(gemspec).build
@@ -3,7 +3,7 @@
3
3
  require 'sync_attr'
4
4
 
5
5
  # Sample class with lazy initialized Synchronized Class Attributes
6
- def Person
6
+ class Person
7
7
  include SyncAttr
8
8
 
9
9
  # Thread safe Class Attribute reader for name
@@ -20,12 +20,11 @@ def Person
20
20
  end
21
21
  end
22
22
 
23
- person = Person.new
24
- puts "The person is #{person.name} with age #{person.age}"
23
+ puts "The person is #{Person.name} with age #{Person.age}"
25
24
 
26
- person.age = 22
27
- puts "The person is #{person.name} now has age #{person.age}"
25
+ Person.age = 22
26
+ puts "The person is #{Person.name} now has age #{Person.age}"
28
27
 
29
- person.age = Proc.new {|age| age += 1 }
30
- puts "The person is #{person.name} now has age #{person.age}"
28
+ Person.age = Proc.new {|age| age += 1 }
29
+ puts "The person is #{Person.name} now has age #{Person.age}"
31
30
 
@@ -0,0 +1,35 @@
1
+ # Class Attribute Example
2
+ #
3
+ require 'sync_attr'
4
+
5
+ # Sample class with lazy initialized Synchronized Class Attributes
6
+ class Person
7
+ include SyncAttr
8
+
9
+ # Thread safe Instance Attribute reader for name
10
+ # Sets :name only when it is first called
11
+ # Ideal for when name is loaded after startup from a database or config file
12
+ sync_attr_reader :name do
13
+ "Joe Bloggs"
14
+ end
15
+
16
+ # Thread safe Instance Attribute reader and writer for age
17
+ # Sets :age only when it is first called
18
+ sync_attr_accessor :age do
19
+ 21
20
+ end
21
+ end
22
+
23
+ person = Person.new
24
+ puts "The person is #{person.name} with age #{person.age}"
25
+
26
+ person.age = 22
27
+ puts "The person is #{person.name} now has age #{person.age}"
28
+
29
+ person.age = Proc.new {|age| age += 1 }
30
+ puts "The person is #{person.name} now has age #{person.age}"
31
+
32
+ # Changes to person above do not affect any changes to second_person
33
+ # Also, the initial value that is lazy loaded into name is unaffected by person above
34
+ second_person = Person.new
35
+ puts "The second person is #{second_person.name} with age #{second_person.age}"
data/lib/sync_attr.rb CHANGED
@@ -22,12 +22,14 @@
22
22
  require 'sync'
23
23
  require 'sync_attr/version'
24
24
  require 'sync_attr/class_attributes'
25
- #require 'sync_attr/instance_attributes'
25
+ require 'sync_attr/instance_attributes'
26
26
 
27
27
  module SyncAttr
28
28
  # Add class methods and initialize mixin
29
29
  def self.included(base)
30
30
  base.extend(SyncAttr::ClassAttributes::ClassMethods)
31
- base.send(:sync_attr_class_attr_init)
31
+ base.extend(SyncAttr::InstanceAttributes::ClassMethods)
32
+ base.send(:sync_cattr_init)
33
+ base.send(:sync_attr_init)
32
34
  end
33
35
  end
@@ -25,19 +25,26 @@ module SyncAttr
25
25
  # end
26
26
  def sync_cattr_reader(*attributes, &block)
27
27
  attributes.each do |attribute|
28
- self.class.send(:define_method, attribute.to_sym) do
29
- var_name = "@@#{attribute}".to_sym
30
- if class_variable_defined?(var_name)
31
- sync_attr_sync.synchronize(:SH) { class_variable_get(var_name) }
32
- else
33
- return nil unless block
34
- sync_attr_sync.synchronize(:EX) do
35
- # Now that we have exclusive access make sure that another thread has
36
- # not just initialized this attribute
37
- if class_variable_defined?(var_name)
38
- class_variable_get(var_name)
28
+ metaclass.instance_eval do
29
+ define_method(attribute.to_sym) do
30
+ var_name = "@@#{attribute}".to_sym
31
+ if class_variable_defined?(var_name)
32
+ # If there is no writer then it is not necessary to protect reads
33
+ if self.respond_to?("#{attribute}=".to_sym, true)
34
+ sync_cattr_sync.synchronize(:SH) { class_variable_get(var_name) }
39
35
  else
40
- class_variable_set(var_name, class_eval(&block))
36
+ class_variable_get(var_name)
37
+ end
38
+ else
39
+ return nil unless block
40
+ sync_cattr_sync.synchronize(:EX) do
41
+ # Now that we have exclusive access make sure that another thread has
42
+ # not just initialized this attribute
43
+ if class_variable_defined?(var_name)
44
+ class_variable_get(var_name)
45
+ else
46
+ class_variable_set(var_name, class_eval(&block))
47
+ end
41
48
  end
42
49
  end
43
50
  end
@@ -52,7 +59,7 @@ module SyncAttr
52
59
  attributes.each do |attribute|
53
60
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
54
61
  def self.#{attribute}=(value)
55
- sync_attr_sync.synchronize(:EX) do
62
+ sync_cattr_sync.synchronize(:EX) do
56
63
  if value.is_a?(Proc)
57
64
  current_value = @@#{attribute} if defined?(@@#{attribute})
58
65
  @@#{attribute} = value.call(current_value)
@@ -67,21 +74,32 @@ module SyncAttr
67
74
 
68
75
  # Generate a class reader and writer for the attribute
69
76
  def sync_cattr_accessor(*attributes, &block)
70
- sync_cattr_reader(*attributes, &block)
71
77
  sync_cattr_writer(*attributes)
78
+ sync_cattr_reader(*attributes, &block)
72
79
  end
73
80
 
74
- private
81
+ # Returns the metaclass or eigenclass so that we
82
+ # can dynamically generate class methods
83
+ # With thanks, see: https://gist.github.com/1199817
84
+ def metaclass
85
+ class << self;
86
+ self
87
+ end
88
+ end
75
89
 
76
- # Give each class that this module is mixed into it's own Sync
77
- def sync_attr_class_attr_init
78
- @sync_attr_sync = ::Sync.new
90
+ # Returns the sync used by the included class to synchronize access to the
91
+ # class attributes
92
+ def sync_cattr_sync
93
+ @sync_cattr_sync
79
94
  end
80
95
 
81
- def sync_attr_sync
82
- @sync_attr_sync
96
+ protected
97
+
98
+ # Give each class that this module is mixed into it's own Sync
99
+ def sync_cattr_init
100
+ @sync_cattr_sync = ::Sync.new
83
101
  end
84
102
 
85
103
  end
86
104
  end
87
- end
105
+ end
@@ -0,0 +1,101 @@
1
+ puts "Loaded sync_attr_reader"
2
+ # Synchronize access and lazy initialize one or more attributes
3
+ #
4
+ # Author: Reid Morrison <reidmo@gmail.com>
5
+ module SyncAttr
6
+ module InstanceAttributes
7
+ module ClassMethods
8
+ # Lazy load the specific attribute by calling the supplied block when
9
+ # the attribute is first read and then return the same value for all subsequent
10
+ # calls to the variable
11
+ #
12
+ # An optional block can be supplied to initialize the attribute
13
+ # when first read. Acts as a thread safe lazy initializer. The block will only
14
+ # be called once even if several threads call the reader at the same time
15
+ #
16
+ # Example:
17
+ # class MyClass
18
+ # include SyncAttr
19
+ #
20
+ # # Generates a reader for the class attribute 'hello'
21
+ # # and Lazy initializes the value to 'hello world' only on the first
22
+ # # call to the reader
23
+ # sync_attr_reader :hello do
24
+ # 'hello world'
25
+ # end
26
+ # end
27
+ def sync_attr_reader(*attributes, &block)
28
+ attributes.each do |attribute|
29
+ self.send(:define_method, attribute.to_sym) do
30
+ var_name = "@#{attribute}".to_sym
31
+ if instance_variable_defined?(var_name)
32
+ self.sync_attr_sync.synchronize(:SH) { instance_variable_get(var_name) }
33
+ # If there is no writer then it is not necessary to protect reads
34
+ if self.respond_to?("#{attribute}=".to_sym, true)
35
+ self.sync_attr_sync.synchronize(:SH) { instance_variable_get(var_name) }
36
+ else
37
+ instance_variable_get(var_name)
38
+ end
39
+ else
40
+ return nil unless block
41
+ self.sync_attr_sync.synchronize(:EX) do
42
+ # Now that we have exclusive access make sure that another thread has
43
+ # not just initialized this attribute
44
+ if instance_variable_defined?(var_name)
45
+ instance_variable_get(var_name)
46
+ else
47
+ instance_variable_set(var_name, instance_eval(&block))
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # Generates a writer to set a synchronized attribute
56
+ # Supply a Proc ensure an attribute is not being updated by another thread:
57
+ # my_object.count = Proc.new {|count| (count||0) + 1}
58
+ def sync_attr_writer(*attributes)
59
+ attributes.each do |attribute|
60
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
61
+ def #{attribute}=(value)
62
+ self.sync_attr_sync.synchronize(:EX) do
63
+ if value.is_a?(Proc)
64
+ current_value = @#{attribute} if defined?(@#{attribute})
65
+ @#{attribute} = value.call(current_value)
66
+ else
67
+ @#{attribute} = value
68
+ end
69
+ end
70
+ end
71
+ EOS
72
+ end
73
+ end
74
+
75
+ # Generate a reader and writer for the attribute
76
+ def sync_attr_accessor(*attributes, &block)
77
+ sync_attr_reader(*attributes, &block)
78
+ sync_attr_writer(*attributes)
79
+ end
80
+
81
+ # Give every object instance that this module is mixed into it's own Sync
82
+ # I.e. At an object level, not class level
83
+ def sync_attr_init
84
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
85
+ def sync_attr_sync
86
+ return @sync_attr_sync if @sync_attr_sync
87
+ # Use class sync_cattr_sync to ensure multiple @sync_attr_sync instances
88
+ # are not created when two or more threads call this method for the
89
+ # first time at the same time
90
+ self.class.sync_cattr_sync.synchronize(:EX) do
91
+ # In case another thread already created the sync
92
+ return @sync_attr_sync if @sync_attr_sync
93
+ @sync_attr_sync = ::Sync::new
94
+ end
95
+ end
96
+ EOS
97
+ end
98
+
99
+ end
100
+ end
101
+ end
@@ -1,3 +1,3 @@
1
1
  module SyncAttr #:nodoc
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -6,10 +6,10 @@ require 'test/unit'
6
6
  require 'shoulda'
7
7
  require 'sync_attr'
8
8
 
9
- class SynchAttrExample
9
+ class SyncCAttrExample
10
10
  include SyncAttr
11
11
 
12
- sync_cattr_reader :test do
12
+ sync_cattr_reader :test1 do
13
13
  'hello world'
14
14
  end
15
15
  sync_cattr_reader :test2
@@ -21,11 +21,11 @@ class SynchAttrExample
21
21
  sync_cattr_accessor :test5
22
22
  end
23
23
 
24
- class SynchAttrExample2
24
+ class SyncCAttrExample2
25
25
  include SyncAttr
26
26
 
27
- sync_cattr_reader :test do
28
- 'hello world'
27
+ sync_cattr_reader :test1 do
28
+ 'another world'
29
29
  end
30
30
  end
31
31
 
@@ -33,43 +33,55 @@ class ClassAttributesTest < Test::Unit::TestCase
33
33
  context "with example" do
34
34
 
35
35
  should 'lazy initialize class attribute' do
36
- assert_equal 'hello world', SynchAttrExample.test
36
+ assert_equal 'hello world', SyncCAttrExample.test1
37
37
  end
38
38
 
39
39
  should 'return nil on class attribute without initializer' do
40
- assert_nil SynchAttrExample.test2
40
+ assert_nil SyncCAttrExample.test2
41
41
  end
42
42
 
43
43
  should 'set and then return a value for a class attribute without an initializer' do
44
- assert_nil SynchAttrExample.test3
45
- assert_equal 'test3', (SynchAttrExample.test3 = 'test3')
46
- assert_equal 'test3', SynchAttrExample.test3
44
+ assert_nil SyncCAttrExample.test3
45
+ assert_equal 'test3', (SyncCAttrExample.test3 = 'test3')
46
+ assert_equal 'test3', SyncCAttrExample.test3
47
47
  end
48
48
 
49
49
  should 'lazy initialize class attribute and also have writer' do
50
- assert_equal 'hello world 4', SynchAttrExample.test4
51
- assert_equal 'test4', (SynchAttrExample.test4 = 'test4')
52
- assert_equal 'test4', SynchAttrExample.test4
50
+ assert_equal 'hello world 4', SyncCAttrExample.test4
51
+ assert_equal 'test4', (SyncCAttrExample.test4 = 'test4')
52
+ assert_equal 'test4', SyncCAttrExample.test4
53
53
  end
54
54
 
55
55
  should 'support setting a Proc within a synch block' do
56
- assert_nil SynchAttrExample.test5
56
+ assert_nil SyncCAttrExample.test5
57
57
 
58
58
  # Returns the Proc
59
- SynchAttrExample.test5 = Proc.new {|val| (val||0) + 1}
60
- assert_equal 1, SynchAttrExample.test5
59
+ SyncCAttrExample.test5 = Proc.new {|val| (val||0) + 1}
60
+ assert_equal 1, SyncCAttrExample.test5
61
61
 
62
- SynchAttrExample.test5 = Proc.new {|val| (val||0) + 1}
63
- assert_equal 2, SynchAttrExample.test5
62
+ SyncCAttrExample.test5 = Proc.new {|val| (val||0) + 1}
63
+ assert_equal 2, SyncCAttrExample.test5
64
64
  end
65
65
  end
66
66
 
67
67
  context "with example2" do
68
68
 
69
69
  should 'ensure that different classes have their own synch instances' do
70
- assert ex1 = SynchAttrExample.new
71
- assert ex2 = SynchAttrExample2.new
72
- assert ex1.class.send(:sync_attr_sync).object_id != ex2.class.send(:sync_attr_sync).object_id
70
+ assert ex1 = SyncCAttrExample.new
71
+ assert ex2 = SyncCAttrExample2.new
72
+ assert ex1.class.send(:sync_cattr_sync).object_id != ex2.class.send(:sync_cattr_sync).object_id
73
+ end
74
+
75
+ should 'ensure that different classes have their own class attributes' do
76
+ assert ex1 = SyncCAttrExample.new
77
+ assert_equal 'hello world', ex1.class.test1
78
+ assert ex2 = SyncCAttrExample2.new
79
+ assert_equal 'another world', ex2.class.test1
80
+ assert_equal 'hello world', ex1.class.test1
81
+
82
+ assert !defined? ex2.class.test2
83
+ assert !defined? ex2.class.test3
84
+ assert !defined? ex2.class.test4
73
85
  end
74
86
  end
75
87
  end
@@ -0,0 +1,88 @@
1
+ # Allow examples to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+ require 'shoulda'
7
+ require 'sync_attr'
8
+
9
+ class SyncAttrExample
10
+ include SyncAttr
11
+
12
+ sync_attr_reader :test1 do
13
+ 'hello world'
14
+ end
15
+ sync_attr_reader :test2
16
+ sync_attr_accessor :test3
17
+ sync_attr_accessor :test4 do
18
+ 'hello world 4'
19
+ end
20
+
21
+ sync_attr_accessor :test5
22
+ end
23
+
24
+ # Ensure that class and instance attributes are distinct
25
+ class SyncAttrExample2
26
+ include SyncAttr
27
+
28
+ sync_attr_reader :test1 do
29
+ 'hello world instance'
30
+ end
31
+ sync_cattr_reader :test1 do
32
+ 'hello world class'
33
+ end
34
+ end
35
+
36
+ class InstanceAttributesTest < Test::Unit::TestCase
37
+ context "with example" do
38
+
39
+ should 'lazy initialize attribute' do
40
+ assert_equal 'hello world', SyncAttrExample.new.test1
41
+ end
42
+
43
+ should 'return nil on attribute without initializer' do
44
+ assert_nil SyncAttrExample.new.test2
45
+ end
46
+
47
+ should 'set and then return a value for a class attribute without an initializer' do
48
+ assert example = SyncAttrExample.new
49
+ assert_nil example.test3
50
+ assert_equal 'test3', (example.test3 = 'test3')
51
+ assert_equal 'test3', example.test3
52
+ end
53
+
54
+ should 'lazy initialize attribute and also have writer' do
55
+ assert example = SyncAttrExample.new
56
+ assert_equal 'hello world 4', example.test4
57
+ assert_equal 'test4', (example.test4 = 'test4')
58
+ assert_equal 'test4', example.test4
59
+ end
60
+
61
+ should 'support setting a Proc within a synch block' do
62
+ assert example = SyncAttrExample.new
63
+ assert_nil example.test5
64
+
65
+ # Returns the Proc
66
+ example.test5 = Proc.new {|val| (val||0) + 1}
67
+ assert_equal 1, example.test5
68
+
69
+ example.test5 = Proc.new {|val| (val||0) + 1}
70
+ assert_equal 2, example.test5
71
+ end
72
+ end
73
+
74
+ context "with example2" do
75
+
76
+ should 'have distinct class and instance attributes when they have the same name' do
77
+ assert s = SyncAttrExample2.new
78
+ assert_equal 'hello world instance', s.test1
79
+ assert_equal 'hello world class', s.class.test1
80
+ end
81
+
82
+ should 'ensure that different classes have their own synch instances' do
83
+ assert ex1 = SyncAttrExample.new
84
+ assert ex2 = SyncAttrExample2.new
85
+ assert ex1.class.send(:sync_cattr_sync).object_id != ex2.class.send(:sync_cattr_sync).object_id
86
+ end
87
+ end
88
+ end
metadata CHANGED
@@ -1,22 +1,26 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sync_attr
3
3
  version: !ruby/object:Gem::Version
4
- prerelease:
5
- version: 0.0.1
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
6
10
  platform: ruby
7
11
  authors:
8
- - Reid Morrison
12
+ - Reid Morrison
9
13
  autorequire:
10
14
  bindir: bin
11
15
  cert_chain: []
12
16
 
13
- date: 2012-02-20 00:00:00 -05:00
17
+ date: 2012-03-05 00:00:00 -05:00
14
18
  default_executable:
15
19
  dependencies: []
16
20
 
17
21
  description: SyncAttr is a mixin to read, write and lazy initialize both class and instance variables in a multi-threaded environment when the attribute could be modified by two threads at the same time, written in Ruby.
18
22
  email:
19
- - reidmo@gmail.com
23
+ - reidmo@gmail.com
20
24
  executables: []
21
25
 
22
26
  extensions: []
@@ -24,20 +28,17 @@ extensions: []
24
28
  extra_rdoc_files: []
25
29
 
26
30
  files:
27
- - LICENSE.txt
28
- - Rakefile
29
- - README.md
30
- - examples/class_attribute.rb
31
- - lib/sync_attr.rb
32
- - lib/sync_attr/class_attributes.rb
33
- - lib/sync_attr/version.rb
34
- - nbproject/project.properties
35
- - nbproject/project.xml
36
- - nbproject/private/config.properties
37
- - nbproject/private/private.properties
38
- - nbproject/private/private.xml
39
- - nbproject/private/rake-d.txt
40
- - test/class_attributes_test.rb
31
+ - examples/class_attribute.rb
32
+ - examples/instance_attribute.rb
33
+ - lib/sync_attr/class_attributes.rb
34
+ - lib/sync_attr/instance_attributes.rb
35
+ - lib/sync_attr/version.rb
36
+ - lib/sync_attr.rb
37
+ - LICENSE.txt
38
+ - Rakefile
39
+ - README.md
40
+ - test/class_attributes_test.rb
41
+ - test/instance_attributes_test.rb
41
42
  has_rdoc: true
42
43
  homepage: https://github.com/ClarityServices/sync_attr
43
44
  licenses: []
@@ -46,23 +47,25 @@ post_install_message:
46
47
  rdoc_options: []
47
48
 
48
49
  require_paths:
49
- - lib
50
+ - lib
50
51
  required_ruby_version: !ruby/object:Gem::Requirement
51
- none: false
52
52
  requirements:
53
- - - ">="
54
- - !ruby/object:Gem::Version
55
- version: "0"
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ version: "0"
56
58
  required_rubygems_version: !ruby/object:Gem::Requirement
57
- none: false
58
59
  requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: "0"
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
64
+ version: "0"
62
65
  requirements: []
63
66
 
64
67
  rubyforge_project:
65
- rubygems_version: 1.5.1
68
+ rubygems_version: 1.3.6
66
69
  signing_key:
67
70
  specification_version: 3
68
71
  summary: Thread safe accessors for Ruby class and instance attributes. Supports thread safe lazy loading of attributes
File without changes
@@ -1,4 +0,0 @@
1
- file.reference.synch_attr-examples=/Users/rmorrison/Sandbox/synch_attr/examples
2
- file.reference.synch_attr-lib=/Users/rmorrison/Sandbox/synch_attr/lib
3
- file.reference.synch_attr-test=/Users/rmorrison/Sandbox/synch_attr/test
4
- platform.active=JRuby_0
@@ -1,4 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project-private xmlns="http://www.netbeans.org/ns/project-private/1">
3
- <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
4
- </project-private>
File without changes
@@ -1,10 +0,0 @@
1
- file.reference.synch_attr-examples=examples
2
- file.reference.synch_attr-lib=lib
3
- file.reference.synch_attr-test=test
4
- javac.classpath=
5
- main.file=
6
- platform.active=JRuby_0
7
- source.encoding=UTF-8
8
- src.examples.dir=examples
9
- src.lib.dir=lib
10
- test.test.dir=test
@@ -1,16 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project xmlns="http://www.netbeans.org/ns/project/1">
3
- <type>org.netbeans.modules.ruby.rubyproject</type>
4
- <configuration>
5
- <data xmlns="http://www.netbeans.org/ns/ruby-project/1">
6
- <name>sync_attr</name>
7
- <source-roots>
8
- <root id="src.lib.dir" name="Source Files"/>
9
- <root id="src.examples.dir" name="Examples"/>
10
- </source-roots>
11
- <test-roots>
12
- <root id="test.test.dir" name="Test Files"/>
13
- </test-roots>
14
- </data>
15
- </configuration>
16
- </project>