thread_safe 0.1.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +144 -0
  5. data/README.md +34 -0
  6. data/Rakefile +36 -0
  7. data/examples/bench_cache.rb +35 -0
  8. data/ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java +200 -0
  9. data/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java +3842 -0
  10. data/ext/org/jruby/ext/thread_safe/jsr166e/LongAdder.java +204 -0
  11. data/ext/org/jruby/ext/thread_safe/jsr166e/Striped64.java +342 -0
  12. data/ext/org/jruby/ext/thread_safe/jsr166y/ThreadLocalRandom.java +199 -0
  13. data/ext/thread_safe/JrubyCacheBackendService.java +15 -0
  14. data/lib/thread_safe.rb +65 -0
  15. data/lib/thread_safe/atomic_reference_cache_backend.rb +922 -0
  16. data/lib/thread_safe/cache.rb +137 -0
  17. data/lib/thread_safe/mri_cache_backend.rb +62 -0
  18. data/lib/thread_safe/non_concurrent_cache_backend.rb +133 -0
  19. data/lib/thread_safe/synchronized_cache_backend.rb +76 -0
  20. data/lib/thread_safe/synchronized_delegator.rb +35 -0
  21. data/lib/thread_safe/util.rb +16 -0
  22. data/lib/thread_safe/util/adder.rb +59 -0
  23. data/lib/thread_safe/util/atomic_reference.rb +12 -0
  24. data/lib/thread_safe/util/cheap_lockable.rb +105 -0
  25. data/lib/thread_safe/util/power_of_two_tuple.rb +26 -0
  26. data/lib/thread_safe/util/striped64.rb +226 -0
  27. data/lib/thread_safe/util/volatile.rb +62 -0
  28. data/lib/thread_safe/util/volatile_tuple.rb +46 -0
  29. data/lib/thread_safe/util/xor_shift_random.rb +39 -0
  30. data/lib/thread_safe/version.rb +3 -0
  31. data/test/test_array.rb +20 -0
  32. data/test/test_cache.rb +792 -0
  33. data/test/test_cache_loops.rb +453 -0
  34. data/test/test_hash.rb +20 -0
  35. data/test/test_helper.rb +73 -0
  36. data/test/test_synchronized_delegator.rb +42 -0
  37. data/thread_safe.gemspec +21 -0
  38. metadata +100 -0
