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,453 @@
1
+ require 'thread'
2
+ require 'test/unit'
3
+ require 'thread_safe'
4
+ require File.join(File.dirname(__FILE__), "test_helper")
5
+
6
+ Thread.abort_on_exception = true
7
+
8
+ class TestCacheTorture < Test::Unit::TestCase
9
+ THREAD_COUNT = 40
10
+ KEY_COUNT = (((2**13) - 2) * 0.75).to_i # get close to the doubling cliff
11
+ LOW_KEY_COUNT = (((2**8 ) - 2) * 0.75).to_i # get close to the doubling cliff
12
+
13
+ INITIAL_VALUE_CACHE_SETUP = lambda do |options, keys|
14
+ cache = ThreadSafe::Cache.new
15
+ initial_value = options[:initial_value] || 0
16
+ keys.each {|key| cache[key] = initial_value}
17
+ cache
18
+ end
19
+ ZERO_VALUE_CACHE_SETUP = lambda do |options, keys|
20
+ INITIAL_VALUE_CACHE_SETUP.call(options.merge(:initial_value => 0), keys)
21
+ end
22
+
23
+ DEFAULTS = {
24
+ :key_count => KEY_COUNT,
25
+ :thread_count => THREAD_COUNT,
26
+ :loop_count => 1,
27
+ :prelude => '',
28
+ :cache_setup => lambda {|options, keys| ThreadSafe::Cache.new}
29
+ }
30
+
31
+ LOW_KEY_COUNT_OPTIONS = {:loop_count => 150, :key_count => LOW_KEY_COUNT}
32
+ SINGLE_KEY_COUNT_OPTIONS = {:loop_count => 100_000, :key_count => 1}
33
+
34
+ def test_concurrency
35
+ code = <<-RUBY_EVAL
36
+ cache[key]
37
+ cache[key] = key
38
+ cache[key]
39
+ cache.delete(key)
40
+ RUBY_EVAL
41
+ do_thread_loop(:concurrency, code)
42
+ end
43
+
44
+ def test_put_if_absent
45
+ do_thread_loop(:put_if_absent, 'acc += 1 unless cache.put_if_absent(key, key)', :key_count => 100_000) do |result, cache, options, keys|
46
+ assert_standard_accumulator_test_result(result, cache, options, keys)
47
+ end
48
+ end
49
+
50
+ def test_compute_if_absent
51
+ code = 'cache.compute_if_absent(key) { acc += 1; key }'
52
+ do_thread_loop(:compute_if_absent, code) do |result, cache, options, keys|
53
+ assert_standard_accumulator_test_result(result, cache, options, keys)
54
+ end
55
+ end
56
+
57
+ def test_compute_put_if_absent
58
+ code = <<-RUBY_EVAL
59
+ if key.even?
60
+ cache.compute_if_absent(key) { acc += 1; key }
61
+ else
62
+ acc += 1 unless cache.put_if_absent(key, key)
63
+ end
64
+ RUBY_EVAL
65
+ do_thread_loop(:compute_put_if_absent, code) do |result, cache, options, keys|
66
+ assert_standard_accumulator_test_result(result, cache, options, keys)
67
+ end
68
+ end
69
+
70
+ def test_compute_if_absent_and_present
71
+ compute_if_absent_and_present
72
+ compute_if_absent_and_present(LOW_KEY_COUNT_OPTIONS)
73
+ compute_if_absent_and_present(SINGLE_KEY_COUNT_OPTIONS)
74
+ end
75
+
76
+ def test_add_remove_to_zero
77
+ add_remove_to_zero
78
+ add_remove_to_zero(LOW_KEY_COUNT_OPTIONS)
79
+ add_remove_to_zero(SINGLE_KEY_COUNT_OPTIONS)
80
+ end
81
+
82
+ def test_add_remove_to_zero_via_merge_pair
83
+ add_remove_to_zero_via_merge_pair
84
+ add_remove_to_zero_via_merge_pair(LOW_KEY_COUNT_OPTIONS)
85
+ add_remove_to_zero_via_merge_pair(SINGLE_KEY_COUNT_OPTIONS)
86
+ end
87
+
88
+ def test_add_remove
89
+ add_remove
90
+ add_remove(LOW_KEY_COUNT_OPTIONS)
91
+ add_remove(SINGLE_KEY_COUNT_OPTIONS)
92
+ end
93
+
94
+ def test_add_remove_via_compute
95
+ add_remove_via_compute
96
+ add_remove_via_compute(LOW_KEY_COUNT_OPTIONS)
97
+ add_remove_via_compute(SINGLE_KEY_COUNT_OPTIONS)
98
+ end
99
+
100
+ def add_remove_via_compute_if_absent_present
101
+ add_remove_via_compute_if_absent_present
102
+ add_remove_via_compute_if_absent_present(LOW_KEY_COUNT_OPTIONS)
103
+ add_remove_via_compute_if_absent_present(SINGLE_KEY_COUNT_OPTIONS)
104
+ end
105
+
106
+ def test_add_remove_indiscriminate
107
+ add_remove_indiscriminate
108
+ add_remove_indiscriminate(LOW_KEY_COUNT_OPTIONS)
109
+ add_remove_indiscriminate(SINGLE_KEY_COUNT_OPTIONS)
110
+ end
111
+
112
+ def test_count_up
113
+ count_up
114
+ count_up(LOW_KEY_COUNT_OPTIONS)
115
+ count_up(SINGLE_KEY_COUNT_OPTIONS)
116
+ end
117
+
118
+ def test_count_up_via_compute
119
+ count_up_via_compute
120
+ count_up_via_compute(LOW_KEY_COUNT_OPTIONS)
121
+ count_up_via_compute(SINGLE_KEY_COUNT_OPTIONS)
122
+ end
123
+
124
+ def test_count_up_via_merge_pair
125
+ count_up_via_merge_pair
126
+ count_up_via_merge_pair(LOW_KEY_COUNT_OPTIONS)
127
+ count_up_via_merge_pair(SINGLE_KEY_COUNT_OPTIONS)
128
+ end
129
+
130
+ def test_count_race
131
+ prelude = 'change = (rand(2) == 1) ? 1 : -1'
132
+ code = <<-RUBY_EVAL
133
+ v = cache[key]
134
+ acc += change if cache.replace_pair(key, v, v + change)
135
+ RUBY_EVAL
136
+ do_thread_loop(:count_race, code, :loop_count => 5, :prelude => prelude, :cache_setup => ZERO_VALUE_CACHE_SETUP) do |result, cache, options, keys|
137
+ result_sum = sum(result)
138
+ assert_equal(sum(keys.map {|key| cache[key]}), result_sum)
139
+ assert_equal(sum(cache.values), result_sum)
140
+ assert_equal(options[:key_count], cache.size)
141
+ end
142
+ end
143
+
144
+ def test_get_and_set_new
145
+ code = 'acc += 1 unless cache.get_and_set(key, key)'
146
+ do_thread_loop(:get_and_set_new, code) do |result, cache, options, keys|
147
+ assert_standard_accumulator_test_result(result, cache, options, keys)
148
+ end
149
+ end
150
+
151
+ def test_get_and_set_existing
152
+ code = 'acc += 1 if cache.get_and_set(key, key) == -1'
153
+ do_thread_loop(:get_and_set_existing, code, :cache_setup => INITIAL_VALUE_CACHE_SETUP, :initial_value => -1) do |result, cache, options, keys|
154
+ assert_standard_accumulator_test_result(result, cache, options, keys)
155
+ end
156
+ end
157
+
158
+ private
159
+ def compute_if_absent_and_present(opts = {})
160
+ prelude = 'on_present = rand(2) == 1'
161
+ code = <<-RUBY_EVAL
162
+ if on_present
163
+ cache.compute_if_present(key) {|old_value| acc += 1; old_value + 1}
164
+ else
165
+ cache.compute_if_absent(key) { acc += 1; 1 }
166
+ end
167
+ RUBY_EVAL
168
+ do_thread_loop(:compute_if_absent_and_present, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
169
+ stored_sum = 0
170
+ stored_key_count = 0
171
+ keys.each do |k|
172
+ if value = cache[k]
173
+ stored_sum += value
174
+ stored_key_count += 1
175
+ end
176
+ end
177
+ assert_equal(stored_sum, sum(result))
178
+ assert_equal(stored_key_count, cache.size)
179
+ end
180
+ end
181
+
182
+ def add_remove(opts = {})
183
+ prelude = 'do_add = rand(2) == 1'
184
+ code = <<-RUBY_EVAL
185
+ if do_add
186
+ acc += 1 unless cache.put_if_absent(key, key)
187
+ else
188
+ acc -= 1 if cache.delete_pair(key, key)
189
+ end
190
+ RUBY_EVAL
191
+ do_thread_loop(:add_remove, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
192
+ assert_all_key_mappings_exist(cache, keys, false)
193
+ assert_equal(cache.size, sum(result))
194
+ end
195
+ end
196
+
197
+ def add_remove_via_compute(opts = {})
198
+ prelude = 'do_add = rand(2) == 1'
199
+ code = <<-RUBY_EVAL
200
+ cache.compute(key) do |old_value|
201
+ if do_add
202
+ acc += 1 unless old_value
203
+ key
204
+ else
205
+ acc -= 1 if old_value
206
+ nil
207
+ end
208
+ end
209
+ RUBY_EVAL
210
+ do_thread_loop(:add_remove_via_compute, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
211
+ assert_all_key_mappings_exist(cache, keys, false)
212
+ assert_equal(cache.size, sum(result))
213
+ end
214
+ end
215
+
216
+ def add_remove_via_compute_if_absent_present(opts = {})
217
+ prelude = 'do_add = rand(2) == 1'
218
+ code = <<-RUBY_EVAL
219
+ if do_add
220
+ cache.compute_if_absent(key) { acc += 1; key }
221
+ else
222
+ cache.compute_if_present(key) { acc -= 1; nil }
223
+ end
224
+ RUBY_EVAL
225
+ do_thread_loop(:add_remove_via_compute_if_absent_present, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
226
+ assert_all_key_mappings_exist(cache, keys, false)
227
+ assert_equal(cache.size, sum(result))
228
+ end
229
+ end
230
+
231
+ def add_remove_indiscriminate(opts = {})
232
+ prelude = 'do_add = rand(2) == 1'
233
+ code = <<-RUBY_EVAL
234
+ if do_add
235
+ acc += 1 unless cache.put_if_absent(key, key)
236
+ else
237
+ acc -= 1 if cache.delete(key)
238
+ end
239
+ RUBY_EVAL
240
+ do_thread_loop(:add_remove, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
241
+ assert_all_key_mappings_exist(cache, keys, false)
242
+ assert_equal(cache.size, sum(result))
243
+ end
244
+ end
245
+
246
+ def count_up(opts = {})
247
+ code = <<-RUBY_EVAL
248
+ v = cache[key]
249
+ acc += 1 if cache.replace_pair(key, v, v + 1)
250
+ RUBY_EVAL
251
+ do_thread_loop(:count_up, code, {:loop_count => 5, :cache_setup => ZERO_VALUE_CACHE_SETUP}.merge(opts)) do |result, cache, options, keys|
252
+ assert_count_up(result, cache, options, keys)
253
+ end
254
+ end
255
+
256
+ def count_up_via_compute(opts = {})
257
+ code = <<-RUBY_EVAL
258
+ cache.compute(key) do |old_value|
259
+ acc += 1
260
+ old_value ? old_value + 1 : 1
261
+ end
262
+ RUBY_EVAL
263
+ do_thread_loop(:count_up_via_compute, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
264
+ assert_count_up(result, cache, options, keys)
265
+ result.inject(nil) do |previous_value, next_value| # since compute guarantees atomicity all count ups should be equal
266
+ assert_equal previous_value, next_value if previous_value
267
+ next_value
268
+ end
269
+ end
270
+ end
271
+
272
+ def count_up_via_merge_pair(opts = {})
273
+ code = <<-RUBY_EVAL
274
+ cache.merge_pair(key, 1) {|old_value| old_value + 1}
275
+ RUBY_EVAL
276
+ do_thread_loop(:count_up_via_merge_pair, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
277
+ all_match = true
278
+ expected_value = options[:loop_count] * options[:thread_count]
279
+ keys.each do |key|
280
+ if expected_value != (value = cache[key])
281
+ all_match = false
282
+ break
283
+ end
284
+ end
285
+ assert all_match
286
+ end
287
+ end
288
+
289
+ def add_remove_to_zero(opts = {})
290
+ code = <<-RUBY_EVAL
291
+ acc += 1 unless cache.put_if_absent(key, key)
292
+ acc -= 1 if cache.delete_pair(key, key)
293
+ RUBY_EVAL
294
+ do_thread_loop(:add_remove_to_zero, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
295
+ assert_all_key_mappings_exist(cache, keys, false)
296
+ assert_equal(cache.size, sum(result))
297
+ end
298
+ end
299
+
300
+ def add_remove_to_zero_via_merge_pair(opts = {})
301
+ code = <<-RUBY_EVAL
302
+ acc += (cache.merge_pair(key, key) {}) ? -1 : 1
303
+ RUBY_EVAL
304
+ do_thread_loop(:add_remove_to_zero_via_merge_pair, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
305
+ assert_all_key_mappings_exist(cache, keys, false)
306
+ assert_equal(cache.size, sum(result))
307
+ end
308
+ end
309
+
310
+ def do_thread_loop(name, code, options = {}, &block)
311
+ options = DEFAULTS.merge(options)
312
+ meth = define_loop name, code, options[:prelude]
313
+ assert_nothing_raised do
314
+ keys = to_keys_array(options[:key_count])
315
+ run_thread_loop(meth, keys, options, &block)
316
+
317
+ if options[:key_count] > 1
318
+ options[:key_count] = (options[:key_count] / 40).to_i
319
+ keys = to_hash_collision_keys_array(options[:key_count])
320
+ run_thread_loop(meth, keys, options.merge(:loop_count => (options[:loop_count] * 5)), &block)
321
+ end
322
+ end
323
+ end
324
+
325
+ def run_thread_loop(meth, keys, options)
326
+ cache = options[:cache_setup].call(options, keys)
327
+ barrier = ThreadSafe::Test::Barrier.new(options[:thread_count])
328
+ t = Time.now
329
+ result = (1..options[:thread_count]).map do
330
+ Thread.new do
331
+ setup_sync_and_start_loop(meth, cache, keys, barrier, options[:loop_count])
332
+ end
333
+ end.map(&:value)
334
+ yield result, cache, options, keys if block_given?
335
+ end
336
+
337
+ def setup_sync_and_start_loop(meth, cache, keys, barrier, loop_count)
338
+ my_keys = keys.shuffle
339
+ barrier.await
340
+ if my_keys.size == 1
341
+ key = my_keys.first
342
+ send("#{meth}_single_key", cache, key, loop_count)
343
+ else
344
+ send("#{meth}_multiple_keys", cache, my_keys, loop_count)
345
+ end
346
+ end
347
+
348
+ def define_loop(name, body, prelude)
349
+ inner_meth_name = :"_#{name}_loop_inner"
350
+ outer_meth_name = :"_#{name}_loop_outer"
351
+ # looping is splitted into the "loop methods" to trigger the JIT
352
+ self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
353
+ def #{inner_meth_name}_multiple_keys(cache, keys, i, length, acc)
354
+ #{prelude}
355
+ target = i + length
356
+ while i < target
357
+ key = keys[i]
358
+ #{body}
359
+ i += 1
360
+ end
361
+ acc
362
+ end unless method_defined?(:#{inner_meth_name}_multiple_keys)
363
+ RUBY_EVAL
364
+
365
+ self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
366
+ def #{inner_meth_name}_single_key(cache, key, i, length, acc)
367
+ #{prelude}
368
+ target = i + length
369
+ while i < target
370
+ #{body}
371
+ i += 1
372
+ end
373
+ acc
374
+ end unless method_defined?(:#{inner_meth_name}_single_key)
375
+ RUBY_EVAL
376
+
377
+ self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
378
+ def #{outer_meth_name}_multiple_keys(cache, keys, loop_count)
379
+ total_length = keys.size
380
+ acc = 0
381
+ inc = 100
382
+ loop_count.times do
383
+ i = 0
384
+ pre_loop_inc = total_length % inc
385
+ acc = #{inner_meth_name}_multiple_keys(cache, keys, i, pre_loop_inc, acc)
386
+ i += pre_loop_inc
387
+ while i < total_length
388
+ acc = #{inner_meth_name}_multiple_keys(cache, keys, i, inc, acc)
389
+ i += inc
390
+ end
391
+ end
392
+ acc
393
+ end unless method_defined?(:#{outer_meth_name}_multiple_keys)
394
+ RUBY_EVAL
395
+
396
+ self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
397
+ def #{outer_meth_name}_single_key(cache, key, loop_count)
398
+ acc = 0
399
+ i = 0
400
+ inc = 100
401
+
402
+ pre_loop_inc = loop_count % inc
403
+ acc = #{inner_meth_name}_single_key(cache, key, i, pre_loop_inc, acc)
404
+ i += pre_loop_inc
405
+
406
+ while i < loop_count
407
+ acc = #{inner_meth_name}_single_key(cache, key, i, inc, acc)
408
+ i += inc
409
+ end
410
+ acc
411
+ end unless method_defined?(:#{outer_meth_name}_single_key)
412
+ RUBY_EVAL
413
+ outer_meth_name
414
+ end
415
+
416
+ def to_keys_array(key_count)
417
+ arr = []
418
+ key_count.times {|i| arr << i}
419
+ arr
420
+ end
421
+
422
+ def to_hash_collision_keys_array(key_count)
423
+ to_keys_array(key_count).map {|key| ThreadSafe::Test::HashCollisionKey(key)}
424
+ end
425
+
426
+ def sum(result)
427
+ result.inject(0) {|acc, i| acc + i}
428
+ end
429
+
430
+ def assert_standard_accumulator_test_result(result, cache, options, keys)
431
+ assert_all_key_mappings_exist(cache, keys)
432
+ assert_equal(options[:key_count], sum(result))
433
+ assert_equal(options[:key_count], cache.size)
434
+ end
435
+
436
+ def assert_all_key_mappings_exist(cache, keys, all_must_exist = true)
437
+ keys.each do |key|
438
+ if (value = cache[key]) || all_must_exist
439
+ assert_equal key, value unless key == value # don't do a bazzilion assertions unless necessary
440
+ end
441
+ end
442
+ end
443
+
444
+ def assert_count_up(result, cache, options, keys)
445
+ keys.each do |key|
446
+ unless value = cache[key]
447
+ assert value
448
+ end
449
+ end
450
+ assert_equal(sum(cache.values), sum(result))
451
+ assert_equal(options[:key_count], cache.size)
452
+ end
453
+ end