thread_safe 0.3.5 → 0.3.6

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