@@ -0,0 +1,59 @@
1
+ module ThreadSafe
2
+ module Util
3
+ # A Ruby port of the Doug Lea's jsr166e.LondAdder class version 1.8 available in public domain.
4
+ # Original source code available here: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.8
5
+ #
6
+ # One or more variables that together maintain an initially zero
7
+ # sum. When updates (method +add+) are contended across threads,
8
+ # the set of variables may grow dynamically to reduce contention.
9
+ # Method +sum+ returns the current total combined across the
10
+ # variables maintaining the sum.
11
+ #
12
+ # This class is usually preferable to single +Atomic+ reference when
13
+ # multiple threads update a common sum that is used for purposes such
14
+ # as collecting statistics, not for fine-grained synchronization
15
+ # control. Under low update contention, the two classes have similar
16
+ # characteristics. But under high contention, expected throughput of
17
+ # this class is significantly higher, at the expense of higher space
18
+ # consumption.
19
+ class Adder < Striped64
20
+ # Adds the given value.
21
+ def add(x)
22
+ if (current_cells = cells) || !cas_base_computed {|current_base| current_base + x}
23
+ was_uncontended = true
24
+ hash = hash_code
25
+ unless current_cells && (cell = current_cells.volatile_get_by_hash(hash)) && (was_uncontended = cell.cas_computed {|current_value| current_value + x})
26
+ retry_update(x, hash, was_uncontended) {|current_value| current_value + x}
27
+ end
28
+ end
29
+ end
30
+
31
+ def increment
32
+ add(1)
33
+ end
34
+
35
+ def decrement
36
+ add(-1)
37
+ end
38
+
39
+ # Returns the current sum. The returned value is _NOT_ an
40
+ # atomic snapshot: Invocation in the absence of concurrent
41
+ # updates returns an accurate result, but concurrent updates that
42
+ # occur while the sum is being calculated might not be
43
+ # incorporated.
44
+ def sum
45
+ x = base
46
+ if current_cells = cells
47
+ current_cells.each do |cell|
48
+ x += cell.value if cell
49
+ end
50
+ end
51
+ x
52
+ end
53
+
54
+ def reset
55
+ internal_reset(0)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,12 @@
1
+ module ThreadSafe
2
+ module Util
3
+ # An overhead-less atomic reference.
4
+ AtomicReference =
5
+ if defined?(Rubinius::AtomicReference)
6
+ Rubinius::AtomicReference
7
+ else
8
+ require 'atomic'
9
+ defined?(Atomic::InternalReference) ? Atomic::InternalReference : Atomic
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,105 @@
1
+ module ThreadSafe
2
+ module Util
3
+ # Provides a cheapest possible (mainly in terms of memory usage) +Mutex+ with the +ConditionVariable+ bundled in.
4
+ #
5
+ # Usage:
6
+ # class A
7
+ # include CheapLockable
8
+ #
9
+ # def do_exlusively
10
+ # cheap_synchronize { yield }
11
+ # end
12
+ #
13
+ # def wait_for_something
14
+ # cheap_synchronize do
15
+ # cheap_wait until resource_available?
16
+ # do_something
17
+ # cheap_broadcast # wake up others
18
+ # end
19
+ # end
20
+ # end
21
+ module CheapLockable
22
+ private
23
+ engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
24
+ if engine == 'rbx'
25
+ # Making use of the Rubinius' ability to lock via object headers to avoid the overhead of the extra Mutex objects.
26
+ def cheap_synchronize
27
+ Rubinius.lock(self)
28
+ begin
29
+ yield
30
+ ensure
31
+ Rubinius.unlock(self)
32
+ end
33
+ end
34
+
35
+ def cheap_wait
36
+ wchan = Rubinius::Channel.new
37
+
38
+ begin
39
+ waiters = @waiters ||= []
40
+ waiters.push wchan
41
+ Rubinius.unlock(self)
42
+ signaled = wchan.receive_timeout nil
43
+ ensure
44
+ Rubinius.lock(self)
45
+
46
+ unless signaled or waiters.delete(wchan)
47
+ # we timed out, but got signaled afterwards (e.g. while waiting to
48
+ # acquire @lock), so pass that signal on to the next waiter
49
+ waiters.shift << true unless waiters.empty?
50
+ end
51
+ end
52
+
53
+ self
54
+ end
55
+
56
+ def cheap_broadcast
57
+ waiters = @waiters ||= []
58
+ waiters.shift << true until waiters.empty?
59
+ self
60
+ end
61
+ elsif engine == 'jruby'
62
+ # Use Java's native synchronized (this) { wait(); notifyAll(); } to avoid the overhead of the extra Mutex objects
63
+ require 'jruby'
64
+
65
+ def cheap_synchronize
66
+ JRuby.reference0(self).synchronized { yield }
67
+ end
68
+
69
+ def cheap_wait
70
+ JRuby.reference0(self).wait
71
+ end
72
+
73
+ def cheap_broadcast
74
+ JRuby.reference0(self).notify_all
75
+ end
76
+ else
77
+ require 'thread'
78
+
79
+ extend Volatile
80
+ attr_volatile :mutex
81
+
82
+ # Non-reentrant Mutex#syncrhonize
83
+ def cheap_synchronize
84
+ true until (my_mutex = mutex) || cas_mutex(nil, my_mutex = Mutex.new)
85
+ my_mutex.synchronize { yield }
86
+ end
87
+
88
+ # Releases this object's +cheap_synchronize+ lock and goes to sleep waiting for other threads to +cheap_broadcast+, reacquires the lock on wakeup.
89
+ # Must only be called in +cheap_broadcast+'s block.
90
+ def cheap_wait
91
+ conditional_variable = @conditional_variable ||= ConditionVariable.new
92
+ conditional_variable.wait(mutex)
93
+ end
94
+
95
+ # Wakes up all threads waiting for this object's +cheap_synchronize+ lock.
96
+ # Must only be called in +cheap_broadcast+'s block.
97
+ def cheap_broadcast
98
+ if conditional_variable = @conditional_variable
99
+ conditional_variable.broadcast
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,26 @@
1
+ module ThreadSafe
2
+ module Util
3
+ class PowerOfTwoTuple < VolatileTuple
4
+ def initialize(size)
5
+ raise ArgumentError, "size must be a power of 2 (#{size.inspect} provided)" unless size > 0 && size & (size - 1) == 0
6
+ super(size)
7
+ end
8
+
9
+ def hash_to_index(hash)
10
+ (size - 1) & hash
11
+ end
12
+
13
+ def volatile_get_by_hash(hash)
14
+ volatile_get(hash_to_index(hash))
15
+ end
16
+
17
+ def volatile_set_by_hash(hash, value)
18
+ volatile_set(hash_to_index(hash), value)
19
+ end
20
+
21
+ def next_in_size_table
22
+ self.class.new(size << 1)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,226 @@
1
+ module ThreadSafe
2
+ module Util
3
+ # A Ruby port of the Doug Lea's jsr166e.Striped64 class version 1.6 available in public domain.
4
+ # Original source code available here: http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java?revision=1.6
5
+ #
6
+ # Class holding common representation and mechanics for classes supporting dynamic striping on 64bit values.
7
+ #
8
+ # This class maintains a lazily-initialized table of atomically
9
+ # updated variables, plus an extra +base+ field. The table size
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.
13
+ #
14
+ # Table entries are of class +Cell+; a variant of AtomicLong padded
15
+ # to reduce cache contention on most processors. Padding is
16
+ # overkill for most Atomics because they are usually irregularly
17
+ # scattered in memory and thus don't interfere much with each
18
+ # other. But Atomic objects residing in arrays will tend to be
19
+ # placed adjacent to each other, and so will most often share
20
+ # cache lines (with a huge negative performance impact) without
21
+ # this precaution.
22
+ #
23
+ # In part because +Cell+s are relatively large, we avoid creating
24
+ # them until they are needed. When there is no contention, all
25
+ # updates are made to the +base+ field. Upon first contention (a
26
+ # failed CAS on +base+ update), the table is initialized to size 2.
27
+ # The table size is doubled upon further contention until
28
+ # reaching the nearest power of two greater than or equal to the
29
+ # number of CPUS. Table slots remain empty (+nil+) until they are
30
+ # needed.
31
+ #
32
+ # A single spinlock (+busy+) is used for initializing and
33
+ # resizing the table, as well as populating slots with new +Cell+s.
34
+ # There is no need for a blocking lock: When the lock is not
35
+ # available, threads try other slots (or the base). During these
36
+ # retries, there is increased contention and reduced locality,
37
+ # which is still better than alternatives.
38
+ #
39
+ # Per-thread hash codes are initialized to random values.
40
+ # Contention and/or table collisions are indicated by failed
41
+ # CASes when performing an update operation (see method
42
+ # +retry_update+). Upon a collision, if the table size is less than
43
+ # the capacity, it is doubled in size unless some other thread
44
+ # holds the lock. If a hashed slot is empty, and lock is
45
+ # available, a new +Cell+ is created. Otherwise, if the slot
46
+ # exists, a CAS is tried. Retries proceed by "double hashing",
47
+ # using a secondary hash (XorShift) to try to find a
48
+ # free slot.
49
+ #
50
+ # The table size is capped because, when there are more threads
51
+ # than CPUs, supposing that each thread were bound to a CPU,
52
+ # there would exist a perfect hash function mapping threads to
53
+ # slots that eliminates collisions. When we reach capacity, we
54
+ # search for this mapping by randomly varying the hash codes of
55
+ # colliding threads. Because search is random, and collisions
56
+ # only become known via CAS failures, convergence can be slow,
57
+ # and because threads are typically not bound to CPUS forever,
58
+ # may not occur at all. However, despite these limitations,
59
+ # observed contention rates are typically low in these cases.
60
+ #
61
+ # It is possible for a +Cell+ to become unused when threads that
62
+ # once hashed to it terminate, as well as in the case where
63
+ # doubling the table causes no thread to hash to it under
64
+ # expanded mask. We do not try to detect or remove such cells,
65
+ # under the assumption that for long-running instances, observed
66
+ # contention levels will recur, so the cells will eventually be
67
+ # needed again; and for short-lived ones, it does not matter.
68
+ class Striped64
69
+ # Padded variant of AtomicLong supporting only raw accesses plus CAS.
70
+ # The +value+ field is placed between pads, hoping that the JVM doesn't
71
+ # reorder them.
72
+ #
73
+ # Optimisation note: It would be possible to use a release-only
74
+ # form of CAS here, if it were provided.
75
+ class Cell < AtomicReference
76
+ # TODO: this only adds padding after the :value slot, need to find a way to add padding before the slot
77
+ attr_reader *(Array.new(12).map {|i| :"padding_#{i}"})
78
+
79
+ alias_method :cas, :compare_and_set
80
+
81
+ def cas_computed
82
+ cas(current_value = value, yield(current_value))
83
+ end
84
+ end
85
+
86
+ extend Volatile
87
+ attr_volatile :cells, # Table of cells. When non-null, size is a power of 2.
88
+ :base, # Base value, used mainly when there is no contention, but also as a fallback during table initialization races. Updated via CAS.
89
+ :busy # Spinlock (locked via CAS) used when resizing and/or creating Cells.
90
+
91
+ alias_method :busy?, :busy
92
+
93
+ def initialize
94
+ super()
95
+ self.busy = false
96
+ self.base = 0
97
+ end
98
+
99
+ # Handles cases of updates involving initialization, resizing,
100
+ # creating new Cells, and/or contention. See above for
101
+ # explanation. This method suffers the usual non-modularity
102
+ # problems of optimistic retry code, relying on rechecked sets of
103
+ # reads.
104
+ #
105
+ # Arguments:
106
+ # [+x+]
107
+ # the value
108
+ # [+hash_code+]
109
+ # hash code used
110
+ # [+x+]
111
+ # false if CAS failed before call
112
+ def retry_update(x, hash_code, was_uncontended) # :yields: current_value
113
+ hash = hash_code
114
+ collided = false # True if last slot nonempty
115
+ while true
116
+ if current_cells = cells
117
+ if !(cell = current_cells.volatile_get_by_hash(hash))
118
+ if busy?
119
+ collided = false
120
+ else # Try to attach new Cell
121
+ if try_to_install_new_cell(Cell.new(x), hash) # Optimistically create and try to insert new cell
122
+ break
123
+ else
124
+ redo # Slot is now non-empty
125
+ end
126
+ end
127
+ elsif !was_uncontended # CAS already known to fail
128
+ was_uncontended = true # Continue after rehash
129
+ elsif cell.cas_computed {|current_value| yield current_value}
130
+ break
131
+ elsif current_cells.size >= CPU_COUNT || cells != current_cells # At max size or stale
132
+ collided = false
133
+ elsif collided && expand_table_unless_stale(current_cells)
134
+ collided = false
135
+ redo # Retry with expanded table
136
+ else
137
+ collided = true
138
+ end
139
+ hash = XorShiftRandom.xorshift(hash)
140
+
141
+ elsif try_initialize_cells(x, hash) || cas_base_computed {|current_base| yield current_base}
142
+ break
143
+ end
144
+ end
145
+ self.hash_code = hash
146
+ end
147
+
148
+ private
149
+ # Static per-thread hash code key. Shared across all instances to
150
+ # reduce Thread locals pollution and because adjustments due to
151
+ # collisions in one table are likely to be appropriate for
152
+ # others.
153
+ THREAD_LOCAL_KEY = "#{name}.hash_code".to_sym
154
+
155
+ # A thread-local hash code accessor. The code is initially
156
+ # random, but may be set to a different value upon collisions.
157
+ def hash_code
158
+ Thread.current[THREAD_LOCAL_KEY] ||= XorShiftRandom.get
159
+ end
160
+
161
+ def hash_code=(hash)
162
+ Thread.current[THREAD_LOCAL_KEY] = hash
163
+ end
164
+
165
+ # Sets base and all +cells+ to the given value.
166
+ def internal_reset(initial_value)
167
+ current_cells = cells
168
+ self.base = initial_value
169
+ if current_cells
170
+ current_cells.each do |cell|
171
+ cell.value = initial_value if cell
172
+ end
173
+ end
174
+ end
175
+
176
+ def cas_base_computed
177
+ cas_base(current_base = base, yield(current_base))
178
+ end
179
+
180
+ def free?
181
+ !busy?
182
+ end
183
+
184
+ def try_initialize_cells(x, hash)
185
+ if free? && !cells
186
+ try_in_busy do
187
+ unless cells # Recheck under lock
188
+ new_cells = PowerOfTwoTuple.new(2)
189
+ new_cells.volatile_set_by_hash(hash, Cell.new(x))
190
+ self.cells = new_cells
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ def expand_table_unless_stale(current_cells)
197
+ try_in_busy do
198
+ if current_cells == cells # Recheck under lock
199
+ new_cells = current_cells.next_in_size_table
200
+ current_cells.each_with_index {|x, i| new_cells.volatile_set(i, x)}
201
+ self.cells = new_cells
202
+ end
203
+ end
204
+ end
205
+
206
+ def try_to_install_new_cell(new_cell, hash)
207
+ try_in_busy do
208
+ # Recheck under lock
209
+ if (current_cells = cells) && !current_cells.volatile_get(i = current_cells.hash_to_index(hash))
210
+ current_cells.volatile_set(i, new_cell)
211
+ end
212
+ end
213
+ end
214
+
215
+ def try_in_busy
216
+ if cas_busy(false, true)
217
+ begin
218
+ yield
219
+ ensure
220
+ self.busy = false
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,62 @@
1
+ module ThreadSafe
2
+ module Util
3
+ module Volatile
4
+ # Provides +volatile+ (in the JVM's sense) attribute accessors implemented atop of the +AtomicReference+s.
5
+ # Usage:
6
+ # class Foo
7
+ # extend ThreadSafe::Util::Volatile
8
+ # attr_volatile :foo, :bar
9
+ #
10
+ # def initialize(bar)
11
+ # super() # must super() into parent initializers before using the volatile attribute accessors
12
+ # self.bar = bar
13
+ # end
14
+ #
15
+ # def hello
16
+ # my_foo = foo # volatile read
17
+ # self.foo = 1 # volatile write
18
+ # cas_foo(1, 2) # => true | a strong CAS
19
+ # end
20
+ # end
21
+ def attr_volatile(*attr_names)
22
+ return if attr_names.empty?
23
+ include(Module.new do
24
+ atomic_ref_setup = attr_names.map {|attr_name| "@__#{attr_name} = ThreadSafe::Util::AtomicReference.new"}
25
+ initialize_copy_setup = attr_names.zip(atomic_ref_setup).map do |attr_name, ref_setup|
26
+ "#{ref_setup}(other.instance_variable_get(:@__#{attr_name}).get)"
27
+ end
28
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
29
+ def initialize(*)
30
+ super
31
+ #{atomic_ref_setup.join('; ')}
32
+ end
33
+
34
+ def initialize_copy(other)
35
+ super
36
+ #{initialize_copy_setup.join('; ')}
37
+ end
38
+ RUBY_EVAL
39
+
40
+ attr_names.each do |attr_name|
41
+ class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
42
+ def #{attr_name}
43
+ @__#{attr_name}.get
44
+ end
45
+
46
+ def #{attr_name}=(value)
47
+ @__#{attr_name}.set(value)
48
+ end
49
+
50
+ def compare_and_set_#{attr_name}(old_value, new_value)
51
+ @__#{attr_name}.compare_and_set(old_value, new_value)
52
+ end
53
+ RUBY_EVAL
54
+
55
+ alias_method :"cas_#{attr_name}", :"compare_and_set_#{attr_name}"
56
+ alias_method :"lazy_set_#{attr_name}", :"#{attr_name}="
57
+ end
58
+ end)
59
+ end
60
+ end
61
+ end
62
+ end