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 +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +125 -69
- data/Rakefile +15 -25
- data/lib/sync_attr.rb +4 -30
- data/lib/sync_attr/{instance_attributes.rb → attributes.rb} +41 -43
- data/lib/sync_attr/class_attributes.rb +64 -85
- data/lib/sync_attr/version.rb +1 -1
- data/test/{instance_attributes_test.rb → attributes_test.rb} +19 -12
- data/test/class_attributes_test.rb +2 -18
- data/test/test_helper.rb +11 -0
- metadata +18 -18
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -26
- data/examples/class_attribute.rb +0 -30
- data/examples/instance_attribute.rb +0 -35
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6081ee6568e87da15f5739a7e0f9098e8697a4db
|
4
|
+
data.tar.gz: 45bc09a1ff0ce624b42fda4fd27d4c114a57492a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4089d07c4d675212967e58cfe825b3f591b5535715ac6cc977f9aee24300c6d3a45503fbeb182681ee4f02f2f2b1e529d37d17206abe1033068dd3119703a668
|
7
|
+
data.tar.gz: 8aa44e84c312076143b0997f3140b8df9c9f4798e28e56c37ab3cbc2f671ac54b5f3d3aba5a605484b4230c92638cded73813aca06723f1153f487194780086b
|
data/LICENSE.txt
CHANGED
@@ -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
|
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 [](http://travis-ci.org/reidmorrison/sync_attr)
|
2
2
|
=========
|
3
3
|
|
4
|
-
Thread-safe Ruby class
|
4
|
+
Thread-safe Ruby class and instance attributes
|
5
5
|
|
6
|
-
* http://github.com/
|
6
|
+
* http://github.com/reidmorrison/sync_attr
|
7
7
|
|
8
|
-
|
8
|
+
## Status
|
9
9
|
|
10
|
-
|
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
|
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
|
-
|
34
|
+
## Features
|
20
35
|
|
21
|
-
* Adds thread-safe accessors for class attributes
|
22
|
-
*
|
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
|
30
|
-
once and only when needed.
|
31
|
-
* Avoids having to create yet another Rails initializer
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
96
|
+
Person.age = Proc.new {|age| age += 1 }
|
97
|
+
puts "The person is #{Person.name} now has age #{Person.age}"
|
38
98
|
|
39
|
-
|
99
|
+
# => The person is Joe Bloggs now has age 23
|
100
|
+
```
|
40
101
|
|
41
|
-
|
42
|
-
class Person
|
43
|
-
include SyncAttr
|
102
|
+
## Thread-safe Instance Attribute example
|
44
103
|
|
45
|
-
|
46
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
113
|
+
Example:
|
60
114
|
|
61
|
-
|
62
|
-
|
115
|
+
```ruby
|
116
|
+
require 'sync_attr'
|
63
117
|
|
64
|
-
|
65
|
-
|
118
|
+
# Sample class with lazy initialized Thread-safe attributes
|
119
|
+
class Person
|
120
|
+
include SyncAttr::Attributes
|
66
121
|
|
67
|
-
|
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
|
-
|
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
|
-
|
72
|
-
|
73
|
-
include SyncAttr
|
137
|
+
person = Person.new
|
138
|
+
puts "The person is #{person.name} with age #{person.age}"
|
74
139
|
|
75
|
-
|
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
|
-
|
82
|
-
|
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
|
-
|
89
|
-
puts "The person is #{person.name} with age #{person.age}"
|
145
|
+
# => The person is Joe Bloggs now has age 22
|
90
146
|
|
91
|
-
|
92
|
-
|
147
|
+
person.age = Proc.new {|age| age += 1 }
|
148
|
+
puts "The person is #{person.name} now has age #{person.age}"
|
93
149
|
|
94
|
-
|
95
|
-
|
150
|
+
# => The person is Joe Bloggs now has age 23
|
151
|
+
```
|
96
152
|
|
97
|
-
|
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/
|
105
|
-
* Home: <https://github.com/
|
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
|
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
|
-
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
9
5
|
require 'sync_attr/version'
|
10
6
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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(:
|
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['
|
25
|
+
Rake::Task['functional'].invoke
|
38
26
|
end
|
27
|
+
|
28
|
+
task :default => :test
|
data/lib/sync_attr.rb
CHANGED
@@ -1,35 +1,9 @@
|
|
1
|
-
|
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/
|
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
|
2
|
+
module Attributes
|
6
3
|
module ClassMethods
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
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
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
2
|
-
#
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/sync_attr/version.rb
CHANGED
@@ -1,13 +1,7 @@
|
|
1
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
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
|
data/test/test_helper.rb
ADDED
@@ -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:
|
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:
|
11
|
+
date: 2015-03-31 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
14
|
-
|
15
|
-
|
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/
|
35
|
-
homepage: https://github.com/
|
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.
|
53
|
+
rubygems_version: 2.4.5
|
56
54
|
signing_key:
|
57
55
|
specification_version: 4
|
58
|
-
summary: Thread
|
59
|
-
|
60
|
-
|
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
data/Gemfile.lock
DELETED
@@ -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
|
data/examples/class_attribute.rb
DELETED
@@ -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}"
|