thread_safe 0.1.1-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 +7 -0
- data/.gitignore +21 -0
- data/Gemfile +4 -0
- data/LICENSE +144 -0
- data/README.md +34 -0
- data/Rakefile +36 -0
- data/examples/bench_cache.rb +35 -0
- data/ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java +200 -0
- data/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java +3842 -0
- data/ext/org/jruby/ext/thread_safe/jsr166e/LongAdder.java +204 -0
- data/ext/org/jruby/ext/thread_safe/jsr166e/Striped64.java +342 -0
- data/ext/org/jruby/ext/thread_safe/jsr166y/ThreadLocalRandom.java +199 -0
- data/ext/thread_safe/JrubyCacheBackendService.java +15 -0
- data/lib/thread_safe.rb +65 -0
- data/lib/thread_safe/atomic_reference_cache_backend.rb +922 -0
- data/lib/thread_safe/cache.rb +137 -0
- data/lib/thread_safe/mri_cache_backend.rb +62 -0
- data/lib/thread_safe/non_concurrent_cache_backend.rb +133 -0
- data/lib/thread_safe/synchronized_cache_backend.rb +76 -0
- data/lib/thread_safe/synchronized_delegator.rb +35 -0
- data/lib/thread_safe/util.rb +16 -0
- data/lib/thread_safe/util/adder.rb +59 -0
- data/lib/thread_safe/util/atomic_reference.rb +12 -0
- data/lib/thread_safe/util/cheap_lockable.rb +105 -0
- data/lib/thread_safe/util/power_of_two_tuple.rb +26 -0
- data/lib/thread_safe/util/striped64.rb +226 -0
- data/lib/thread_safe/util/volatile.rb +62 -0
- data/lib/thread_safe/util/volatile_tuple.rb +46 -0
- data/lib/thread_safe/util/xor_shift_random.rb +39 -0
- data/lib/thread_safe/version.rb +3 -0
- data/test/test_array.rb +20 -0
- data/test/test_cache.rb +792 -0
- data/test/test_cache_loops.rb +453 -0
- data/test/test_hash.rb +20 -0
- data/test/test_helper.rb +73 -0
- data/test/test_synchronized_delegator.rb +42 -0
- data/thread_safe.gemspec +21 -0
- metadata +100 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
module ThreadSafe
|
2
|
+
module Util
|
3
|
+
# A fixed size array with volatile volatile getters/setters.
|
4
|
+
# Usage:
|
5
|
+
# arr = VolatileTuple.new(16)
|
6
|
+
# arr.volatile_set(0, :foo)
|
7
|
+
# arr.volatile_get(0) # => :foo
|
8
|
+
# arr.cas(0, :foo, :bar) # => true
|
9
|
+
# arr.volatile_get(0) # => :bar
|
10
|
+
class VolatileTuple
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
Tuple = defined?(Rubinius::Tuple) ? Rubinius::Tuple : Array
|
14
|
+
|
15
|
+
def initialize(size)
|
16
|
+
@tuple = tuple = Tuple.new(size)
|
17
|
+
i = 0
|
18
|
+
while i < size
|
19
|
+
tuple[i] = AtomicReference.new
|
20
|
+
i += 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def volatile_get(i)
|
25
|
+
@tuple[i].get
|
26
|
+
end
|
27
|
+
|
28
|
+
def volatile_set(i, value)
|
29
|
+
@tuple[i].set(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def compare_and_set(i, old_value, new_value)
|
33
|
+
@tuple[i].compare_and_set(old_value, new_value)
|
34
|
+
end
|
35
|
+
alias_method :cas, :compare_and_set
|
36
|
+
|
37
|
+
def size
|
38
|
+
@tuple.size
|
39
|
+
end
|
40
|
+
|
41
|
+
def each
|
42
|
+
@tuple.each {|ref| yield ref.get}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ThreadSafe
|
2
|
+
module Util
|
3
|
+
# A xorshift random number (positive +Fixnum+s) generator, provides reasonably cheap way to generate thread local random numbers without contending for
|
4
|
+
# the global +Kernel.rand+.
|
5
|
+
# Usage:
|
6
|
+
# x = XorShiftRandom.get # uses Kernel.rand to generate an initial seed
|
7
|
+
# while true
|
8
|
+
# if (x = XorShiftRandom.xorshift).odd? # thread-localy generate a next random number
|
9
|
+
# do_something_at_random
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
module XorShiftRandom
|
13
|
+
extend self
|
14
|
+
MAX_XOR_SHIFTABLE_INT = MAX_INT - 1
|
15
|
+
|
16
|
+
# Generates an initial non-zero positive +Fixnum+ via +Kernel.rand+.
|
17
|
+
def get
|
18
|
+
Kernel.rand(MAX_XOR_SHIFTABLE_INT) + 1 # 0 can't be xorshifted
|
19
|
+
end
|
20
|
+
|
21
|
+
# xorshift based on: http://www.jstatsoft.org/v08/i14/paper
|
22
|
+
if 0.size == 4
|
23
|
+
# using the "yˆ=y>>a; yˆ=y<<b; yˆ=y>>c;" transform with the (a,b,c) tuple with values (3,1,14) to minimise Bignum overflows
|
24
|
+
def xorshift(x)
|
25
|
+
x ^= x >> 3
|
26
|
+
x ^= (x << 1) & MAX_INT # cut-off Bignum overflow
|
27
|
+
x ^= x >> 14
|
28
|
+
end
|
29
|
+
else
|
30
|
+
# using the "yˆ=y>>a; yˆ=y<<b; yˆ=y>>c;" transform with the (a,b,c) tuple with values (1,1,54) to minimise Bignum overflows
|
31
|
+
def xorshift(x)
|
32
|
+
x ^= x >> 1
|
33
|
+
x ^= (x << 1) & MAX_INT # cut-off Bignum overflow
|
34
|
+
x ^= x >> 54
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/test/test_array.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'thread_safe'
|
3
|
+
|
4
|
+
class TestArray < Test::Unit::TestCase
|
5
|
+
def test_concurrency
|
6
|
+
ary = ThreadSafe::Array.new
|
7
|
+
assert_nothing_raised do
|
8
|
+
(1..100).map do |i|
|
9
|
+
Thread.new do
|
10
|
+
1000.times do
|
11
|
+
ary << i
|
12
|
+
ary.each {|x| x * 2}
|
13
|
+
ary.shift
|
14
|
+
ary.last
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end.map(&:join)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/test/test_cache.rb
ADDED
@@ -0,0 +1,792 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'thread_safe'
|
3
|
+
require 'thread'
|
4
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
5
|
+
|
6
|
+
Thread.abort_on_exception = true
|
7
|
+
|
8
|
+
class TestCache < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@cache = ThreadSafe::Cache.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_concurrency
|
14
|
+
cache = @cache
|
15
|
+
assert_nothing_raised do
|
16
|
+
(1..100).map do |i|
|
17
|
+
Thread.new do
|
18
|
+
1000.times do |j|
|
19
|
+
key = i*1000+j
|
20
|
+
cache[key] = i
|
21
|
+
cache[key]
|
22
|
+
cache.delete(key)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end.map(&:join)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_retrieval
|
30
|
+
assert_size_change 1 do
|
31
|
+
assert_equal nil, @cache[:a]
|
32
|
+
@cache[:a] = 1
|
33
|
+
assert_equal 1, @cache[:a]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_put_if_absent
|
38
|
+
with_or_without_default_proc do
|
39
|
+
assert_size_change 1 do
|
40
|
+
assert_equal nil, @cache.put_if_absent(:a, 1)
|
41
|
+
assert_equal 1, @cache.put_if_absent(:a, 1)
|
42
|
+
assert_equal 1, @cache.put_if_absent(:a, 2)
|
43
|
+
assert_equal 1, @cache[:a]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_compute_if_absent
|
49
|
+
with_or_without_default_proc do
|
50
|
+
assert_size_change 3 do
|
51
|
+
assert_equal(1, (@cache.compute_if_absent(:a) {1}))
|
52
|
+
assert_equal(1, (@cache.compute_if_absent(:a) {2}))
|
53
|
+
assert_equal 1, @cache[:a]
|
54
|
+
@cache[:b] = nil
|
55
|
+
assert_equal(nil, (@cache.compute_if_absent(:b) {1}))
|
56
|
+
assert_equal(nil, (@cache.compute_if_absent(:c) {}))
|
57
|
+
assert_equal nil, @cache[:c]
|
58
|
+
assert_equal true, @cache.key?(:c)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_compute_if_absent_with_return
|
64
|
+
with_or_without_default_proc { assert_handles_return_lambda(:compute_if_absent, :a) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_compute_if_absent_exception
|
68
|
+
with_or_without_default_proc { assert_handles_exception(:compute_if_absent, :a) }
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_compute_if_absent_atomicity
|
72
|
+
late_compute_threads_count = 10
|
73
|
+
late_put_if_absent_threads_count = 10
|
74
|
+
getter_threads_count = 5
|
75
|
+
compute_started = ThreadSafe::Test::Latch.new(1)
|
76
|
+
compute_proceed = ThreadSafe::Test::Latch.new(late_compute_threads_count + late_put_if_absent_threads_count + getter_threads_count)
|
77
|
+
block_until_compute_started = lambda do |name|
|
78
|
+
if (v = @cache[:a]) != nil
|
79
|
+
assert_equal nil, v
|
80
|
+
end
|
81
|
+
compute_proceed.release
|
82
|
+
compute_started.await
|
83
|
+
end
|
84
|
+
|
85
|
+
assert_size_change 1 do
|
86
|
+
late_compute_threads = Array.new(late_compute_threads_count) do
|
87
|
+
Thread.new do
|
88
|
+
block_until_compute_started.call('compute_if_absent')
|
89
|
+
assert_equal(1, (@cache.compute_if_absent(:a) { flunk }))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
late_put_if_absent_threads = Array.new(late_put_if_absent_threads_count) do
|
94
|
+
Thread.new do
|
95
|
+
block_until_compute_started.call('put_if_absent')
|
96
|
+
assert_equal(1, @cache.put_if_absent(:a, 2))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
getter_threads = Array.new(getter_threads_count) do
|
101
|
+
Thread.new do
|
102
|
+
block_until_compute_started.call('getter')
|
103
|
+
Thread.pass while @cache[:a].nil?
|
104
|
+
assert_equal 1, @cache[:a]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
Thread.new do
|
109
|
+
@cache.compute_if_absent(:a) do
|
110
|
+
compute_started.release
|
111
|
+
compute_proceed.await
|
112
|
+
sleep(0.2)
|
113
|
+
1
|
114
|
+
end
|
115
|
+
end.join
|
116
|
+
(late_compute_threads + late_put_if_absent_threads + getter_threads).each(&:join)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_compute_if_present
|
121
|
+
with_or_without_default_proc do
|
122
|
+
assert_no_size_change do
|
123
|
+
assert_equal(nil, @cache.compute_if_present(:a) {})
|
124
|
+
assert_equal(nil, @cache.compute_if_present(:a) {1})
|
125
|
+
assert_equal(nil, @cache.compute_if_present(:a) {flunk})
|
126
|
+
assert_equal false, @cache.key?(:a)
|
127
|
+
end
|
128
|
+
|
129
|
+
@cache[:a] = 1
|
130
|
+
assert_no_size_change do
|
131
|
+
assert_equal(1, @cache.compute_if_present(:a) {1})
|
132
|
+
assert_equal(1, @cache[:a])
|
133
|
+
assert_equal(2, @cache.compute_if_present(:a) {2})
|
134
|
+
assert_equal(2, @cache[:a])
|
135
|
+
assert_equal(false, @cache.compute_if_present(:a) {false})
|
136
|
+
assert_equal(false, @cache[:a])
|
137
|
+
|
138
|
+
@cache[:a] = 1
|
139
|
+
yielded = false
|
140
|
+
@cache.compute_if_present(:a) do |old_value|
|
141
|
+
yielded = true
|
142
|
+
assert_equal 1, old_value
|
143
|
+
2
|
144
|
+
end
|
145
|
+
assert yielded
|
146
|
+
end
|
147
|
+
|
148
|
+
assert_size_change -1 do
|
149
|
+
assert_equal(nil, @cache.compute_if_present(:a) {})
|
150
|
+
assert_equal(false, @cache.key?(:a))
|
151
|
+
assert_equal(nil, @cache.compute_if_present(:a) {1})
|
152
|
+
assert_equal(false, @cache.key?(:a))
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_compute_if_present_with_return
|
158
|
+
with_or_without_default_proc do
|
159
|
+
@cache[:a] = 1
|
160
|
+
assert_handles_return_lambda(:compute_if_present, :a)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_compute_if_present_exception
|
165
|
+
with_or_without_default_proc do
|
166
|
+
@cache[:a] = 1
|
167
|
+
assert_handles_exception(:compute_if_present, :a)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_compute
|
172
|
+
with_or_without_default_proc do
|
173
|
+
assert_no_size_change do
|
174
|
+
assert_compute(:a, nil, nil) {}
|
175
|
+
end
|
176
|
+
|
177
|
+
assert_size_change 1 do
|
178
|
+
assert_compute(:a, nil, 1) {1}
|
179
|
+
assert_compute(:a, 1, 2) {2}
|
180
|
+
assert_compute(:a, 2, false) {false}
|
181
|
+
assert_equal false, @cache[:a]
|
182
|
+
end
|
183
|
+
|
184
|
+
assert_size_change -1 do
|
185
|
+
assert_compute(:a, false, nil) {}
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_compute_with_return
|
191
|
+
with_or_without_default_proc do
|
192
|
+
assert_handles_return_lambda(:compute, :a)
|
193
|
+
@cache[:a] = 1
|
194
|
+
assert_handles_return_lambda(:compute, :a)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def test_compute_exception
|
199
|
+
with_or_without_default_proc do
|
200
|
+
assert_handles_exception(:compute, :a)
|
201
|
+
@cache[:a] = 1
|
202
|
+
assert_handles_exception(:compute, :a)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_merge_pair
|
207
|
+
with_or_without_default_proc do
|
208
|
+
assert_size_change 1 do
|
209
|
+
assert_equal(nil, @cache.merge_pair(:a, nil) {flunk})
|
210
|
+
assert_equal true, @cache.key?(:a)
|
211
|
+
assert_equal nil, @cache[:a]
|
212
|
+
end
|
213
|
+
|
214
|
+
assert_no_size_change do
|
215
|
+
assert_merge_pair(:a, nil, nil, false) {false}
|
216
|
+
assert_merge_pair(:a, nil, false, 1) {1}
|
217
|
+
assert_merge_pair(:a, nil, 1, 2) {2}
|
218
|
+
end
|
219
|
+
|
220
|
+
assert_size_change -1 do
|
221
|
+
assert_merge_pair(:a, nil, 2, nil) {}
|
222
|
+
assert_equal false, @cache.key?(:a)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_merge_pair_with_return
|
228
|
+
with_or_without_default_proc do
|
229
|
+
@cache[:a] = 1
|
230
|
+
assert_handles_return_lambda(:merge_pair, :a, 2)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_merge_pair_exception
|
235
|
+
with_or_without_default_proc do
|
236
|
+
@cache[:a] = 1
|
237
|
+
assert_handles_exception(:merge_pair, :a, 2)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_updates_dont_block_reads
|
242
|
+
getters_count = 20
|
243
|
+
key_klass = ThreadSafe::Test::HashCollisionKey
|
244
|
+
keys = [key_klass.new(1, 100), key_klass.new(2, 100), key_klass.new(3, 100)] # hash colliding keys
|
245
|
+
inserted_keys = []
|
246
|
+
|
247
|
+
keys.each do |key, i|
|
248
|
+
compute_started = ThreadSafe::Test::Latch.new(1)
|
249
|
+
compute_finished = ThreadSafe::Test::Latch.new(1)
|
250
|
+
getters_started = ThreadSafe::Test::Latch.new(getters_count)
|
251
|
+
getters_finished = ThreadSafe::Test::Latch.new(getters_count)
|
252
|
+
|
253
|
+
computer_thread = Thread.new do
|
254
|
+
getters_started.await
|
255
|
+
@cache.compute_if_absent(key) do
|
256
|
+
compute_started.release
|
257
|
+
getters_finished.await
|
258
|
+
1
|
259
|
+
end
|
260
|
+
compute_finished.release
|
261
|
+
end
|
262
|
+
|
263
|
+
getter_threads = (1..getters_count).map do
|
264
|
+
Thread.new do
|
265
|
+
getters_started.release
|
266
|
+
inserted_keys.each do |inserted_key|
|
267
|
+
assert_equal true, @cache.key?(inserted_key)
|
268
|
+
assert_equal 1, @cache[inserted_key]
|
269
|
+
end
|
270
|
+
assert_equal false, @cache.key?(key)
|
271
|
+
compute_started.await
|
272
|
+
inserted_keys.each do |inserted_key|
|
273
|
+
assert_equal true, @cache.key?(inserted_key)
|
274
|
+
assert_equal 1, @cache[inserted_key]
|
275
|
+
end
|
276
|
+
assert_equal false, @cache.key?(key)
|
277
|
+
assert_equal nil, @cache[key]
|
278
|
+
getters_finished.release
|
279
|
+
compute_finished.await
|
280
|
+
assert_equal true, @cache.key?(key)
|
281
|
+
assert_equal 1, @cache[key]
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
(getter_threads << computer_thread).map {|t| assert(t.join(2))} # asserting no deadlocks
|
286
|
+
inserted_keys << key
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def test_collision_resistance
|
291
|
+
keys = (0..1000).map {|i| ThreadSafe::Test::HashCollisionKey.new(i, 1)}
|
292
|
+
keys.each {|k| @cache[k] = k.key}
|
293
|
+
10.times do |i|
|
294
|
+
size = keys.size
|
295
|
+
while i < size
|
296
|
+
k = keys[i]
|
297
|
+
assert(k.key == @cache.delete(k) && !@cache.key?(k) && (@cache[k] = k.key; @cache[k] == k.key))
|
298
|
+
i += 10
|
299
|
+
end
|
300
|
+
end
|
301
|
+
assert(keys.all? {|k| @cache[k] == k.key})
|
302
|
+
end
|
303
|
+
|
304
|
+
def test_replace_pair
|
305
|
+
with_or_without_default_proc do
|
306
|
+
assert_no_size_change do
|
307
|
+
assert_equal false, @cache.replace_pair(:a, 1, 2)
|
308
|
+
assert_equal false, @cache.replace_pair(:a, nil, nil)
|
309
|
+
assert_equal false, @cache.key?(:a)
|
310
|
+
end
|
311
|
+
|
312
|
+
@cache[:a] = 1
|
313
|
+
assert_no_size_change do
|
314
|
+
assert_equal true, @cache.replace_pair(:a, 1, 2)
|
315
|
+
assert_equal false, @cache.replace_pair(:a, 1, 2)
|
316
|
+
assert_equal 2, @cache[:a]
|
317
|
+
assert_equal true, @cache.replace_pair(:a, 2, 2)
|
318
|
+
assert_equal 2, @cache[:a]
|
319
|
+
assert_equal true, @cache.replace_pair(:a, 2, nil)
|
320
|
+
assert_equal false, @cache.replace_pair(:a, 2, nil)
|
321
|
+
assert_equal nil, @cache[:a]
|
322
|
+
assert_equal true, @cache.key?(:a)
|
323
|
+
assert_equal true, @cache.replace_pair(:a, nil, nil)
|
324
|
+
assert_equal true, @cache.key?(:a)
|
325
|
+
assert_equal true, @cache.replace_pair(:a, nil, 1)
|
326
|
+
assert_equal 1, @cache[:a]
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def test_replace_if_exists
|
332
|
+
with_or_without_default_proc do
|
333
|
+
assert_no_size_change do
|
334
|
+
assert_equal nil, @cache.replace_if_exists(:a, 1)
|
335
|
+
assert_equal false, @cache.key?(:a)
|
336
|
+
end
|
337
|
+
|
338
|
+
@cache[:a] = 1
|
339
|
+
assert_no_size_change do
|
340
|
+
assert_equal 1, @cache.replace_if_exists(:a, 2)
|
341
|
+
assert_equal 2, @cache[:a]
|
342
|
+
assert_equal 2, @cache.replace_if_exists(:a, nil)
|
343
|
+
assert_equal nil, @cache[:a]
|
344
|
+
assert_equal true, @cache.key?(:a)
|
345
|
+
assert_equal nil, @cache.replace_if_exists(:a, 1)
|
346
|
+
assert_equal 1, @cache[:a]
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def test_get_and_set
|
352
|
+
with_or_without_default_proc do
|
353
|
+
assert_size_change 1 do
|
354
|
+
assert_equal nil, @cache.get_and_set(:a, 1)
|
355
|
+
assert_equal true, @cache.key?(:a)
|
356
|
+
assert_equal 1, @cache[:a]
|
357
|
+
assert_equal 1, @cache.get_and_set(:a, 2)
|
358
|
+
assert_equal 2, @cache.get_and_set(:a, nil)
|
359
|
+
assert_equal nil, @cache[:a]
|
360
|
+
assert_equal true, @cache.key?(:a)
|
361
|
+
assert_equal nil, @cache.get_and_set(:a, 1)
|
362
|
+
assert_equal 1, @cache[:a]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def test_key
|
368
|
+
with_or_without_default_proc do
|
369
|
+
assert_equal false, @cache.key?(:a)
|
370
|
+
@cache[:a] = 1
|
371
|
+
assert_equal true, @cache.key?(:a)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def test_value
|
376
|
+
with_or_without_default_proc do
|
377
|
+
assert_equal false, @cache.value?(1)
|
378
|
+
@cache[:a] = 1
|
379
|
+
assert_equal true, @cache.value?(1)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def test_delete
|
384
|
+
with_or_without_default_proc do |default_proc_set|
|
385
|
+
assert_no_size_change do
|
386
|
+
assert_equal nil, @cache.delete(:a)
|
387
|
+
end
|
388
|
+
@cache[:a] = 1
|
389
|
+
assert_size_change -1 do
|
390
|
+
assert_equal 1, @cache.delete(:a)
|
391
|
+
end
|
392
|
+
assert_no_size_change do
|
393
|
+
assert_equal nil, @cache[:a] unless default_proc_set
|
394
|
+
|
395
|
+
assert_equal false, @cache.key?(:a)
|
396
|
+
assert_equal nil, @cache.delete(:a)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def test_delete_pair
|
402
|
+
with_or_without_default_proc do
|
403
|
+
assert_no_size_change do
|
404
|
+
assert_equal false, @cache.delete_pair(:a, 2)
|
405
|
+
assert_equal false, @cache.delete_pair(:a, nil)
|
406
|
+
end
|
407
|
+
@cache[:a] = 1
|
408
|
+
assert_no_size_change do
|
409
|
+
assert_equal false, @cache.delete_pair(:a, 2)
|
410
|
+
end
|
411
|
+
assert_size_change -1 do
|
412
|
+
assert_equal 1, @cache[:a]
|
413
|
+
assert_equal true, @cache.delete_pair(:a, 1)
|
414
|
+
assert_equal false, @cache.delete_pair(:a, 1)
|
415
|
+
assert_equal false, @cache.key?(:a)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
def test_default_proc
|
421
|
+
@cache = cache_with_default_proc(1)
|
422
|
+
assert_no_size_change do
|
423
|
+
assert_equal false, @cache.key?(:a)
|
424
|
+
end
|
425
|
+
assert_size_change 1 do
|
426
|
+
assert_equal 1, @cache[:a]
|
427
|
+
assert_equal true, @cache.key?(:a)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def test_falsy_default_proc
|
432
|
+
@cache = cache_with_default_proc(nil)
|
433
|
+
assert_no_size_change do
|
434
|
+
assert_equal false, @cache.key?(:a)
|
435
|
+
end
|
436
|
+
assert_size_change 1 do
|
437
|
+
assert_equal nil, @cache[:a]
|
438
|
+
assert_equal true, @cache.key?(:a)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def test_fetch
|
443
|
+
with_or_without_default_proc do |default_proc_set|
|
444
|
+
assert_no_size_change do
|
445
|
+
assert_equal 1, @cache.fetch(:a, 1)
|
446
|
+
assert_equal(1, (@cache.fetch(:a) {1}))
|
447
|
+
assert_equal false, @cache.key?(:a)
|
448
|
+
|
449
|
+
assert_equal nil, @cache[:a] unless default_proc_set
|
450
|
+
end
|
451
|
+
|
452
|
+
@cache[:a] = 1
|
453
|
+
assert_no_size_change do
|
454
|
+
assert_equal(1, (@cache.fetch(:a) {flunk}))
|
455
|
+
end
|
456
|
+
|
457
|
+
assert_raise(ThreadSafe::Cache::KEY_ERROR) do
|
458
|
+
@cache.fetch(:b)
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
def test_falsy_fetch
|
464
|
+
with_or_without_default_proc do
|
465
|
+
assert_equal false, @cache.key?(:a)
|
466
|
+
|
467
|
+
assert_no_size_change do
|
468
|
+
assert_equal(nil, @cache.fetch(:a, nil))
|
469
|
+
assert_equal(false, @cache.fetch(:a, false))
|
470
|
+
assert_equal(nil, (@cache.fetch(:a) {}))
|
471
|
+
assert_equal(false, (@cache.fetch(:a) {false}))
|
472
|
+
end
|
473
|
+
|
474
|
+
@cache[:a] = nil
|
475
|
+
assert_no_size_change do
|
476
|
+
assert_equal true, @cache.key?(:a)
|
477
|
+
assert_equal(nil, (@cache.fetch(:a) {flunk}))
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
def test_fetch_with_return
|
483
|
+
with_or_without_default_proc do
|
484
|
+
r = lambda do
|
485
|
+
@cache.fetch(:a) { return 10 }
|
486
|
+
end.call
|
487
|
+
|
488
|
+
assert_no_size_change do
|
489
|
+
assert_equal 10, r
|
490
|
+
assert_equal false, @cache.key?(:a)
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
def test_clear
|
496
|
+
@cache[:a] = 1
|
497
|
+
assert_size_change -1 do
|
498
|
+
assert_equal @cache, @cache.clear
|
499
|
+
assert_equal false, @cache.key?(:a)
|
500
|
+
assert_equal nil, @cache[:a]
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
def test_each_pair
|
505
|
+
@cache.each_pair {|k, v| flunk}
|
506
|
+
assert_equal(@cache, (@cache.each_pair {}))
|
507
|
+
@cache[:a] = 1
|
508
|
+
|
509
|
+
h = {}
|
510
|
+
@cache.each_pair {|k, v| h[k] = v}
|
511
|
+
assert_equal({:a => 1}, h)
|
512
|
+
|
513
|
+
@cache[:b] = 2
|
514
|
+
h = {}
|
515
|
+
@cache.each_pair {|k, v| h[k] = v}
|
516
|
+
assert_equal({:a => 1, :b => 2}, h)
|
517
|
+
end
|
518
|
+
|
519
|
+
def test_each_pair_iterator
|
520
|
+
@cache[:a] = 1
|
521
|
+
@cache[:b] = 2
|
522
|
+
i = 0
|
523
|
+
r = @cache.each_pair do |k, v|
|
524
|
+
if i == 0
|
525
|
+
i += 1
|
526
|
+
next
|
527
|
+
flunk
|
528
|
+
elsif i == 1
|
529
|
+
break :breaked
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
assert_equal :breaked, r
|
534
|
+
end
|
535
|
+
|
536
|
+
def test_each_pair_allows_modification
|
537
|
+
@cache[:a] = 1
|
538
|
+
@cache[:b] = 1
|
539
|
+
@cache[:c] = 1
|
540
|
+
|
541
|
+
assert_nothing_raised do
|
542
|
+
assert_size_change 1 do
|
543
|
+
@cache.each_pair do |k, v|
|
544
|
+
@cache[:z] = 1
|
545
|
+
end
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
def test_keys
|
551
|
+
assert_equal [], @cache.keys
|
552
|
+
|
553
|
+
@cache[1] = 1
|
554
|
+
assert_equal [1], @cache.keys
|
555
|
+
|
556
|
+
@cache[2] = 2
|
557
|
+
assert_equal [1, 2], @cache.keys.sort
|
558
|
+
end
|
559
|
+
|
560
|
+
def test_values
|
561
|
+
assert_equal [], @cache.values
|
562
|
+
|
563
|
+
@cache[1] = 1
|
564
|
+
assert_equal [1], @cache.values
|
565
|
+
|
566
|
+
@cache[2] = 2
|
567
|
+
assert_equal [1, 2], @cache.values.sort
|
568
|
+
end
|
569
|
+
|
570
|
+
def test_each_key
|
571
|
+
assert_equal(@cache, (@cache.each_key {flunk}))
|
572
|
+
|
573
|
+
@cache[1] = 1
|
574
|
+
arr = []
|
575
|
+
@cache.each_key {|k| arr << k}
|
576
|
+
assert_equal [1], arr
|
577
|
+
|
578
|
+
@cache[2] = 2
|
579
|
+
arr = []
|
580
|
+
@cache.each_key {|k| arr << k}
|
581
|
+
assert_equal [1, 2], arr.sort
|
582
|
+
end
|
583
|
+
|
584
|
+
def test_each_value
|
585
|
+
assert_equal(@cache, (@cache.each_value {flunk}))
|
586
|
+
|
587
|
+
@cache[1] = 1
|
588
|
+
arr = []
|
589
|
+
@cache.each_value {|k| arr << k}
|
590
|
+
assert_equal [1], arr
|
591
|
+
|
592
|
+
@cache[2] = 2
|
593
|
+
arr = []
|
594
|
+
@cache.each_value {|k| arr << k}
|
595
|
+
assert_equal [1, 2], arr.sort
|
596
|
+
end
|
597
|
+
|
598
|
+
def test_empty
|
599
|
+
assert_equal true, @cache.empty?
|
600
|
+
@cache[:a] = 1
|
601
|
+
assert_equal false, @cache.empty?
|
602
|
+
end
|
603
|
+
|
604
|
+
def test_options_validation
|
605
|
+
assert_valid_options(nil)
|
606
|
+
assert_valid_options({})
|
607
|
+
assert_valid_options(:foo => :bar)
|
608
|
+
end
|
609
|
+
|
610
|
+
def test_initial_capacity_options_validation
|
611
|
+
assert_valid_option(:initial_capacity, nil)
|
612
|
+
assert_valid_option(:initial_capacity, 1)
|
613
|
+
assert_invalid_option(:initial_capacity, '')
|
614
|
+
assert_invalid_option(:initial_capacity, 1.0)
|
615
|
+
assert_invalid_option(:initial_capacity, -1)
|
616
|
+
end
|
617
|
+
|
618
|
+
def test_load_factor_options_validation
|
619
|
+
assert_valid_option(:load_factor, nil)
|
620
|
+
assert_valid_option(:load_factor, 0.01)
|
621
|
+
assert_valid_option(:load_factor, 0.75)
|
622
|
+
assert_valid_option(:load_factor, 1)
|
623
|
+
assert_invalid_option(:load_factor, '')
|
624
|
+
assert_invalid_option(:load_factor, 0)
|
625
|
+
assert_invalid_option(:load_factor, 1.1)
|
626
|
+
assert_invalid_option(:load_factor, 2)
|
627
|
+
assert_invalid_option(:load_factor, -1)
|
628
|
+
end
|
629
|
+
|
630
|
+
def test_size
|
631
|
+
assert_equal 0, @cache.size
|
632
|
+
@cache[:a] = 1
|
633
|
+
assert_equal 1, @cache.size
|
634
|
+
@cache[:b] = 1
|
635
|
+
assert_equal 2, @cache.size
|
636
|
+
@cache.delete(:a)
|
637
|
+
assert_equal 1, @cache.size
|
638
|
+
@cache.delete(:b)
|
639
|
+
assert_equal 0, @cache.size
|
640
|
+
end
|
641
|
+
|
642
|
+
def test_get_or_default
|
643
|
+
with_or_without_default_proc do
|
644
|
+
assert_equal 1, @cache.get_or_default(:a, 1)
|
645
|
+
assert_equal nil, @cache.get_or_default(:a, nil)
|
646
|
+
assert_equal false, @cache.get_or_default(:a, false)
|
647
|
+
assert_equal false, @cache.key?(:a)
|
648
|
+
|
649
|
+
@cache[:a] = 1
|
650
|
+
assert_equal 1, @cache.get_or_default(:a, 2)
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
def test_dup_clone
|
655
|
+
[:dup, :clone].each do |meth|
|
656
|
+
cache = cache_with_default_proc(:default_value)
|
657
|
+
cache[:a] = 1
|
658
|
+
dupped = cache.send(meth)
|
659
|
+
assert_equal 1, dupped[:a]
|
660
|
+
assert_equal 1, dupped.size
|
661
|
+
assert_size_change 1, cache do
|
662
|
+
assert_no_size_change dupped do
|
663
|
+
cache[:b] = 1
|
664
|
+
end
|
665
|
+
end
|
666
|
+
assert_equal false, dupped.key?(:b)
|
667
|
+
assert_no_size_change cache do
|
668
|
+
assert_size_change -1, dupped do
|
669
|
+
dupped.delete(:a)
|
670
|
+
end
|
671
|
+
end
|
672
|
+
assert_equal false, dupped.key?(:a)
|
673
|
+
assert_equal true, cache.key?(:a)
|
674
|
+
# test default proc
|
675
|
+
assert_size_change 1, cache do
|
676
|
+
assert_no_size_change dupped do
|
677
|
+
assert_equal :default_value, cache[:c]
|
678
|
+
assert_equal false, dupped.key?(:c)
|
679
|
+
end
|
680
|
+
end
|
681
|
+
assert_no_size_change cache do
|
682
|
+
assert_size_change 1, dupped do
|
683
|
+
assert_equal :default_value, dupped[:d]
|
684
|
+
assert_equal false, cache.key?(:d)
|
685
|
+
end
|
686
|
+
end
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
def test_is_unfreezable
|
691
|
+
assert_raise(NoMethodError) { @cache.freeze }
|
692
|
+
end
|
693
|
+
|
694
|
+
def test_marshal_dump_load
|
695
|
+
assert_nothing_raised do
|
696
|
+
new_cache = Marshal.load(Marshal.dump(@cache))
|
697
|
+
assert_equal 0, new_cache.size
|
698
|
+
end
|
699
|
+
@cache[:a] = 1
|
700
|
+
new_cache = Marshal.load(Marshal.dump(@cache))
|
701
|
+
assert_equal 1, @cache[:a]
|
702
|
+
assert_equal 1, new_cache.size
|
703
|
+
end
|
704
|
+
|
705
|
+
def test_marshal_dump_doesnt_work_with_default_proc
|
706
|
+
assert_raise(TypeError) do
|
707
|
+
Marshal.dump(ThreadSafe::Cache.new {})
|
708
|
+
end
|
709
|
+
end
|
710
|
+
|
711
|
+
private
|
712
|
+
def with_or_without_default_proc
|
713
|
+
yield false
|
714
|
+
@cache = ThreadSafe::Cache.new {|h, k| h[k] = :default_value}
|
715
|
+
yield true
|
716
|
+
end
|
717
|
+
|
718
|
+
def cache_with_default_proc(default_value = 1)
|
719
|
+
ThreadSafe::Cache.new {|cache, k| cache[k] = default_value}
|
720
|
+
end
|
721
|
+
|
722
|
+
def assert_valid_option(option_name, value)
|
723
|
+
assert_valid_options(option_name => value)
|
724
|
+
end
|
725
|
+
|
726
|
+
def assert_valid_options(options)
|
727
|
+
assert_nothing_raised { ThreadSafe::Cache.new(options) }
|
728
|
+
end
|
729
|
+
|
730
|
+
def assert_invalid_option(option_name, value)
|
731
|
+
assert_invalid_options(option_name => value)
|
732
|
+
end
|
733
|
+
|
734
|
+
def assert_invalid_options(options)
|
735
|
+
assert_raise(ArgumentError) { ThreadSafe::Cache.new(options) }
|
736
|
+
end
|
737
|
+
|
738
|
+
def assert_size_change(change, cache = @cache)
|
739
|
+
start = cache.size
|
740
|
+
yield
|
741
|
+
assert_equal change, cache.size - start
|
742
|
+
end
|
743
|
+
|
744
|
+
def assert_no_size_change(cache = @cache, &block)
|
745
|
+
assert_size_change(0, cache, &block)
|
746
|
+
end
|
747
|
+
|
748
|
+
def assert_handles_return_lambda(method, key, *args)
|
749
|
+
before_had_key = @cache.key?(key)
|
750
|
+
before_had_value = before_had_key ? @cache[key] : nil
|
751
|
+
|
752
|
+
returning_lambda = lambda do
|
753
|
+
@cache.send(method, key, *args) { return :direct_return }
|
754
|
+
end
|
755
|
+
|
756
|
+
assert_no_size_change do
|
757
|
+
assert_equal(:direct_return, returning_lambda.call)
|
758
|
+
assert_equal before_had_key, @cache.key?(key)
|
759
|
+
assert_equal before_had_value, @cache[key] if before_had_value
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
class TestException < Exception; end
|
764
|
+
def assert_handles_exception(method, key, *args)
|
765
|
+
before_had_key = @cache.key?(key)
|
766
|
+
before_had_value = before_had_key ? @cache[key] : nil
|
767
|
+
|
768
|
+
assert_no_size_change do
|
769
|
+
assert_raise(TestException) do
|
770
|
+
@cache.send(method, key, *args) { raise TestException, '' }
|
771
|
+
end
|
772
|
+
assert_equal before_had_key, @cache.key?(key)
|
773
|
+
assert_equal before_had_value, @cache[key] if before_had_value
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
777
|
+
def assert_compute(key, expected_old_value, expected_result)
|
778
|
+
result = @cache.compute(:a) do |old_value|
|
779
|
+
assert_equal expected_old_value, old_value
|
780
|
+
yield
|
781
|
+
end
|
782
|
+
assert_equal expected_result, result
|
783
|
+
end
|
784
|
+
|
785
|
+
def assert_merge_pair(key, value, expected_old_value, expected_result)
|
786
|
+
result = @cache.merge_pair(key, value) do |old_value|
|
787
|
+
assert_equal expected_old_value, old_value
|
788
|
+
yield
|
789
|
+
end
|
790
|
+
assert_equal expected_result, result
|
791
|
+
end
|
792
|
+
end
|