sync_attr 1.0.0 → 2.0.0

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