thread_safe-ianunruh 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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 +106 -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