thread_safe 0.3.4-java → 0.3.5-java
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/.travis.yml +30 -11
- data/.yardopts +13 -0
- data/Gemfile +14 -1
- data/README.md +1 -1
- data/Rakefile +14 -1
- data/ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java +2 -2
- data/lib/thread_safe/atomic_reference_cache_backend.rb +155 -169
- data/lib/thread_safe/jruby_cache_backend.jar +0 -0
- data/lib/thread_safe/mri_cache_backend.rb +14 -7
- data/lib/thread_safe/non_concurrent_cache_backend.rb +4 -2
- data/lib/thread_safe/synchronized_cache_backend.rb +2 -1
- data/lib/thread_safe/synchronized_delegator.rb +3 -3
- data/lib/thread_safe/util/adder.rb +5 -2
- data/lib/thread_safe/util/cheap_lockable.rb +2 -1
- data/lib/thread_safe/util/striped64.rb +52 -56
- data/lib/thread_safe/util/volatile.rb +3 -1
- data/lib/thread_safe/util/xor_shift_random.rb +4 -2
- data/lib/thread_safe/version.rb +1 -1
- data/tasks/update_doc.rake +45 -0
- data/test/test_array.rb +1 -1
- data/test/test_cache.rb +1 -1
- data/test/test_hash.rb +1 -1
- data/test/test_helper.rb +30 -9
- data/thread_safe.gemspec +2 -2
- data/yard-template/default/fulldoc/html/css/common.css +125 -0
- data/yard-template/default/layout/html/footer.erb +16 -0
- metadata +13 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbd202dbf911be29e025cf656ec85fa07024805f
|
4
|
+
data.tar.gz: ece8fcb51b53bea677a7de3bd3729b5a2345e394
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8262d11575eda01b362dcf1661cfb023e1a6216fdffdfb0e4a47d4f48d0ea87a6d3950af9978d23dd9d85d524bbeef72f8b13b5c12db0bd930886bff2806c0d6
|
7
|
+
data.tar.gz: b1ac9f0689d82854d827dc58009e519518cb57cc25bb3908465322739b9e3f92c6d51e43e5f54fb89dc30ac6efa4136e5884fa6aecabf0b3eb8801fffb283aed
|
data/.travis.yml
CHANGED
@@ -1,24 +1,43 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
-
|
4
|
-
-
|
5
|
-
-
|
6
|
-
- 1.8.7
|
7
|
-
- 1.9.3
|
3
|
+
- 2.2.0
|
4
|
+
- 2.1.5
|
5
|
+
- 2.1.4
|
8
6
|
- 2.0.0
|
9
|
-
-
|
7
|
+
- 1.9.3
|
8
|
+
- ruby-head
|
9
|
+
- jruby-1.7.18
|
10
|
+
- jruby-head
|
11
|
+
- rbx-2
|
10
12
|
jdk: # for JRuby only
|
11
13
|
- openjdk7
|
12
14
|
- oraclejdk8
|
13
15
|
matrix:
|
14
16
|
exclude:
|
15
|
-
- rvm:
|
17
|
+
- rvm: 2.2.0
|
18
|
+
jdk: openjdk7
|
16
19
|
jdk: oraclejdk8
|
17
|
-
- rvm: 1.
|
20
|
+
- rvm: 2.1.5
|
21
|
+
jdk: openjdk7
|
18
22
|
jdk: oraclejdk8
|
19
|
-
- rvm: 1.
|
23
|
+
- rvm: 2.1.4
|
24
|
+
jdk: openjdk7
|
20
25
|
jdk: oraclejdk8
|
21
26
|
- rvm: 2.0.0
|
27
|
+
jdk: openjdk7
|
22
28
|
jdk: oraclejdk8
|
23
|
-
- rvm:
|
24
|
-
jdk:
|
29
|
+
- rvm: 1.9.3
|
30
|
+
jdk: openjdk7
|
31
|
+
jdk: oraclejdk8
|
32
|
+
- rvm: ruby-head
|
33
|
+
jdk: openjdk7
|
34
|
+
jdk: oraclejdk8
|
35
|
+
- rvm: rbx-2
|
36
|
+
jdk: openjdk7
|
37
|
+
jdk: oraclejdk8
|
38
|
+
allow_failures:
|
39
|
+
- rvm: ruby-head
|
40
|
+
- rvm: jruby-head
|
41
|
+
- rvm: 1.9.3
|
42
|
+
|
43
|
+
script: "rake TESTOPTS='--seed=1'"
|
data/.yardopts
ADDED
data/Gemfile
CHANGED
@@ -1,4 +1,17 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in thread_safe.gemspec
|
4
3
|
gemspec
|
4
|
+
|
5
|
+
group :development, :test do
|
6
|
+
gem 'minitest', '~> 5.5.1'
|
7
|
+
gem 'minitest-reporters', '~> 1.0.11'
|
8
|
+
gem 'simplecov', '~> 0.9.2', :require => false
|
9
|
+
gem 'coveralls', '~> 0.7.11', :require => false
|
10
|
+
end
|
11
|
+
|
12
|
+
group :documentation do
|
13
|
+
gem 'countloc', '~> 0.4.0', :platforms => :mri, :require => false
|
14
|
+
gem 'yard', '~> 0.8.7.6', :require => false
|
15
|
+
gem 'inch', '~> 0.5.10', :platforms => :mri, :require => false
|
16
|
+
gem 'redcarpet', '~> 3.2.2', platforms: :mri # understands github markdown
|
17
|
+
end
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Threadsafe
|
2
2
|
|
3
|
-
[](http://badge.fury.io/rb/thread_safe) [](https://travis-ci.org/ruby-concurrency/thread_safe) [](https://coveralls.io/r/ruby-concurrency/thread_safe) [](https://codeclimate.com/github/ruby-concurrency/thread_safe) [](https://gemnasium.com/ruby-concurrency/thread_safe) [](http://opensource.org/licenses/MIT) [](https://gitter.im/ruby-concurrency/concurrent-ruby)
|
4
4
|
|
5
5
|
A collection of thread-safe versions of common core Ruby classes.
|
6
6
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rake/testtask"
|
3
3
|
|
4
|
+
## safely load all the rake tasks in the `tasks` directory
|
5
|
+
def safe_load(file)
|
6
|
+
begin
|
7
|
+
load file
|
8
|
+
rescue LoadError => ex
|
9
|
+
puts "Error loading rake tasks from '#{file}' but will continue..."
|
10
|
+
puts ex.message
|
11
|
+
end
|
12
|
+
end
|
13
|
+
Dir.glob('tasks/**/*.rake').each do |rakefile|
|
14
|
+
safe_load rakefile
|
15
|
+
end
|
16
|
+
|
4
17
|
task :default => :test
|
5
18
|
|
6
19
|
if defined?(JRUBY_VERSION)
|
@@ -36,7 +49,7 @@ if defined?(JRUBY_VERSION)
|
|
36
49
|
ant.jar :basedir => 'pkg/tests', :destfile => 'test/package.jar', :includes => '**/*.class'
|
37
50
|
end
|
38
51
|
|
39
|
-
task :package => [ :jar, 'test-jar' ]
|
52
|
+
task :package => [ :clean, :compile, :jar, 'test-jar' ]
|
40
53
|
else
|
41
54
|
# No need to package anything for non-jruby rubies
|
42
55
|
task :package
|
@@ -63,7 +63,7 @@ public class JRubyCacheBackendLibrary implements Library {
|
|
63
63
|
return true;
|
64
64
|
} catch (Throwable t) { // ensuring we really do catch everything
|
65
65
|
// Doug's Unsafe setup errors always have this "Could not ini.." message
|
66
|
-
if (
|
66
|
+
if (isCausedBySecurityException(t)) {
|
67
67
|
return false;
|
68
68
|
}
|
69
69
|
throw (t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t));
|
@@ -72,7 +72,7 @@ public class JRubyCacheBackendLibrary implements Library {
|
|
72
72
|
|
73
73
|
private static boolean isCausedBySecurityException(Throwable t) {
|
74
74
|
while (t != null) {
|
75
|
-
if (t instanceof SecurityException) {
|
75
|
+
if ((t.getMessage() != null && t.getMessage().contains("Could not initialize intrinsics")) || t instanceof SecurityException) {
|
76
76
|
return true;
|
77
77
|
}
|
78
78
|
t = t.getCause();
|
@@ -1,169 +1,158 @@
|
|
1
1
|
module ThreadSafe
|
2
|
-
# A Ruby port of the Doug Lea's jsr166e.ConcurrentHashMapV8 class version 1.59
|
3
|
-
#
|
2
|
+
# A Ruby port of the Doug Lea's jsr166e.ConcurrentHashMapV8 class version 1.59
|
3
|
+
# available in public domain.
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
5
|
+
# Original source code available here:
|
6
|
+
# http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/ConcurrentHashMapV8.java?revision=1.59
|
7
7
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# operations are thread-safe, retrieval operations do _not_ entail locking,
|
11
|
-
# and there is _not_ any support for locking the entire table
|
12
|
-
# in a way that prevents all access.
|
8
|
+
# The Ruby port skips out the +TreeBin+ (red-black trees for use in bins whose
|
9
|
+
# size exceeds a threshold).
|
13
10
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# that key reporting the updated value.) For aggregate operations
|
20
|
-
# such as +clear()+, concurrent retrievals may reflect insertion or removal
|
21
|
-
# of only some entries. Similarly, the +each_pair+ iterator yields elements
|
22
|
-
# reflecting the state of the hash table at some point at or since
|
23
|
-
# the start of the +each_pair+. Bear in mind that the results of
|
24
|
-
# aggregate status methods including +size()+ and +empty?+} are typically
|
25
|
-
# useful only when a map is not undergoing concurrent updates in other
|
26
|
-
# threads. Otherwise the results of these methods reflect transient
|
27
|
-
# states that may be adequate for monitoring or estimation purposes, but not
|
28
|
-
# for program control.
|
11
|
+
# A hash table supporting full concurrency of retrievals and high expected
|
12
|
+
# concurrency for updates. However, even though all operations are
|
13
|
+
# thread-safe, retrieval operations do _not_ entail locking, and there is
|
14
|
+
# _not_ any support for locking the entire table in a way that prevents all
|
15
|
+
# access.
|
29
16
|
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
17
|
+
# Retrieval operations generally do not block, so may overlap with update
|
18
|
+
# operations. Retrievals reflect the results of the most recently _completed_
|
19
|
+
# update operations holding upon their onset. (More formally, an update
|
20
|
+
# operation for a given key bears a _happens-before_ relation with any (non
|
21
|
+
# +nil+) retrieval for that key reporting the updated value.) For aggregate
|
22
|
+
# operations such as +clear()+, concurrent retrievals may reflect insertion or
|
23
|
+
# removal of only some entries. Similarly, the +each_pair+ iterator yields
|
24
|
+
# elements reflecting the state of the hash table at some point at or since
|
25
|
+
# the start of the +each_pair+. Bear in mind that the results of aggregate
|
26
|
+
# status methods including +size()+ and +empty?+} are typically useful only
|
27
|
+
# when a map is not undergoing concurrent updates in other threads. Otherwise
|
28
|
+
# the results of these methods reflect transient states that may be adequate
|
29
|
+
# for monitoring or estimation purposes, but not for program control.
|
30
|
+
#
|
31
|
+
# The table is dynamically expanded when there are too many collisions (i.e.,
|
32
|
+
# keys that have distinct hash codes but fall into the same slot modulo the
|
33
|
+
# table size), with the expected average effect of maintaining roughly two
|
34
|
+
# bins per mapping (corresponding to a 0.75 load factor threshold for
|
35
|
+
# resizing). There may be much variance around this average as mappings are
|
36
|
+
# added and removed, but overall, this maintains a commonly accepted
|
37
|
+
# time/space tradeoff for hash tables. However, resizing this or any other
|
38
|
+
# kind of hash table may be a relatively slow operation. When possible, it is
|
39
|
+
# a good idea to provide a size estimate as an optional :initial_capacity
|
40
40
|
# initializer argument. An additional optional :load_factor constructor
|
41
|
-
# argument provides a further means of customizing initial table capacity
|
42
|
-
#
|
43
|
-
#
|
44
|
-
#
|
45
|
-
#
|
41
|
+
# argument provides a further means of customizing initial table capacity by
|
42
|
+
# specifying the table density to be used in calculating the amount of space
|
43
|
+
# to allocate for the given number of elements. Note that using many keys with
|
44
|
+
# exactly the same +hash+ is a sure way to slow down performance of any hash
|
45
|
+
# table.
|
46
46
|
#
|
47
|
-
#
|
47
|
+
# ## Design overview
|
48
48
|
#
|
49
|
-
# The primary design goal of this hash table is to maintain
|
50
|
-
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
# the same or better than plain +Hash+, and to support high
|
49
|
+
# The primary design goal of this hash table is to maintain concurrent
|
50
|
+
# readability (typically method +[]+, but also iteration and related methods)
|
51
|
+
# while minimizing update contention. Secondary goals are to keep space
|
52
|
+
# consumption about the same or better than plain +Hash+, and to support high
|
54
53
|
# initial insertion rates on an empty table by many threads.
|
55
54
|
#
|
56
|
-
# Each key-value mapping is held in a +Node+. The validation-based
|
57
|
-
#
|
58
|
-
#
|
55
|
+
# Each key-value mapping is held in a +Node+. The validation-based approach
|
56
|
+
# explained below leads to a lot of code sprawl because retry-control
|
57
|
+
# precludes factoring into smaller methods.
|
58
|
+
#
|
59
|
+
# The table is lazily initialized to a power-of-two size upon the first
|
60
|
+
# insertion. Each bin in the table normally contains a list of +Node+s (most
|
61
|
+
# often, the list has only zero or one +Node+). Table accesses require
|
62
|
+
# volatile/atomic reads, writes, and CASes. The lists of nodes within bins are
|
63
|
+
# always accurately traversable under volatile reads, so long as lookups check
|
64
|
+
# hash code and non-nullness of value before checking key equality.
|
59
65
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
# Table accesses require volatile/atomic reads, writes, and
|
64
|
-
# CASes. The lists of nodes within bins are always accurately traversable
|
65
|
-
# under volatile reads, so long as lookups check hash code
|
66
|
-
# and non-nullness of value before checking key equality.
|
66
|
+
# We use the top two bits of +Node+ hash fields for control purposes -- they
|
67
|
+
# are available anyway because of addressing constraints. As explained further
|
68
|
+
# below, these top bits are used as follows:
|
67
69
|
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
# 00 - Normal
|
73
|
-
# 01 - Locked
|
74
|
-
# 11 - Locked and may have a thread waiting for lock
|
75
|
-
# 10 - +Node+ is a forwarding node
|
70
|
+
# - 00 - Normal
|
71
|
+
# - 01 - Locked
|
72
|
+
# - 11 - Locked and may have a thread waiting for lock
|
73
|
+
# - 10 - +Node+ is a forwarding node
|
76
74
|
#
|
77
|
-
# The lower 28 bits of each +Node+'s hash field contain a
|
78
|
-
#
|
79
|
-
#
|
75
|
+
# The lower 28 bits of each +Node+'s hash field contain a the key's hash code,
|
76
|
+
# except for forwarding nodes, for which the lower bits are zero (and so
|
77
|
+
# always have hash field == +MOVED+).
|
80
78
|
#
|
81
|
-
# Insertion (via +[]=+ or its variants) of the first node in an
|
82
|
-
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
# the
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
91
|
-
# and so normally use builtin monitors only for blocking and signalling using
|
79
|
+
# Insertion (via +[]=+ or its variants) of the first node in an empty bin is
|
80
|
+
# performed by just CASing it to the bin. This is by far the most common case
|
81
|
+
# for put operations under most key/hash distributions. Other update
|
82
|
+
# operations (insert, delete, and replace) require locks. We do not want to
|
83
|
+
# waste the space required to associate a distinct lock object with each bin,
|
84
|
+
# so instead use the first node of a bin list itself as a lock. Blocking
|
85
|
+
# support for these locks relies +Util::CheapLockable. However, we also need a
|
86
|
+
# +try_lock+ construction, so we overlay these by using bits of the +Node+
|
87
|
+
# hash field for lock control (see above), and so normally use builtin
|
88
|
+
# monitors only for blocking and signalling using
|
92
89
|
# +cheap_wait+/+cheap_broadcast+ constructions. See +Node#try_await_lock+.
|
93
90
|
#
|
94
|
-
# Using the first node of a list as a lock does not by itself
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
# lazy locking technique described by Herlihy & Shavit.
|
91
|
+
# Using the first node of a list as a lock does not by itself suffice though:
|
92
|
+
# When a node is locked, any update must first validate that it is still the
|
93
|
+
# first node after locking it, and retry if not. Because new nodes are always
|
94
|
+
# appended to lists, once a node is first in a bin, it remains first until
|
95
|
+
# deleted or the bin becomes invalidated (upon resizing). However, operations
|
96
|
+
# that only conditionally update may inspect nodes until the point of update.
|
97
|
+
# This is a converse of sorts to the lazy locking technique described by
|
98
|
+
# Herlihy & Shavit.
|
103
99
|
#
|
104
|
-
# The main disadvantage of per-bin locks is that other update
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
# of
|
113
|
-
#
|
114
|
-
# list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The
|
115
|
-
# first values are:
|
100
|
+
# The main disadvantage of per-bin locks is that other update operations on
|
101
|
+
# other nodes in a bin list protected by the same lock can stall, for example
|
102
|
+
# when user +eql?+ or mapping functions take a long time. However,
|
103
|
+
# statistically, under random hash codes, this is not a common problem.
|
104
|
+
# Ideally, the frequency of nodes in bins follows a Poisson distribution
|
105
|
+
# (http://en.wikipedia.org/wiki/Poisson_distribution) with a parameter of
|
106
|
+
# about 0.5 on average, given the resizing threshold of 0.75, although with a
|
107
|
+
# large variance because of resizing granularity. Ignoring variance, the
|
108
|
+
# expected occurrences of list size k are (exp(-0.5) * pow(0.5, k) /
|
109
|
+
# factorial(k)). The first values are:
|
116
110
|
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
#
|
111
|
+
# - 0: 0.60653066
|
112
|
+
# - 1: 0.30326533
|
113
|
+
# - 2: 0.07581633
|
114
|
+
# - 3: 0.01263606
|
115
|
+
# - 4: 0.00157952
|
116
|
+
# - 5: 0.00015795
|
117
|
+
# - 6: 0.00001316
|
118
|
+
# - 7: 0.00000094
|
119
|
+
# - 8: 0.00000006
|
120
|
+
# - more: less than 1 in ten million
|
127
121
|
#
|
128
|
-
# Lock contention probability for two threads accessing distinct
|
129
|
-
#
|
122
|
+
# Lock contention probability for two threads accessing distinct elements is
|
123
|
+
# roughly 1 / (8 * #elements) under random hashes.
|
130
124
|
#
|
131
|
-
# The table is resized when occupancy exceeds a percentage
|
132
|
-
#
|
133
|
-
#
|
134
|
-
#
|
135
|
-
#
|
136
|
-
#
|
137
|
-
# power
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
# node (with hash field +MOVED+) that contains the next table as
|
147
|
-
# its key. On encountering a forwarding node, access and update
|
148
|
-
# operations restart, using the new table.
|
125
|
+
# The table is resized when occupancy exceeds a percentage threshold
|
126
|
+
# (nominally, 0.75, but see below). Only a single thread performs the resize
|
127
|
+
# (using field +size_control+, to arrange exclusion), but the table otherwise
|
128
|
+
# remains usable for reads and updates. Resizing proceeds by transferring
|
129
|
+
# bins, one by one, from the table to the next table. Because we are using
|
130
|
+
# power-of-two expansion, the elements from each bin must either stay at same
|
131
|
+
# index, or move with a power of two offset. We eliminate unnecessary node
|
132
|
+
# creation by catching cases where old nodes can be reused because their next
|
133
|
+
# fields won't change. On average, only about one-sixth of them need cloning
|
134
|
+
# when a table doubles. The nodes they replace will be garbage collectable as
|
135
|
+
# soon as they are no longer referenced by any reader thread that may be in
|
136
|
+
# the midst of concurrently traversing table. Upon transfer, the old table bin
|
137
|
+
# contains only a special forwarding node (with hash field +MOVED+) that
|
138
|
+
# contains the next table as its key. On encountering a forwarding node,
|
139
|
+
# access and update operations restart, using the new table.
|
149
140
|
#
|
150
|
-
# Each bin transfer requires its bin lock. However, unlike other
|
151
|
-
#
|
152
|
-
#
|
153
|
-
#
|
154
|
-
#
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
#
|
162
|
-
#
|
163
|
-
#
|
164
|
-
#
|
165
|
-
# is possible under the above encoding.) These more expensive
|
166
|
-
# mechanics trigger only when necessary.
|
141
|
+
# Each bin transfer requires its bin lock. However, unlike other cases, a
|
142
|
+
# transfer can skip a bin if it fails to acquire its lock, and revisit it
|
143
|
+
# later. Method +rebuild+ maintains a buffer of TRANSFER_BUFFER_SIZE bins that
|
144
|
+
# have been skipped because of failure to acquire a lock, and blocks only if
|
145
|
+
# none are available (i.e., only very rarely). The transfer operation must
|
146
|
+
# also ensure that all accessible bins in both the old and new table are
|
147
|
+
# usable by any traversal. When there are no lock acquisition failures, this
|
148
|
+
# is arranged simply by proceeding from the last bin (+table.size - 1+) up
|
149
|
+
# towards the first. Upon seeing a forwarding node, traversals arrange to move
|
150
|
+
# to the new table without revisiting nodes. However, when any node is skipped
|
151
|
+
# during a transfer, all earlier table bins may have become visible, so are
|
152
|
+
# initialized with a reverse-forwarding node back to the old table until the
|
153
|
+
# new ones are established. (This sometimes requires transiently locking a
|
154
|
+
# forwarding node, which is possible under the above encoding.) These more
|
155
|
+
# expensive mechanics trigger only when necessary.
|
167
156
|
#
|
168
157
|
# The traversal scheme also applies to partial traversals of
|
169
158
|
# ranges of bins (via an alternate Traverser constructor)
|
@@ -229,10 +218,10 @@ module ThreadSafe
|
|
229
218
|
end
|
230
219
|
end
|
231
220
|
|
232
|
-
# Key-value entry. Nodes with a hash field of +MOVED+ are special,
|
233
|
-
#
|
234
|
-
#
|
235
|
-
#
|
221
|
+
# Key-value entry. Nodes with a hash field of +MOVED+ are special, and do
|
222
|
+
# not contain user keys or values. Otherwise, keys are never +nil+, and
|
223
|
+
# +NULL+ +value+ fields indicate that a node is in the process of being
|
224
|
+
# deleted or created. For purposes of read-only access, a key may be read
|
236
225
|
# before a value, but can only be used after checking value to be +!= NULL+.
|
237
226
|
class Node
|
238
227
|
extend Util::Volatile
|
@@ -259,17 +248,15 @@ module ThreadSafe
|
|
259
248
|
self.next = next_node
|
260
249
|
end
|
261
250
|
|
262
|
-
# Spins a while if +LOCKED+ bit set and this node is the first
|
263
|
-
#
|
264
|
-
#
|
265
|
-
#
|
266
|
-
# which enables these simple single-wait mechanics.
|
251
|
+
# Spins a while if +LOCKED+ bit set and this node is the first of its bin,
|
252
|
+
# and then sets +WAITING+ bits on hash field and blocks (once) if they are
|
253
|
+
# still set. It is OK for this method to return even if lock is not
|
254
|
+
# available upon exit, which enables these simple single-wait mechanics.
|
267
255
|
#
|
268
|
-
# The corresponding signalling operation is performed within
|
269
|
-
#
|
270
|
-
#
|
271
|
-
#
|
272
|
-
# perform a +cheap_broadcast+.
|
256
|
+
# The corresponding signalling operation is performed within callers: Upon
|
257
|
+
# detecting that +WAITING+ has been set when unlocking lock (via a failed
|
258
|
+
# CAS from non-waiting +LOCKED+ state), unlockers acquire the
|
259
|
+
# +cheap_synchronize+ lock and perform a +cheap_broadcast+.
|
273
260
|
def try_await_lock(table, i)
|
274
261
|
if table && i >= 0 && i < table.size # bounds check, TODO: why are we bounds checking?
|
275
262
|
spins = SPIN_LOCK_ATTEMPTS
|
@@ -360,12 +347,12 @@ module ThreadSafe
|
|
360
347
|
extend Util::Volatile
|
361
348
|
attr_volatile :table, # The array of bins. Lazily initialized upon first insertion. Size is always a power of two.
|
362
349
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
350
|
+
# Table initialization and resizing control. When negative, the
|
351
|
+
# table is being initialized or resized. Otherwise, when table is
|
352
|
+
# null, holds the initial table size to use upon creation, or 0
|
353
|
+
# for default. After initialization, holds the next element count
|
354
|
+
# value upon which to resize the table.
|
355
|
+
:size_control
|
369
356
|
|
370
357
|
def initialize(options = nil)
|
371
358
|
super()
|
@@ -786,10 +773,9 @@ module ThreadSafe
|
|
786
773
|
current_table
|
787
774
|
end
|
788
775
|
|
789
|
-
# If table is too small and not already resizing, creates next
|
790
|
-
#
|
791
|
-
#
|
792
|
-
# are lagging additions.
|
776
|
+
# If table is too small and not already resizing, creates next table and
|
777
|
+
# transfers bins. Rechecks occupancy after a transfer to see if another
|
778
|
+
# resize is already needed because resizings are lagging additions.
|
793
779
|
def check_for_resize
|
794
780
|
while (current_table = table) && MAX_CAPACITY > (table_size = current_table.size) && NOW_RESIZING != (size_ctrl = size_control) && size_ctrl < @counter.sum
|
795
781
|
try_in_resize_lock(current_table, size_ctrl) do
|
Binary file
|
@@ -1,14 +1,21 @@
|
|
1
1
|
module ThreadSafe
|
2
2
|
class MriCacheBackend < NonConcurrentCacheBackend
|
3
|
-
# We can get away with a single global write lock (instead of a per-instance
|
3
|
+
# We can get away with a single global write lock (instead of a per-instance
|
4
|
+
# one) because of the GVL/green threads.
|
4
5
|
#
|
5
|
-
# The previous implementation used `Thread.critical` on 1.8 MRI to implement
|
6
|
-
#
|
7
|
-
#
|
8
|
-
# `
|
6
|
+
# The previous implementation used `Thread.critical` on 1.8 MRI to implement
|
7
|
+
# the 4 composed atomic operations (`put_if_absent`, `replace_pair`,
|
8
|
+
# `replace_if_exists`, `delete_pair`) this however doesn't work for
|
9
|
+
# `compute_if_absent` because on 1.8 the Mutex class is itself implemented
|
10
|
+
# via `Thread.critical` and a call to `Mutex#lock` does not restore the
|
11
|
+
# previous `Thread.critical` value (thus any synchronisation clears the
|
12
|
+
# `Thread.critical` flag and we loose control). This poses a problem as the
|
13
|
+
# provided block might use synchronisation on its own.
|
9
14
|
#
|
10
|
-
# NOTE: a neat idea of writing a c-ext to manually perform atomic
|
11
|
-
#
|
15
|
+
# NOTE: a neat idea of writing a c-ext to manually perform atomic
|
16
|
+
# put_if_absent, while relying on Ruby not releasing a GVL while calling a
|
17
|
+
# c-ext will not work because of the potentially Ruby implemented `#hash`
|
18
|
+
# and `#eql?` key methods.
|
12
19
|
WRITE_LOCK = Mutex.new
|
13
20
|
|
14
21
|
def []=(key, value)
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module ThreadSafe
|
2
2
|
class NonConcurrentCacheBackend
|
3
|
-
# WARNING: all public methods of the class must operate on the @backend
|
4
|
-
#
|
3
|
+
# WARNING: all public methods of the class must operate on the @backend
|
4
|
+
# directly without calling each other. This is important because of the
|
5
|
+
# SynchronizedCacheBackend which uses a non-reentrant mutex for perfomance
|
6
|
+
# reasons.
|
5
7
|
def initialize(options = nil)
|
6
8
|
@backend = {}
|
7
9
|
end
|
@@ -2,7 +2,8 @@ module ThreadSafe
|
|
2
2
|
class SynchronizedCacheBackend < NonConcurrentCacheBackend
|
3
3
|
require 'mutex_m'
|
4
4
|
include Mutex_m
|
5
|
-
# WARNING: Mutex_m is a non-reentrant lock, so the synchronized methods are
|
5
|
+
# WARNING: Mutex_m is a non-reentrant lock, so the synchronized methods are
|
6
|
+
# not allowed to call each other.
|
6
7
|
|
7
8
|
def [](key)
|
8
9
|
synchronize { super }
|
@@ -9,9 +9,9 @@ require 'monitor'
|
|
9
9
|
# array = SynchronizedDelegator.new([]) # thread-safe
|
10
10
|
#
|
11
11
|
# A simple `Monitor` provides a very coarse-grained way to synchronize a given
|
12
|
-
# object, in that it will cause synchronization for methods that have no
|
13
|
-
#
|
14
|
-
#
|
12
|
+
# object, in that it will cause synchronization for methods that have no need
|
13
|
+
# for it, but this is a trivial way to get thread-safety where none may exist
|
14
|
+
# currently on some implementations.
|
15
15
|
#
|
16
16
|
# This class is currently being considered for inclusion into stdlib, via
|
17
17
|
# https://bugs.ruby-lang.org/issues/8556
|
@@ -1,7 +1,10 @@
|
|
1
1
|
module ThreadSafe
|
2
2
|
module Util
|
3
|
-
# A Ruby port of the Doug Lea's jsr166e.LondAdder class version 1.8
|
4
|
-
#
|
3
|
+
# A Ruby port of the Doug Lea's jsr166e.LondAdder class version 1.8
|
4
|
+
# available in public domain.
|
5
|
+
#
|
6
|
+
# Original source code available here:
|
7
|
+
# http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.8
|
5
8
|
#
|
6
9
|
# One or more variables that together maintain an initially zero
|
7
10
|
# sum. When updates (method +add+) are contended across threads,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module ThreadSafe
|
2
2
|
module Util
|
3
|
-
# Provides a cheapest possible (mainly in terms of memory usage) +Mutex+
|
3
|
+
# Provides a cheapest possible (mainly in terms of memory usage) +Mutex+
|
4
|
+
# with the +ConditionVariable+ bundled in.
|
4
5
|
#
|
5
6
|
# Usage:
|
6
7
|
# class A
|
@@ -1,69 +1,65 @@
|
|
1
1
|
module ThreadSafe
|
2
2
|
module Util
|
3
|
-
# A Ruby port of the Doug Lea's jsr166e.Striped64 class version 1.6
|
4
|
-
#
|
3
|
+
# A Ruby port of the Doug Lea's jsr166e.Striped64 class version 1.6
|
4
|
+
# available in public domain.
|
5
5
|
#
|
6
|
-
#
|
6
|
+
# Original source code available here:
|
7
|
+
# http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java?revision=1.6
|
7
8
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# is a power of two. Indexing uses masked per-thread hash codes.
|
11
|
-
# Nearly all methods on this class are private, accessed directly
|
12
|
-
# by subclasses.
|
9
|
+
# Class holding common representation and mechanics for classes supporting
|
10
|
+
# dynamic striping on 64bit values.
|
13
11
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# cache
|
12
|
+
# This class maintains a lazily-initialized table of atomically updated
|
13
|
+
# variables, plus an extra +base+ field. The table size is a power of two.
|
14
|
+
# Indexing uses masked per-thread hash codes. Nearly all methods on this
|
15
|
+
# class are private, accessed directly by subclasses.
|
16
|
+
#
|
17
|
+
# Table entries are of class +Cell+; a variant of AtomicLong padded to
|
18
|
+
# reduce cache contention on most processors. Padding is overkill for most
|
19
|
+
# Atomics because they are usually irregularly scattered in memory and thus
|
20
|
+
# don't interfere much with each other. But Atomic objects residing in
|
21
|
+
# arrays will tend to be placed adjacent to each other, and so will most
|
22
|
+
# often share cache lines (with a huge negative performance impact) without
|
21
23
|
# this precaution.
|
22
24
|
#
|
23
|
-
# In part because +Cell+s are relatively large, we avoid creating
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
# number of CPUS. Table slots remain empty (+nil+) until they are
|
25
|
+
# In part because +Cell+s are relatively large, we avoid creating them until
|
26
|
+
# they are needed. When there is no contention, all updates are made to the
|
27
|
+
# +base+ field. Upon first contention (a failed CAS on +base+ update), the
|
28
|
+
# table is initialized to size 2. The table size is doubled upon further
|
29
|
+
# contention until reaching the nearest power of two greater than or equal
|
30
|
+
# to the number of CPUS. Table slots remain empty (+nil+) until they are
|
30
31
|
# needed.
|
31
32
|
#
|
32
|
-
# A single spinlock (+busy+) is used for initializing and
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# which is still better than alternatives.
|
33
|
+
# A single spinlock (+busy+) is used for initializing and resizing the
|
34
|
+
# table, as well as populating slots with new +Cell+s. There is no need for
|
35
|
+
# a blocking lock: When the lock is not available, threads try other slots
|
36
|
+
# (or the base). During these retries, there is increased contention and
|
37
|
+
# reduced locality, which is still better than alternatives.
|
38
38
|
#
|
39
|
-
# Per-thread hash codes are initialized to random values.
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# the
|
44
|
-
#
|
45
|
-
#
|
46
|
-
#
|
47
|
-
# using a secondary hash (XorShift) to try to find a
|
48
|
-
# free slot.
|
39
|
+
# Per-thread hash codes are initialized to random values. Contention and/or
|
40
|
+
# table collisions are indicated by failed CASes when performing an update
|
41
|
+
# operation (see method +retry_update+). Upon a collision, if the table size
|
42
|
+
# is less than the capacity, it is doubled in size unless some other thread
|
43
|
+
# holds the lock. If a hashed slot is empty, and lock is available, a new
|
44
|
+
# +Cell+ is created. Otherwise, if the slot exists, a CAS is tried. Retries
|
45
|
+
# proceed by "double hashing", using a secondary hash (XorShift) to try to
|
46
|
+
# find a free slot.
|
49
47
|
#
|
50
|
-
# The table size is capped because, when there are more threads
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
# observed contention rates are typically low in these cases.
|
48
|
+
# The table size is capped because, when there are more threads than CPUs,
|
49
|
+
# supposing that each thread were bound to a CPU, there would exist a
|
50
|
+
# perfect hash function mapping threads to slots that eliminates collisions.
|
51
|
+
# When we reach capacity, we search for this mapping by randomly varying the
|
52
|
+
# hash codes of colliding threads. Because search is random, and collisions
|
53
|
+
# only become known via CAS failures, convergence can be slow, and because
|
54
|
+
# threads are typically not bound to CPUS forever, may not occur at all.
|
55
|
+
# However, despite these limitations, observed contention rates are
|
56
|
+
# typically low in these cases.
|
60
57
|
#
|
61
|
-
# It is possible for a +Cell+ to become unused when threads that
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
# contention levels will recur, so the cells will eventually be
|
58
|
+
# It is possible for a +Cell+ to become unused when threads that once hashed
|
59
|
+
# to it terminate, as well as in the case where doubling the table causes no
|
60
|
+
# thread to hash to it under expanded mask. We do not try to detect or
|
61
|
+
# remove such cells, under the assumption that for long-running instances,
|
62
|
+
# observed contention levels will recur, so the cells will eventually be
|
67
63
|
# needed again; and for short-lived ones, it does not matter.
|
68
64
|
class Striped64
|
69
65
|
# Padded variant of AtomicLong supporting only raw accesses plus CAS.
|
@@ -85,8 +81,8 @@ module ThreadSafe
|
|
85
81
|
|
86
82
|
extend Volatile
|
87
83
|
attr_volatile :cells, # Table of cells. When non-null, size is a power of 2.
|
88
|
-
|
89
|
-
|
84
|
+
:base, # Base value, used mainly when there is no contention, but also as a fallback during table initialization races. Updated via CAS.
|
85
|
+
:busy # Spinlock (locked via CAS) used when resizing and/or creating Cells.
|
90
86
|
|
91
87
|
alias_method :busy?, :busy
|
92
88
|
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module ThreadSafe
|
2
2
|
module Util
|
3
3
|
module Volatile
|
4
|
-
# Provides +volatile+ (in the JVM's sense) attribute accessors implemented
|
4
|
+
# Provides +volatile+ (in the JVM's sense) attribute accessors implemented
|
5
|
+
# atop of the +AtomicReference+s.
|
6
|
+
#
|
5
7
|
# Usage:
|
6
8
|
# class Foo
|
7
9
|
# extend ThreadSafe::Util::Volatile
|
@@ -1,7 +1,9 @@
|
|
1
1
|
module ThreadSafe
|
2
2
|
module Util
|
3
|
-
# A xorshift random number (positive +Fixnum+s) generator, provides
|
4
|
-
#
|
3
|
+
# A xorshift random number (positive +Fixnum+s) generator, provides
|
4
|
+
# reasonably cheap way to generate thread local random numbers without
|
5
|
+
# contending for the global +Kernel.rand+.
|
6
|
+
#
|
5
7
|
# Usage:
|
6
8
|
# x = XorShiftRandom.get # uses Kernel.rand to generate an initial seed
|
7
9
|
# while true
|
data/lib/thread_safe/version.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'yard'
|
2
|
+
YARD::Rake::YardocTask.new
|
3
|
+
|
4
|
+
root = File.expand_path File.join(File.dirname(__FILE__), '..')
|
5
|
+
|
6
|
+
namespace :yard do
|
7
|
+
|
8
|
+
cmd = lambda do |command|
|
9
|
+
puts ">> executing: #{command}"
|
10
|
+
system command or raise "#{command} failed"
|
11
|
+
end
|
12
|
+
|
13
|
+
desc 'Pushes generated documentation to github pages: http://ruby-concurrency.github.io/thread_safe/'
|
14
|
+
task :push => [:setup, :yard] do
|
15
|
+
|
16
|
+
message = Dir.chdir(root) do
|
17
|
+
`git log -n 1 --oneline`.strip
|
18
|
+
end
|
19
|
+
puts "Generating commit: #{message}"
|
20
|
+
|
21
|
+
Dir.chdir "#{root}/yardoc" do
|
22
|
+
cmd.call "git add -A"
|
23
|
+
cmd.call "git commit -m '#{message}'"
|
24
|
+
cmd.call 'git push origin gh-pages'
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
desc 'Setups second clone in ./yardoc dir for pushing doc to github'
|
30
|
+
task :setup do
|
31
|
+
|
32
|
+
unless File.exist? "#{root}/yardoc/.git"
|
33
|
+
cmd.call "rm -rf #{root}/yardoc" if File.exist?("#{root}/yardoc")
|
34
|
+
Dir.chdir "#{root}" do
|
35
|
+
cmd.call 'git clone --single-branch --branch gh-pages git@github.com:ruby-concurrency/thread_safe.git ./yardoc'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
Dir.chdir "#{root}/yardoc" do
|
39
|
+
cmd.call 'git fetch origin'
|
40
|
+
cmd.call 'git reset --hard origin/gh-pages'
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/test/test_array.rb
CHANGED
data/test/test_cache.rb
CHANGED
data/test/test_hash.rb
CHANGED
data/test/test_helper.rb
CHANGED
@@ -1,14 +1,35 @@
|
|
1
|
-
|
2
|
-
require '
|
3
|
-
|
1
|
+
unless defined?(JRUBY_VERSION)
|
2
|
+
require 'simplecov'
|
3
|
+
require 'coveralls'
|
4
|
+
|
5
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
6
|
+
SimpleCov::Formatter::HTMLFormatter,
|
7
|
+
Coveralls::SimpleCov::Formatter
|
8
|
+
]
|
9
|
+
|
10
|
+
SimpleCov.start do
|
11
|
+
project_name 'thread_safe'
|
12
|
+
|
13
|
+
add_filter '/examples/'
|
14
|
+
add_filter '/pkg/'
|
15
|
+
add_filter '/test/'
|
16
|
+
add_filter '/tasks/'
|
17
|
+
add_filter '/yard-template/'
|
18
|
+
add_filter '/yardoc/'
|
19
|
+
|
20
|
+
command_name 'Mintest'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
4
24
|
require 'minitest/autorun'
|
5
25
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
26
|
+
require 'minitest/reporters'
|
27
|
+
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new(color: true)
|
28
|
+
|
29
|
+
require 'thread'
|
30
|
+
require 'thread_safe'
|
31
|
+
|
32
|
+
THREADS = (RUBY_ENGINE == 'ruby' ? 100 : 10)
|
12
33
|
|
13
34
|
if defined?(JRUBY_VERSION) && ENV['TEST_NO_UNSAFE']
|
14
35
|
# to be used like this: rake test TEST_NO_UNSAFE=true
|
data/thread_safe.gemspec
CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |gem|
|
|
7
7
|
gem.email = ["headius@headius.com", "thedarkone2@gmail.com"]
|
8
8
|
gem.description = %q{Thread-safe collections and utilities for Ruby}
|
9
9
|
gem.summary = %q{A collection of data structures and utilities to make thread-safe programming in Ruby easier}
|
10
|
-
gem.homepage = "https://github.com/
|
10
|
+
gem.homepage = "https://github.com/ruby-concurrency/thread_safe"
|
11
11
|
|
12
12
|
gem.files = `git ls-files`.split($\)
|
13
13
|
gem.files += ['lib/thread_safe/jruby_cache_backend.jar'] if defined?(JRUBY_VERSION)
|
@@ -20,7 +20,7 @@ Gem::Specification.new do |gem|
|
|
20
20
|
gem.version = ThreadSafe::VERSION
|
21
21
|
gem.license = "Apache-2.0"
|
22
22
|
|
23
|
-
gem.add_development_dependency 'atomic',
|
23
|
+
gem.add_development_dependency 'atomic', '= 1.1.16'
|
24
24
|
gem.add_development_dependency 'rake'
|
25
25
|
gem.add_development_dependency 'minitest', '>= 4'
|
26
26
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
/* Override this file with custom rules */
|
2
|
+
|
3
|
+
body {
|
4
|
+
line-height: 18px;
|
5
|
+
}
|
6
|
+
|
7
|
+
.docstring code, .docstring .object_link a, #filecontents code {
|
8
|
+
padding: 0px 3px 1px 3px;
|
9
|
+
border: 1px solid #eef;
|
10
|
+
background: #f5f5ff;
|
11
|
+
}
|
12
|
+
|
13
|
+
#filecontents pre code, .docstring pre code {
|
14
|
+
border: none;
|
15
|
+
background: none;
|
16
|
+
padding: 0;
|
17
|
+
}
|
18
|
+
|
19
|
+
#filecontents pre.code, .docstring pre.code, .tags pre.example, .docstring code, .docstring .object_link a,
|
20
|
+
#filecontents code {
|
21
|
+
-moz-border-radius: 2px;
|
22
|
+
-webkit-border-radius: 2px;
|
23
|
+
}
|
24
|
+
|
25
|
+
/* syntax highlighting */
|
26
|
+
.source_code {
|
27
|
+
display: none;
|
28
|
+
padding: 3px 8px;
|
29
|
+
border-left: 8px solid #ddd;
|
30
|
+
margin-top: 5px;
|
31
|
+
}
|
32
|
+
|
33
|
+
#filecontents pre.code, .docstring pre.code, .source_code pre {
|
34
|
+
font-family: monospace;
|
35
|
+
}
|
36
|
+
|
37
|
+
#filecontents pre.code, .docstring pre.code {
|
38
|
+
display: block;
|
39
|
+
}
|
40
|
+
|
41
|
+
.source_code .lines {
|
42
|
+
padding-right: 12px;
|
43
|
+
color: #555;
|
44
|
+
text-align: right;
|
45
|
+
}
|
46
|
+
|
47
|
+
#filecontents pre.code, .docstring pre.code,
|
48
|
+
.tags pre.example {
|
49
|
+
padding: 5px 12px;
|
50
|
+
margin-top: 4px;
|
51
|
+
border: 1px solid #eef;
|
52
|
+
background: #f5f5ff;
|
53
|
+
}
|
54
|
+
|
55
|
+
pre.code {
|
56
|
+
color: #000;
|
57
|
+
}
|
58
|
+
|
59
|
+
pre.code .info.file {
|
60
|
+
color: #555;
|
61
|
+
}
|
62
|
+
|
63
|
+
pre.code .val {
|
64
|
+
color: #036A07;
|
65
|
+
}
|
66
|
+
|
67
|
+
pre.code .tstring_content,
|
68
|
+
pre.code .heredoc_beg, pre.code .heredoc_end,
|
69
|
+
pre.code .qwords_beg, pre.code .qwords_end,
|
70
|
+
pre.code .tstring, pre.code .dstring {
|
71
|
+
color: #036A07;
|
72
|
+
}
|
73
|
+
|
74
|
+
pre.code .fid,
|
75
|
+
pre.code .rubyid_new,
|
76
|
+
pre.code .rubyid_to_s,
|
77
|
+
pre.code .rubyid_to_sym,
|
78
|
+
pre.code .rubyid_to_f,
|
79
|
+
pre.code .rubyid_to_i,
|
80
|
+
pre.code .rubyid_each {
|
81
|
+
color: inherit;
|
82
|
+
}
|
83
|
+
|
84
|
+
pre.code .comment {
|
85
|
+
color: #777;
|
86
|
+
font-style: italic;
|
87
|
+
}
|
88
|
+
|
89
|
+
pre.code .const, pre.code .constant {
|
90
|
+
color: inherit;
|
91
|
+
font-weight: bold;
|
92
|
+
font-style: italic;
|
93
|
+
}
|
94
|
+
|
95
|
+
pre.code .label,
|
96
|
+
pre.code .symbol {
|
97
|
+
color: #C5060B;
|
98
|
+
}
|
99
|
+
|
100
|
+
pre.code .kw,
|
101
|
+
pre.code .rubyid_require,
|
102
|
+
pre.code .rubyid_extend,
|
103
|
+
pre.code .rubyid_include,
|
104
|
+
pre.code .int {
|
105
|
+
color: #0000FF;
|
106
|
+
}
|
107
|
+
|
108
|
+
pre.code .ivar {
|
109
|
+
color: #660E7A;
|
110
|
+
}
|
111
|
+
|
112
|
+
pre.code .gvar,
|
113
|
+
pre.code .rubyid_backref,
|
114
|
+
pre.code .rubyid_nth_ref {
|
115
|
+
color: #6D79DE;
|
116
|
+
}
|
117
|
+
|
118
|
+
pre.code .regexp, .dregexp {
|
119
|
+
color: #036A07;
|
120
|
+
}
|
121
|
+
|
122
|
+
pre.code a {
|
123
|
+
border-bottom: 1px dotted #bbf;
|
124
|
+
}
|
125
|
+
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<div id="footer">
|
2
|
+
Generated on <%= Time.now.strftime("%c") %> by
|
3
|
+
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
4
|
+
<%= YARD::VERSION %> (ruby-<%= RUBY_VERSION %>).
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<script>
|
8
|
+
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
9
|
+
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
10
|
+
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
11
|
+
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
12
|
+
|
13
|
+
ga('create', 'UA-57940973-1', 'auto');
|
14
|
+
ga('send', 'pageview');
|
15
|
+
|
16
|
+
</script>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thread_safe
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.5
|
5
5
|
platform: java
|
6
6
|
authors:
|
7
7
|
- Charles Oliver Nutter
|
@@ -9,26 +9,20 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-03-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: atomic
|
16
16
|
version_requirements: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- - '
|
19
|
-
- !ruby/object:Gem::Version
|
20
|
-
version: 1.1.7
|
21
|
-
- - <
|
18
|
+
- - '='
|
22
19
|
- !ruby/object:Gem::Version
|
23
|
-
version:
|
20
|
+
version: 1.1.16
|
24
21
|
requirement: !ruby/object:Gem::Requirement
|
25
22
|
requirements:
|
26
|
-
- - '
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
version: 1.1.7
|
29
|
-
- - <
|
23
|
+
- - '='
|
30
24
|
- !ruby/object:Gem::Version
|
31
|
-
version:
|
25
|
+
version: 1.1.16
|
32
26
|
prerelease: false
|
33
27
|
type: :development
|
34
28
|
- !ruby/object:Gem::Dependency
|
@@ -68,6 +62,7 @@ extensions: []
|
|
68
62
|
extra_rdoc_files: []
|
69
63
|
files:
|
70
64
|
- .travis.yml
|
65
|
+
- .yardopts
|
71
66
|
- Gemfile
|
72
67
|
- LICENSE
|
73
68
|
- README.md
|
@@ -101,6 +96,7 @@ files:
|
|
101
96
|
- lib/thread_safe/util/volatile_tuple.rb
|
102
97
|
- lib/thread_safe/util/xor_shift_random.rb
|
103
98
|
- lib/thread_safe/version.rb
|
99
|
+
- tasks/update_doc.rake
|
104
100
|
- test/src/thread_safe/SecurityManager.java
|
105
101
|
- test/test_array.rb
|
106
102
|
- test/test_cache.rb
|
@@ -109,7 +105,9 @@ files:
|
|
109
105
|
- test/test_helper.rb
|
110
106
|
- test/test_synchronized_delegator.rb
|
111
107
|
- thread_safe.gemspec
|
112
|
-
|
108
|
+
- yard-template/default/fulldoc/html/css/common.css
|
109
|
+
- yard-template/default/layout/html/footer.erb
|
110
|
+
homepage: https://github.com/ruby-concurrency/thread_safe
|
113
111
|
licenses:
|
114
112
|
- Apache-2.0
|
115
113
|
metadata: {}
|
@@ -129,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
127
|
version: '0'
|
130
128
|
requirements: []
|
131
129
|
rubyforge_project:
|
132
|
-
rubygems_version: 2.
|
130
|
+
rubygems_version: 2.4.5
|
133
131
|
signing_key:
|
134
132
|
specification_version: 4
|
135
133
|
summary: A collection of data structures and utilities to make thread-safe programming in Ruby easier
|
@@ -141,3 +139,4 @@ test_files:
|
|
141
139
|
- test/test_hash.rb
|
142
140
|
- test/test_helper.rb
|
143
141
|
- test/test_synchronized_delegator.rb
|
142
|
+
has_rdoc:
|