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