sync_attr 1.0.0 → 2.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 44a69c13e6128090ae353172baf6047190bdb0ad
4
- data.tar.gz: 29dda506593efe254aabfd28327f853c8fb54f5f
3
+ metadata.gz: 6081ee6568e87da15f5739a7e0f9098e8697a4db
4
+ data.tar.gz: 45bc09a1ff0ce624b42fda4fd27d4c114a57492a
5
5
  SHA512:
6
- metadata.gz: 14bce4c1073400855394d048455b1168eb94fd53cb6e67c9069eece654f0b784cfd174a966f8943922646cf8fd865b7da2d786074e9cff045c467307fe4d6b72
7
- data.tar.gz: 7dc91d0083331bfdbf27999ffd3584cecd4f02b4b6f2b21ae7ef1e57a28f0188711ae4c985e8a0a5958babc1c75fc63993cef4e8841a389a2f12090958a9484b
6
+ metadata.gz: 4089d07c4d675212967e58cfe825b3f591b5535715ac6cc977f9aee24300c6d3a45503fbeb182681ee4f02f2f2b1e529d37d17206abe1033068dd3119703a668
7
+ data.tar.gz: 8aa44e84c312076143b0997f3140b8df9c9f4798e28e56c37ab3cbc2f671ac54b5f3d3aba5a605484b4230c92638cded73813aca06723f1153f487194780086b
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2012 Clarity Services, Inc.
189
+ Copyright 2012, 2013, 2014 Reid Morrison
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
data/README.md CHANGED
@@ -1,108 +1,164 @@
1
- sync_attr
1
+ sync_attr [![Build Status](https://secure.travis-ci.org/reidmorrison/sync_attr.png?branch=master)](http://travis-ci.org/reidmorrison/sync_attr)
2
2
  =========
3
3
 
4
- Thread-safe Ruby class variables with lazy loaded default values and initializers
4
+ Thread-safe Ruby class and instance attributes
5
5
 
6
- * http://github.com/ClarityServices/sync_attr
6
+ * http://github.com/reidmorrison/sync_attr
7
7
 
8
- ### Introduction
8
+ ## Status
9
9
 
10
- When working in a multithreaded environment it is important to ensure that
10
+ Enterprise Production Ready - In daily use in large multi-threaded application
11
+
12
+ ## Introduction
13
+
14
+ When working in a multi-threaded environment it is important to ensure that
11
15
  any attributes that are shared across threads are properly protected to ensure
12
- that inconsistent data is not created.
16
+ that inconsistent data is not created. Lazy initializing these safe attributes
17
+ improves startup times and only creates resources when they are needed.
13
18
 
14
19
  For example, without sync_attr if two threads attempt to write to the
15
20
  same attribute at the same time it is not deterministic what the results will be.
16
- This condition is made worse when two threads attempt to initialize class variables
17
- at the same time that could take a second or longer to complete.
21
+ This condition is made worse when two threads attempt to initialize class instance
22
+ attributes at the same time that could take a second or longer to complete.
23
+
24
+ A `sync_cattr_reader` is ideal for holding data loaded from configuration files.
25
+ Also, shared objects, such as connection pools can be safely initialized and
26
+ shared in this way across hundreds of threads.
27
+ Once initialized all reads are shared without locks since no writer is defined.
28
+
29
+ Aside from safely sharing reads `sync_cattr_accessor` also ensures that data
30
+ is not read while it is being modified. The writes are completely thread-safe
31
+ ensuring that only one thread is modifying the value at a time and that reads
32
+ are suspended until the write is complete.
18
33
 
19
- ### Features
34
+ ## Features
20
35
 
21
- * Adds thread-safe accessors for class attributes
22
- * Allows shared read access to class and instance attributes. This allows
23
- multiple threads to read the attribute, but will block all reads and writes whilst
24
- the attribute is being modified.
36
+ * Adds thread-safe accessors for class instance attributes.
37
+ * Only one thread can read or write the value at any one time.
25
38
  * Prevents attributes from being read while it is being updated or initialized for
26
39
  the first time.
27
- * Thread-safe attribute lazy initialization
40
+ * Thread-safe attribute lazy initialization.
28
41
  Lazy initialization allows class attributes to be loaded only when first read.
29
- As a result it's value can be read for the first time from a database or config file
30
- once and only when needed.
31
- * Avoids having to create yet another Rails initializer
32
- * Avoids costly startup initialization when the initialized data may never be accessed
33
- For example when Rake tasks are run, they may not need access to everything in
34
- the Rails environment
35
- * Not dependent on Rails
42
+ As a result it's value can be read for the first time from a database or
43
+ configuration file once and only when needed.
44
+ * Avoids having to create yet another Rails initializer, when the data can be
45
+ fetched or held by a class instance attribute.
46
+ * Avoids costly startup initialization when the initialized data may never be accessed.
47
+ For example, when Rake tasks are run, they may not need access to everything in
48
+ the Rails environment.
49
+ * Works with Rails and regular Ruby.
50
+
51
+ ## Thread-safe Class Attribute example
52
+
53
+ Create a reader for a class attribute and lazy initialize its value on the first
54
+ attempt to read it. I.e. Lazy initialize the value.
55
+
56
+ Very useful for initializing shared services that take time to initialize.
57
+ In particular services that may not even be called in a specific process,
58
+ for example when running rake, or when opening a console.
59
+
60
+ An optional block can be supplied to initialize the synchronized class attribute
61
+ when it is first read. The initializer is thread safe and will block all other
62
+ reads to this attribute while it is being initialized. This ensures that the
63
+ initializer is only run once and that all threads to call the reader receive the
64
+ same value, regardless of how many threads call the reader at the same time.
65
+
66
+ Example:
67
+
68
+ ```ruby
69
+ require 'sync_attr'
70
+
71
+ # Sample class with lazy initialized Thread-safe Class Attributes
72
+ class Person
73
+ # Create a reader for the class attribute :name
74
+ # and lazy initialize the value to 'Joe Bloggs' only on the first
75
+ # call to the reader.
76
+ # Ideal for when :name is loaded from a database or configuration file.
77
+ sync_cattr_reader :name do
78
+ 'Joe Bloggs'
79
+ end
80
+
81
+ # Create a reader and a writer for the class attribute :age
82
+ # and lazy initialize the value to 21 only on the first
83
+ # call to the reader.
84
+ sync_cattr_accessor :age do
85
+ 21
86
+ end
87
+ end
88
+
89
+ puts "The person is #{Person.name} with age #{Person.age}"
90
+
91
+ Person.age = 22
92
+ puts "The person is #{Person.name} now has age #{Person.age}"
93
+
94
+ # => The person is Joe Bloggs now has age 22
36
95
 
37
- ### Synchronized Class Attribute example
96
+ Person.age = Proc.new {|age| age += 1 }
97
+ puts "The person is #{Person.name} now has age #{Person.age}"
38
98
 
39
- require 'sync_attr'
99
+ # => The person is Joe Bloggs now has age 23
100
+ ```
40
101
 
41
- # Sample class with lazy initialized Synchronized Class Attributes
42
- class Person
43
- include SyncAttr
102
+ ## Thread-safe Instance Attribute example
44
103
 
45
- # Thread safe Class Attribute reader for name
46
- # with a default value
47
- # Ideal for when name is loaded after startup from a database or config file
48
- sync_cattr_reader :name do
49
- "Joe Bloggs"
50
- end
104
+ Create a reader for an attribute and lazy initialize its value on the first
105
+ attempt to read it. I.e. Lazy initialize the value.
51
106
 
52
- # Thread safe Class Attribute reader and writer for age
53
- # with a default value
54
- sync_cattr_accessor :age do
55
- 21
56
- end
57
- end
107
+ An optional block can be supplied to initialize the synchronized attribute
108
+ when it is first read. The initializer is thread safe and will block all other
109
+ reads to this attribute while it is being initialized. This ensures that the
110
+ initializer is only run once per object and that all threads to call the reader
111
+ receive the same value, regardless of how many threads call the reader at the same time.
58
112
 
59
- puts "The person is #{Person.name} with age #{Person.age}"
113
+ Example:
60
114
 
61
- Person.age = 22
62
- puts "The person is #{Person.name} now has age #{Person.age}"
115
+ ```ruby
116
+ require 'sync_attr'
63
117
 
64
- Person.age = Proc.new {|age| age += 1 }
65
- puts "The person is #{Person.name} now has age #{Person.age}"
118
+ # Sample class with lazy initialized Thread-safe attributes
119
+ class Person
120
+ include SyncAttr::Attributes
66
121
 
67
- ### Synchronized Instance Attribute example
122
+ # Create a reader for the thread-safe attribute :name
123
+ # and lazy initialize the value to 'Joe Bloggs' only on the first
124
+ # call to the reader.
125
+ sync_attr_reader :name do
126
+ 'Joe Bloggs'
127
+ end
68
128
 
69
- require 'sync_attr'
129
+ # Create a thread-safe reader and a writer for the attribute :age
130
+ # and lazy initialize the value to 21 only on the first
131
+ # call to the reader.
132
+ sync_attr_accessor :age do
133
+ 21
134
+ end
135
+ end
70
136
 
71
- # Sample class with lazy initialized Synchronized Class Attributes
72
- class Person
73
- include SyncAttr
137
+ person = Person.new
138
+ puts "The person is #{person.name} with age #{person.age}"
74
139
 
75
- # Thread safe Attribute reader for name
76
- # with a default value
77
- sync_attr_reader :name do
78
- "Joe Bloggs"
79
- end
140
+ # => The person is Joe Bloggs with age 21
80
141
 
81
- # Thread safe Attribute reader and writer for age
82
- # with a default value
83
- sync_attr_accessor :age do
84
- 21
85
- end
86
- end
142
+ person.age = 22
143
+ puts "The person is #{person.name} now has age #{person.age}"
87
144
 
88
- person = Person.new
89
- puts "The person is #{person.name} with age #{person.age}"
145
+ # => The person is Joe Bloggs now has age 22
90
146
 
91
- person.age = 22
92
- puts "The person is #{person.name} now has age #{person.age}"
147
+ person.age = Proc.new {|age| age += 1 }
148
+ puts "The person is #{person.name} now has age #{person.age}"
93
149
 
94
- person.age = Proc.new {|age| age += 1 }
95
- puts "The person is #{person.name} now has age #{person.age}"
150
+ # => The person is Joe Bloggs now has age 23
151
+ ```
96
152
 
97
- ### Install
153
+ ## Install
98
154
 
99
155
  gem install sync_attr
100
156
 
101
157
  Meta
102
158
  ----
103
159
 
104
- * Code: `git clone git://github.com/ClarityServices/sync_attr.git`
105
- * Home: <https://github.com/ClarityServices/sync_attr>
160
+ * Code: `git clone git://github.com/reidmorrison/sync_attr.git`
161
+ * Home: <https://github.com/reidmorrison/sync_attr>
106
162
  * Bugs: <http://github.com/reidmorrison/sync_attr/issues>
107
163
  * Gems: <http://rubygems.org/gems/sync_attr>
108
164
 
@@ -116,7 +172,7 @@ Reid Morrison :: reidmo@gmail.com :: @reidmorrison
116
172
  License
117
173
  -------
118
174
 
119
- Copyright 2012 Clarity Services, Inc.
175
+ Copyright 2012, 2013, 2014, 2015 Reid Morrison
120
176
 
121
177
  Licensed under the Apache License, Version 2.0 (the "License");
122
178
  you may not use this file except in compliance with the License.
data/Rakefile CHANGED
@@ -1,38 +1,28 @@
1
- lib = File.expand_path('../lib/', __FILE__)
2
- $:.unshift lib unless $:.include?(lib)
3
-
4
- require 'rubygems'
5
- require 'rubygems/package'
6
1
  require 'rake/clean'
7
2
  require 'rake/testtask'
8
- require 'date'
3
+
4
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
9
5
  require 'sync_attr/version'
10
6
 
11
- desc "Build gem"
12
- task :gem do |t|
13
- gemspec = Gem::Specification.new do |s|
14
- s.name = 'sync_attr'
15
- s.version = SyncAttr::VERSION
16
- s.platform = Gem::Platform::RUBY
17
- s.authors = ['Reid Morrison']
18
- s.email = ['reidmo@gmail.com']
19
- s.homepage = 'https://github.com/ClarityServices/sync_attr'
20
- s.date = Date.today.to_s
21
- s.summary = "Thread safe accessors for Ruby class and instance attributes. Supports thread safe lazy loading of attributes"
22
- 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."
23
- s.files = FileList["./**/*"].exclude(/\.gem$/, /\.log$/,/nbproject/).map{|f| f.sub(/^\.\//, '')}
24
- s.license = "Apache License V2.0"
25
- s.has_rdoc = true
26
- end
27
- Gem::Package.build gemspec
7
+ task :gem do
8
+ system "gem build sync_attr.gemspec"
9
+ end
10
+
11
+ task :publish => :gem do
12
+ system "git tag -a v#{SyncAttr::VERSION} -m 'Tagging #{SyncAttr::VERSION}'"
13
+ system "git push --tags"
14
+ system "gem push sync_attr-#{SyncAttr::VERSION}.gem"
15
+ system "rm sync_attr-#{SyncAttr::VERSION}.gem"
28
16
  end
29
17
 
30
18
  desc "Run Test Suite"
31
19
  task :test do
32
- Rake::TestTask.new(:unit) do |t|
20
+ Rake::TestTask.new(:functional) do |t|
33
21
  t.test_files = FileList['test/*_test.rb']
34
22
  t.verbose = true
35
23
  end
36
24
 
37
- Rake::Task['unit'].invoke
25
+ Rake::Task['functional'].invoke
38
26
  end
27
+
28
+ task :default => :test
@@ -1,35 +1,9 @@
1
- # Synchronize access and lazy initialize one or more variables in a class
2
- #
3
- # Load configuration files and thread connection pools on demand rather than
4
- # at start time or via a Rails initializer
5
- #
6
- # Locking: Shared reads and exclusive writes
7
- # sync_attr ensures that all reads are shared, meaning that all
8
- # reads to attributes can occur at the same time. All writes are exclusive, so
9
- # all reads and other writes will be blocked whilst a write takes place.
10
- #
11
- # Example:
12
- # class MyClass
13
- # include SyncAttr
14
- #
15
- # # Create class variable @@http and initialize on the first access
16
- # # protecting access by concurrent threads using a Semaphore
17
- # sync_cattr_reader :http do
18
- # PersistentHTTP.new()
19
- # end
20
- #
21
- # Author: Reid Morrison <reidmo@gmail.com>
22
- require 'sync'
1
+ require 'thread'
23
2
  require 'sync_attr/version'
24
3
  require 'sync_attr/class_attributes'
25
- require 'sync_attr/instance_attributes'
4
+ require 'sync_attr/attributes'
26
5
 
6
+ # include SyncAttr is deprecated. Not required for class instance attributes.
7
+ # include SyncAttr::Attributes for object instance attributes.
27
8
  module SyncAttr
28
- # Add class methods and initialize mixin
29
- def self.included(base)
30
- base.extend(SyncAttr::ClassAttributes::ClassMethods)
31
- base.extend(SyncAttr::InstanceAttributes::ClassMethods)
32
- base.send(:sync_cattr_init)
33
- base.send(:sync_attr_init)
34
- end
35
9
  end
@@ -1,12 +1,14 @@
1
- # Synchronize access and lazy initialize one or more attributes
2
- #
3
- # Author: Reid Morrison <reidmo@gmail.com>
4
1
  module SyncAttr
5
- module InstanceAttributes
2
+ module Attributes
6
3
  module ClassMethods
7
- # Lazy load the specific attribute by calling the supplied block when
8
- # the attribute is first read and then return the same value for all subsequent
9
- # calls to the variable
4
+ # Thread-safe access to attributes in an object.
5
+ #
6
+ # Attributes protected with `sync_attr_reader`, `sync_attr_writer`, and/or
7
+ # `sync_attr_accessor` can be safely read and written across many threads.
8
+ #
9
+ # Additionally `sync_attr_reader` supports lazy loading the corresponding
10
+ # attribute in a thread-safe way. While the value is being calculated / loaded
11
+ # all other threads calling that attribute will block until the value is available
10
12
  #
11
13
  # An optional block can be supplied to initialize the attribute
12
14
  # when first read. Acts as a thread safe lazy initializer. The block will only
@@ -14,9 +16,9 @@ module SyncAttr
14
16
  #
15
17
  # Example:
16
18
  # class MyClass
17
- # include SyncAttr
19
+ # include SyncAttr::Attributes
18
20
  #
19
- # # Generates a reader for the class attribute 'hello'
21
+ # # Generates a reader for the attribute 'hello'
20
22
  # # and Lazy initializes the value to 'hello world' only on the first
21
23
  # # call to the reader
22
24
  # sync_attr_reader :hello do
@@ -25,19 +27,19 @@ module SyncAttr
25
27
  # end
26
28
  def sync_attr_reader(*attributes, &block)
27
29
  attributes.each do |attribute|
30
+ raise NameError.new("invalid attribute name: #{attribute}") unless attribute =~ /^[_A-Za-z]\w*$/
28
31
  self.send(:define_method, attribute.to_sym) do
29
32
  var_name = "@#{attribute}".to_sym
30
33
  if instance_variable_defined?(var_name)
31
- self.sync_attr_sync.synchronize(:SH) { instance_variable_get(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
- self.sync_attr_sync.synchronize(:SH) { instance_variable_get(var_name) }
35
- else
36
- instance_variable_get(var_name)
37
- end
34
+ # If there is no writer then it is not necessary to protect reads
35
+ if respond_to?("#{attribute}=".to_sym, true)
36
+ sync_attr_sync(attribute) { instance_variable_get(var_name) }
37
+ else
38
+ instance_variable_get(var_name)
39
+ end
38
40
  else
39
41
  return nil unless block
40
- self.sync_attr_sync.synchronize(:EX) do
42
+ sync_attr_sync(attribute) do
41
43
  # Now that we have exclusive access make sure that another thread has
42
44
  # not just initialized this attribute
43
45
  if instance_variable_defined?(var_name)
@@ -56,17 +58,18 @@ module SyncAttr
56
58
  # my_object.count = Proc.new {|count| (count||0) + 1}
57
59
  def sync_attr_writer(*attributes)
58
60
  attributes.each do |attribute|
61
+ raise NameError.new("invalid attribute name: #{attribute}") unless attribute =~ /^[_A-Za-z]\w*$/
59
62
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
60
- def #{attribute}=(value)
61
- self.sync_attr_sync.synchronize(:EX) do
62
- if value.is_a?(Proc)
63
- current_value = @#{attribute} if defined?(@#{attribute})
64
- @#{attribute} = value.call(current_value)
65
- else
66
- @#{attribute} = value
63
+ def #{attribute}=(value)
64
+ sync_attr_sync('#{attribute}') do
65
+ if value.is_a?(Proc)
66
+ current_value = @#{attribute} if defined?(@#{attribute})
67
+ @#{attribute} = value.call(current_value)
68
+ else
69
+ @#{attribute} = value
70
+ end
71
+ end
67
72
  end
68
- end
69
- end
70
73
  EOS
71
74
  end
72
75
  end
@@ -76,25 +79,20 @@ module SyncAttr
76
79
  sync_attr_reader(*attributes, &block)
77
80
  sync_attr_writer(*attributes)
78
81
  end
82
+ end
79
83
 
80
- # Give every object instance that this module is mixed into it's own Sync
81
- # I.e. At an object level, not class level
82
- def sync_attr_init
83
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
84
- def sync_attr_sync
85
- return @sync_attr_sync if @sync_attr_sync
86
- # Use class sync_cattr_sync to ensure multiple @sync_attr_sync instances
87
- # are not created when two or more threads call this method for the
88
- # first time at the same time
89
- self.class.sync_cattr_sync.synchronize(:EX) do
90
- # In case another thread already created the sync
91
- return @sync_attr_sync if @sync_attr_sync
92
- @sync_attr_sync = ::Sync::new
93
- end
94
- end
95
- EOS
96
- end
84
+ def self.included(base)
85
+ base.extend(SyncAttr::Attributes::ClassMethods)
86
+ end
97
87
 
88
+ private
89
+
90
+ # Thread safe way of creating the instance sync
91
+ def sync_attr_sync(attribute, &block)
92
+ mutex_var_name = "@sync_attr_#{attribute}".to_sym
93
+ instance_variable_set(mutex_var_name, Mutex.new) unless instance_variable_defined?(mutex_var_name)
94
+ instance_variable_get(mutex_var_name).synchronize(&block)
98
95
  end
96
+
99
97
  end
100
98
  end
@@ -1,65 +1,67 @@
1
- # Synchronize access and lazy initialize one or more variables in a class
2
- #
3
- # Author: Reid Morrison <reidmo@gmail.com>
4
- module SyncAttr
5
- module ClassAttributes
6
- module ClassMethods
7
- # Lazy load the specific class attribute by calling the supplied block when
8
- # the attribute is first read and then return the same value for all subsequent
9
- # calls to the class variable
10
- #
11
- # An optional block can be supplied to initialize the synchronized class attribute
12
- # when first read. Acts as a thread safe lazy initializer. The block will only
13
- # be called once even if several threads call the reader at the same time
14
- #
15
- # Example:
16
- # class MyClass
17
- # include SyncAttr
18
- #
19
- # # Generates a reader for the class attribute 'hello'
20
- # # and Lazy initializes the value to 'hello world' only on the first
21
- # # call to the reader
22
- # sync_cattr_reader :hello do
23
- # 'hello world'
24
- # end
25
- # end
26
- def sync_cattr_reader(*attributes, &block)
27
- attributes.each do |attribute|
28
- metaclass.instance_eval do
29
- define_method(attribute.to_sym) do
30
- var_name = "@@#{attribute}".to_sym
1
+ class Module
2
+ # Lazy load the specific class attribute by calling the supplied block when
3
+ # the attribute is first read and then return the same value for all subsequent
4
+ # calls to the class variable
5
+ #
6
+ # An optional block can be supplied to initialize the synchronized class attribute
7
+ # when first read. Acts as a thread safe lazy initializer. The block will only
8
+ # be called once even if several threads call the reader at the same time
9
+ #
10
+ # Example:
11
+ # require 'sync_attr'
12
+ # class MyClass
13
+ # # Generates a reader for the class attribute 'hello'
14
+ # # and Lazy initializes the value to 'hello world' only on the first
15
+ # # call to the reader
16
+ # sync_cattr_reader :hello do
17
+ # 'hello world'
18
+ # end
19
+ # end
20
+ def sync_mattr_reader(*attributes, &block)
21
+ attributes.each do |attribute|
22
+ raise NameError.new("invalid attribute name: #{attribute}") unless attribute =~ /^[_A-Za-z]\w*$/
23
+ mutex_var_name = "@@sync_attr_#{attribute}".to_sym
24
+ class_variable_set(mutex_var_name, Mutex.new) unless class_variable_defined?(mutex_var_name)
25
+ # Class reader with lazy initialization for the first thread that calls this method
26
+ # Use metaclass/eigenclass to dynamically generate class methods
27
+ (class << self; self; end).instance_eval do
28
+ define_method(attribute.to_sym) do
29
+ var_name = "@@#{attribute}".to_sym
30
+ if class_variable_defined?(var_name)
31
+ # If there is no writer then it is not necessary to protect reads
32
+ if self.respond_to?("#{attribute}=".to_sym, true)
33
+ class_variable_get(mutex_var_name).synchronize { class_variable_get(var_name) }
34
+ else
35
+ class_variable_get(var_name)
36
+ end
37
+ else
38
+ return nil unless block
39
+ class_variable_get(mutex_var_name).synchronize do
40
+ # Now that we have exclusive access make sure that another thread has
41
+ # not just initialized this attribute
31
42
  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) }
35
- else
36
- class_variable_get(var_name)
37
- end
43
+ class_variable_get(var_name)
38
44
  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
48
- end
45
+ class_variable_set(var_name, class_eval(&block))
49
46
  end
50
47
  end
51
48
  end
52
49
  end
53
50
  end
51
+ end
52
+ end
53
+ alias :sync_cattr_reader :sync_mattr_reader
54
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
- # MyClass.count = Proc.new {|count| (count||0) + 1}
58
- def sync_cattr_writer(*attributes)
59
- attributes.each do |attribute|
60
- class_eval(<<-EOS, __FILE__, __LINE__ + 1)
55
+ # Generates a writer to set a synchronized attribute
56
+ # Supply a Proc ensure an attribute is not being updated by another thread:
57
+ # MyClass.count = Proc.new {|count| (count||0) + 1}
58
+ def sync_mattr_writer(*attributes)
59
+ attributes.each do |attribute|
60
+ mutex_var_name = "@@sync_attr_#{attribute}".to_sym
61
+ class_variable_set(mutex_var_name, Mutex.new) unless class_variable_defined?(mutex_var_name)
62
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
61
63
  def self.#{attribute}=(value)
62
- sync_cattr_sync.synchronize(:EX) do
64
+ #{mutex_var_name}.synchronize do
63
65
  if value.is_a?(Proc)
64
66
  current_value = @@#{attribute} if defined?(@@#{attribute})
65
67
  @@#{attribute} = value.call(current_value)
@@ -68,38 +70,15 @@ module SyncAttr
68
70
  end
69
71
  end
70
72
  end
71
- EOS
72
- end
73
- end
74
-
75
- # Generate a class reader and writer for the attribute
76
- def sync_cattr_accessor(*attributes, &block)
77
- sync_cattr_writer(*attributes)
78
- sync_cattr_reader(*attributes, &block)
79
- end
80
-
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
89
-
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 ||= ::Sync.new
94
- end
95
-
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
101
- end
102
-
73
+ EOS
103
74
  end
104
75
  end
76
+ alias :sync_cattr_writer :sync_mattr_writer
77
+
78
+ # Generate a class reader and writer for the attribute
79
+ def sync_mattr_accessor(*attributes, &block)
80
+ sync_cattr_writer(*attributes)
81
+ sync_cattr_reader(*attributes, &block)
82
+ end
83
+ alias :sync_cattr_accessor :sync_mattr_accessor
105
84
  end
@@ -1,3 +1,3 @@
1
1
  module SyncAttr
2
- VERSION = "1.0.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -1,13 +1,7 @@
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'
1
+ require_relative 'test_helper'
8
2
 
9
3
  class SyncAttrExample
10
- include SyncAttr
4
+ include SyncAttr::Attributes
11
5
 
12
6
  sync_attr_reader :test1 do
13
7
  'hello world'
@@ -23,7 +17,7 @@ end
23
17
 
24
18
  # Ensure that class and instance attributes are distinct
25
19
  class SyncAttrExample2
26
- include SyncAttr
20
+ include SyncAttr::Attributes
27
21
 
28
22
  sync_attr_reader :test1 do
29
23
  'hello world instance'
@@ -33,7 +27,7 @@ class SyncAttrExample2
33
27
  end
34
28
  end
35
29
 
36
- class InstanceAttributesTest < Test::Unit::TestCase
30
+ class InstanceAttributesTest < Minitest::Test
37
31
  context "with example" do
38
32
 
39
33
  should 'lazy initialize attribute' do
@@ -79,10 +73,23 @@ class InstanceAttributesTest < Test::Unit::TestCase
79
73
  assert_equal 'hello world class', s.class.test1
80
74
  end
81
75
 
82
- should 'ensure that different classes have their own synch instances' do
76
+ should 'ensure that different class instances have their own synchs' do
83
77
  assert ex1 = SyncAttrExample.new
84
78
  assert ex2 = SyncAttrExample2.new
85
- assert ex1.class.send(:sync_cattr_sync).object_id != ex2.class.send(:sync_cattr_sync).object_id
79
+ assert_equal 'hello world', ex1.test1
80
+ assert_equal 'hello world instance', ex2.test1
81
+ end
82
+
83
+ should 'ensure that different instances have their own synchs' do
84
+ assert ex1 = SyncAttrExample.new
85
+ assert ex2 = SyncAttrExample.new
86
+ assert_equal 'hello world', ex1.test1
87
+ assert_equal 'hello world', ex2.test1
88
+ end
89
+
90
+ should 'ensure that objects and classes have their own synchs' do
91
+ assert ex1 = SyncAttrExample.new
92
+ assert_equal 'hello world', ex1.test1
86
93
  end
87
94
  end
88
95
  end
@@ -1,14 +1,6 @@
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'
1
+ require_relative 'test_helper'
8
2
 
9
3
  class SyncCAttrExample
10
- include SyncAttr
11
-
12
4
  sync_cattr_reader :test1 do
13
5
  'hello world'
14
6
  end
@@ -22,14 +14,12 @@ class SyncCAttrExample
22
14
  end
23
15
 
24
16
  class SyncCAttrExample2
25
- include SyncAttr
26
-
27
17
  sync_cattr_reader :test1 do
28
18
  'another world'
29
19
  end
30
20
  end
31
21
 
32
- class ClassAttributesTest < Test::Unit::TestCase
22
+ class ClassAttributesTest < Minitest::Test
33
23
  context "with example" do
34
24
 
35
25
  should 'lazy initialize class attribute' do
@@ -66,12 +56,6 @@ class ClassAttributesTest < Test::Unit::TestCase
66
56
 
67
57
  context "with example2" do
68
58
 
69
- should 'ensure that different classes have their own synch instances' do
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
59
  should 'ensure that different classes have their own class attributes' do
76
60
  assert ex1 = SyncCAttrExample.new
77
61
  assert_equal 'hello world', ex1.class.test1
@@ -0,0 +1,11 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'yaml'
4
+ require 'minitest/autorun'
5
+ require 'minitest/reporters'
6
+ require 'minitest/stub_any_instance'
7
+ require 'shoulda/context'
8
+ require 'sync_attr'
9
+ require 'awesome_print'
10
+
11
+ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
metadata CHANGED
@@ -1,38 +1,36 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sync_attr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-04-03 00:00:00.000000000 Z
11
+ date: 2015-03-31 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: SyncAttr is a mixin to read, write and lazy initialize both class and
14
- instance variables in a multi-threaded environment when the attribute could be modified
15
- by two threads at the same time, written in Ruby.
13
+ description: When working in a multi-threaded environment it is important to ensure
14
+ that any attributes that are shared across threads are properly protected to ensure
15
+ that inconsistent data is not created. Lazy initializing these safe attributes improves
16
+ startup times and only creates resources when they are needed.
16
17
  email:
17
18
  - reidmo@gmail.com
18
19
  executables: []
19
20
  extensions: []
20
21
  extra_rdoc_files: []
21
22
  files:
22
- - Gemfile
23
- - Gemfile.lock
24
23
  - LICENSE.txt
25
24
  - README.md
26
25
  - Rakefile
27
- - examples/class_attribute.rb
28
- - examples/instance_attribute.rb
29
26
  - lib/sync_attr.rb
27
+ - lib/sync_attr/attributes.rb
30
28
  - lib/sync_attr/class_attributes.rb
31
- - lib/sync_attr/instance_attributes.rb
32
29
  - lib/sync_attr/version.rb
30
+ - test/attributes_test.rb
33
31
  - test/class_attributes_test.rb
34
- - test/instance_attributes_test.rb
35
- homepage: https://github.com/ClarityServices/sync_attr
32
+ - test/test_helper.rb
33
+ homepage: https://github.com/reidmorrison/sync_attr
36
34
  licenses:
37
35
  - Apache License V2.0
38
36
  metadata: {}
@@ -42,19 +40,21 @@ require_paths:
42
40
  - lib
43
41
  required_ruby_version: !ruby/object:Gem::Requirement
44
42
  requirements:
45
- - - '>='
43
+ - - ">="
46
44
  - !ruby/object:Gem::Version
47
45
  version: '0'
48
46
  required_rubygems_version: !ruby/object:Gem::Requirement
49
47
  requirements:
50
- - - '>='
48
+ - - ">="
51
49
  - !ruby/object:Gem::Version
52
50
  version: '0'
53
51
  requirements: []
54
52
  rubyforge_project:
55
- rubygems_version: 2.0.2
53
+ rubygems_version: 2.4.5
56
54
  signing_key:
57
55
  specification_version: 4
58
- summary: Thread safe accessors for Ruby class and instance attributes. Supports thread
59
- safe lazy loading of attributes
60
- test_files: []
56
+ summary: Thread-safe class and instance attributes
57
+ test_files:
58
+ - test/attributes_test.rb
59
+ - test/class_attributes_test.rb
60
+ - test/test_helper.rb
data/Gemfile DELETED
@@ -1,6 +0,0 @@
1
- source 'http://rubygems.org'
2
-
3
- group :test do
4
- gem "shoulda"
5
- end
6
-
@@ -1,26 +0,0 @@
1
- GEM
2
- remote: http://rubygems.org/
3
- specs:
4
- activesupport (3.2.12)
5
- i18n (~> 0.6)
6
- multi_json (~> 1.0)
7
- bourne (1.1.2)
8
- mocha (= 0.10.5)
9
- i18n (0.6.4)
10
- metaclass (0.0.1)
11
- mocha (0.10.5)
12
- metaclass (~> 0.0.1)
13
- multi_json (1.6.1)
14
- shoulda (3.3.2)
15
- shoulda-context (~> 1.0.1)
16
- shoulda-matchers (~> 1.4.1)
17
- shoulda-context (1.0.2)
18
- shoulda-matchers (1.4.2)
19
- activesupport (>= 3.0.0)
20
- bourne (~> 1.1.2)
21
-
22
- PLATFORMS
23
- ruby
24
-
25
- DEPENDENCIES
26
- shoulda
@@ -1,30 +0,0 @@
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 Class 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_cattr_reader :name do
13
- "Joe Bloggs"
14
- end
15
-
16
- # Thread safe Class Attribute reader and writer for age
17
- # Sets :age only when it is first called
18
- sync_cattr_accessor :age do
19
- 21
20
- end
21
- end
22
-
23
- puts "The person is #{Person.name} with age #{Person.age}"
24
-
25
- Person.age = 22
26
- puts "The person is #{Person.name} now has age #{Person.age}"
27
-
28
- Person.age = Proc.new {|age| age += 1 }
29
- puts "The person is #{Person.name} now has age #{Person.age}"
30
-
@@ -1,35 +0,0 @@
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}"