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 +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 [![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
|
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}"
|