thread_safe 0.3.4-java → 0.3.5-java
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://travis-ci.org/
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/thread_safe.svg)](http://badge.fury.io/rb/thread_safe) [![Build Status](https://travis-ci.org/ruby-concurrency/thread_safe.svg?branch=master)](https://travis-ci.org/ruby-concurrency/thread_safe) [![Coverage Status](https://img.shields.io/coveralls/ruby-concurrency/thread_safe/master.svg)](https://coveralls.io/r/ruby-concurrency/thread_safe) [![Code Climate](https://codeclimate.com/github/ruby-concurrency/thread_safe.svg)](https://codeclimate.com/github/ruby-concurrency/thread_safe) [![Dependency Status](https://gemnasium.com/ruby-concurrency/thread_safe.svg)](https://gemnasium.com/ruby-concurrency/thread_safe) [![License](https://img.shields.io/badge/license-apache-green.svg)](http://opensource.org/licenses/MIT) [![Gitter chat](http://img.shields.io/badge/gitter-join%20chat%20%E2%86%92-brightgreen.svg)](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:
|