thread_safe 0.1.3 → 0.2.0

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +24 -0
  3. data/README.md +19 -5
  4. data/Rakefile +13 -6
  5. data/examples/bench_cache.rb +1 -1
  6. data/ext/org/jruby/ext/thread_safe/JRubyCacheBackendLibrary.java +54 -15
  7. data/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMap.java +28 -0
  8. data/ext/org/jruby/ext/thread_safe/jsr166e/ConcurrentHashMapV8.java +19 -10
  9. data/ext/org/jruby/ext/thread_safe/jsr166e/LongAdder.java +1 -2
  10. data/ext/org/jruby/ext/thread_safe/jsr166e/Striped64.java +1 -1
  11. data/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/ConcurrentHashMapV8.java +3788 -0
  12. data/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/LongAdder.java +204 -0
  13. data/ext/org/jruby/ext/thread_safe/jsr166e/nounsafe/Striped64.java +291 -0
  14. data/ext/org/jruby/ext/thread_safe/jsr166y/ThreadLocalRandom.java +1 -1
  15. data/ext/thread_safe/JrubyCacheBackendService.java +2 -2
  16. data/lib/thread_safe.rb +1 -1
  17. data/lib/thread_safe/atomic_reference_cache_backend.rb +1 -1
  18. data/lib/thread_safe/cache.rb +6 -3
  19. data/lib/thread_safe/mri_cache_backend.rb +2 -2
  20. data/lib/thread_safe/non_concurrent_cache_backend.rb +1 -1
  21. data/lib/thread_safe/synchronized_cache_backend.rb +1 -1
  22. data/lib/thread_safe/synchronized_delegator.rb +36 -19
  23. data/lib/thread_safe/util.rb +1 -1
  24. data/lib/thread_safe/util/adder.rb +1 -1
  25. data/lib/thread_safe/util/atomic_reference.rb +1 -1
  26. data/lib/thread_safe/util/cheap_lockable.rb +1 -1
  27. data/lib/thread_safe/util/power_of_two_tuple.rb +1 -1
  28. data/lib/thread_safe/util/striped64.rb +1 -1
  29. data/lib/thread_safe/util/volatile.rb +1 -1
  30. data/lib/thread_safe/util/volatile_tuple.rb +1 -1
  31. data/lib/thread_safe/util/xor_shift_random.rb +1 -1
  32. data/lib/thread_safe/version.rb +1 -1
  33. data/test/src/thread_safe/SecurityManager.java +21 -0
  34. data/test/test_array.rb +1 -1
  35. data/test/test_cache.rb +27 -10
  36. data/test/test_cache_loops.rb +377 -376
  37. data/test/test_hash.rb +1 -2
  38. data/test/test_helper.rb +33 -3
  39. data/test/test_synchronized_delegator.rb +67 -17
  40. data/thread_safe.gemspec +6 -3
  41. metadata +36 -10
@@ -196,4 +196,4 @@ public class ThreadLocalRandom extends Random {
196
196
  }
197
197
 
198
198
  private static final long serialVersionUID = -5851777807851030925L;
199
- }
199
+ }
@@ -1,7 +1,7 @@
1
1
  package thread_safe;
2
2
 
3
3
  import java.io.IOException;
4
-
4
+
5
5
  import org.jruby.Ruby;
6
6
  import org.jruby.ext.thread_safe.JRubyCacheBackendLibrary;
7
7
  import org.jruby.runtime.load.BasicLibraryService;
@@ -12,4 +12,4 @@ public class JrubyCacheBackendService implements BasicLibraryService {
12
12
  new JRubyCacheBackendLibrary().load(runtime, false);
13
13
  return true;
14
14
  }
15
- }
15
+ }
@@ -28,7 +28,7 @@ module ThreadSafe
28
28
  class Hash < ::Hash
29
29
  include JRuby::Synchronized
30
30
  end
31
- elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
31
+ elsif !defined?(RUBY_ENGINE) || RUBY_ENGINE == 'ruby'
32
32
  # Because MRI never runs code in parallel, the existing
33
33
  # non-thread-safe structures should usually work fine.
34
34
  Array = ::Array
@@ -919,4 +919,4 @@ module ThreadSafe
919
919
  @counter.add(-by)
920
920
  end
921
921
  end
922
- end
922
+ end
@@ -7,8 +7,8 @@ module ThreadSafe
7
7
  autoload :AtomicReferenceCacheBackend, 'thread_safe/atomic_reference_cache_backend'
8
8
  autoload :SynchronizedCacheBackend, 'thread_safe/synchronized_cache_backend'
9
9
 
10
- ConcurrentCacheBackend =
11
- case defined?(RUBY_ENGINE) && RUBY_ENGINE
10
+ ConcurrentCacheBackend = if defined?(RUBY_ENGINE)
11
+ case RUBY_ENGINE
12
12
  when 'jruby'; JRubyCacheBackend
13
13
  when 'ruby'; MriCacheBackend
14
14
  when 'rbx'; AtomicReferenceCacheBackend
@@ -16,6 +16,9 @@ module ThreadSafe
16
16
  warn 'ThreadSafe: unsupported Ruby engine, using a fully synchronized ThreadSafe::Cache implementation' if $VERBOSE
17
17
  SynchronizedCacheBackend
18
18
  end
19
+ else
20
+ MriCacheBackend
21
+ end
19
22
 
20
23
  class Cache < ConcurrentCacheBackend
21
24
  KEY_ERROR = defined?(KeyError) ? KeyError : IndexError # there is no KeyError in 1.8 mode
@@ -137,4 +140,4 @@ module ThreadSafe
137
140
  end
138
141
  end
139
142
  end
140
- end
143
+ end
@@ -4,7 +4,7 @@ module ThreadSafe
4
4
  #
5
5
  # The previous implementation used `Thread.critical` on 1.8 MRI to implement the 4 composed atomic operations (`put_if_absent`, `replace_pair`,
6
6
  # `replace_if_exists`, `delete_pair`) this however doesn't work for `compute_if_absent` because on 1.8 the Mutex class is itself implemented
7
- # via `Thread.critical` and a call to `Mutex#lock` does not restore the previous `Thread.critical` value (thus any synchronisation clears the
7
+ # via `Thread.critical` and a call to `Mutex#lock` does not restore the previous `Thread.critical` value (thus any synchronisation clears the
8
8
  # `Thread.critical` flag and we loose control). This poses a problem as the provided block might use synchronisation on its own.
9
9
  #
10
10
  # NOTE: a neat idea of writing a c-ext to manually perform atomic put_if_absent, while relying on Ruby not releasing a GVL while calling
@@ -59,4 +59,4 @@ module ThreadSafe
59
59
  WRITE_LOCK.synchronize { super }
60
60
  end
61
61
  end
62
- end
62
+ end
@@ -130,4 +130,4 @@ module ThreadSafe
130
130
  end
131
131
  end
132
132
  end
133
- end
133
+ end
@@ -73,4 +73,4 @@ module ThreadSafe
73
73
  synchronize { super }
74
74
  end
75
75
  end
76
- end
76
+ end
@@ -1,35 +1,52 @@
1
+ require 'delegate'
2
+ require 'monitor'
3
+
1
4
  # This class provides a trivial way to synchronize all calls to a given object
2
- # by wrapping it with a Delegator that performs Mutex#lock/unlock calls around
3
- # the delegated #send. Example:
5
+ # by wrapping it with a `Delegator` that performs `Monitor#enter/exit` calls
6
+ # around the delegated `#send`. Example:
4
7
  #
5
8
  # array = [] # not thread-safe on many impls
6
- # array = MutexedDelegator.new(array) # thread-safe
9
+ # array = SynchronizedDelegator.new([]) # thread-safe
7
10
  #
8
- # A simple Mutex provides a very coarse-grained way to synchronize a given
11
+ # A simple `Monitor` provides a very coarse-grained way to synchronize a given
9
12
  # object, in that it will cause synchronization for methods that have no
10
13
  # need for it, but this is a trivial way to get thread-safety where none may
11
14
  # exist currently on some implementations.
12
15
  #
13
16
  # This class is currently being considered for inclusion into stdlib, via
14
17
  # https://bugs.ruby-lang.org/issues/8556
18
+ class SynchronizedDelegator < SimpleDelegator
15
19
 
16
- require 'delegate'
20
+ def initialize(obj)
21
+ __setobj__(obj)
22
+ @monitor = Monitor.new
23
+ end
17
24
 
18
- unless defined?(SynchronizedDelegator)
19
- class SynchronizedDelegator < SimpleDelegator
20
- def initialize(*)
25
+ def method_missing(method, *args, &block)
26
+ monitor = @monitor
27
+ begin
28
+ monitor.enter
21
29
  super
22
- @mutex = Mutex.new
30
+ ensure
31
+ monitor.exit
23
32
  end
24
-
25
- def method_missing(m, *args, &block)
26
- begin
27
- mutex = @mutex
28
- mutex.lock
29
- super
30
- ensure
31
- mutex.unlock
33
+ end
34
+
35
+ # Work-around for 1.8 std-lib not passing block around to delegate.
36
+ # @private
37
+ def method_missing(method, *args, &block)
38
+ monitor = @monitor
39
+ begin
40
+ monitor.enter
41
+ target = self.__getobj__
42
+ if target.respond_to?(method)
43
+ target.__send__(method, *args, &block)
44
+ else
45
+ super(method, *args, &block)
32
46
  end
47
+ ensure
48
+ monitor.exit
33
49
  end
34
- end
35
- end
50
+ end if RUBY_VERSION[0, 3] == '1.8'
51
+
52
+ end unless defined?(SynchronizedDelegator)
@@ -13,4 +13,4 @@ module ThreadSafe
13
13
  autoload :VolatileTuple, 'thread_safe/util/volatile_tuple'
14
14
  autoload :XorShiftRandom, 'thread_safe/util/xor_shift_random'
15
15
  end
16
- end
16
+ end
@@ -56,4 +56,4 @@ module ThreadSafe
56
56
  end
57
57
  end
58
58
  end
59
- end
59
+ end
@@ -9,4 +9,4 @@ module ThreadSafe
9
9
  defined?(Atomic::InternalReference) ? Atomic::InternalReference : Atomic
10
10
  end
11
11
  end
12
- end
12
+ end
@@ -102,4 +102,4 @@ module ThreadSafe
102
102
  end
103
103
  end
104
104
  end
105
- end
105
+ end
@@ -23,4 +23,4 @@ module ThreadSafe
23
23
  end
24
24
  end
25
25
  end
26
- end
26
+ end
@@ -223,4 +223,4 @@ module ThreadSafe
223
223
  end
224
224
  end
225
225
  end
226
- end
226
+ end
@@ -59,4 +59,4 @@ module ThreadSafe
59
59
  end
60
60
  end
61
61
  end
62
- end
62
+ end
@@ -43,4 +43,4 @@ module ThreadSafe
43
43
  end
44
44
  end
45
45
  end
46
- end
46
+ end
@@ -36,4 +36,4 @@ module ThreadSafe
36
36
  end
37
37
  end
38
38
  end
39
- end
39
+ end
@@ -1,3 +1,3 @@
1
1
  module Threadsafe
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,21 @@
1
+ package thread_safe;
2
+
3
+ import java.security.Permission;
4
+ import java.util.ArrayList;
5
+ import java.util.List;
6
+
7
+ public class SecurityManager extends java.lang.SecurityManager {
8
+ private final List<Permission> deniedPermissions =
9
+ new ArrayList<Permission>();
10
+
11
+ @Override
12
+ public void checkPermission(Permission p) {
13
+ if (deniedPermissions.contains(p)) {
14
+ throw new SecurityException("Denied!");
15
+ }
16
+ }
17
+
18
+ public void deny(Permission p) {
19
+ deniedPermissions.add(p);
20
+ }
21
+ }
@@ -17,4 +17,4 @@ class TestArray < Test::Unit::TestCase
17
17
  end.map(&:join)
18
18
  end
19
19
  end
20
- end
20
+ end
@@ -290,17 +290,21 @@ class TestCache < Test::Unit::TestCase
290
290
  end
291
291
 
292
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
293
+ assert_collision_resistance((0..1000).map {|i| ThreadSafe::Test::HashCollisionKey(i, 1)})
294
+ end
295
+
296
+ def test_collision_resistance_with_arrays
297
+ special_array_class = Class.new(Array) do
298
+ def key # assert_collision_resistance expects to be able to call .key to get the "real" key
299
+ first.key
301
300
  end
302
301
  end
303
- assert(keys.all? {|k| @cache[k] == k.key})
302
+ # Test collision resistance with a keys that say they responds_to <=>, but then raise exceptions
303
+ # when actually called (ie: an Array filled with non-comparable keys).
304
+ # See https://github.com/headius/thread_safe/issues/19 for more info.
305
+ assert_collision_resistance((0..100).map do |i|
306
+ special_array_class.new([ThreadSafe::Test::HashCollisionKeyNonComparable.new(i, 1)])
307
+ end)
304
308
  end
305
309
 
306
310
  def test_replace_pair
@@ -791,4 +795,17 @@ class TestCache < Test::Unit::TestCase
791
795
  end
792
796
  assert_equal expected_result, result
793
797
  end
794
- end
798
+
799
+ def assert_collision_resistance(keys)
800
+ keys.each {|k| @cache[k] = k.key}
801
+ 10.times do |i|
802
+ size = keys.size
803
+ while i < size
804
+ k = keys[i]
805
+ assert(k.key == @cache.delete(k) && !@cache.key?(k) && (@cache[k] = k.key; @cache[k] == k.key))
806
+ i += 10
807
+ end
808
+ end
809
+ assert(keys.all? {|k| @cache[k] == k.key})
810
+ end
811
+ end
@@ -5,449 +5,450 @@ require File.join(File.dirname(__FILE__), "test_helper")
5
5
 
6
6
  Thread.abort_on_exception = true
7
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)
8
+ unless RUBY_VERSION =~ /1\.8/ || ENV['TRAVIS']
9
+ class TestCacheTorture < Test::Unit::TestCase
10
+ THREAD_COUNT = 40
11
+ KEY_COUNT = (((2**13) - 2) * 0.75).to_i # get close to the doubling cliff
12
+ LOW_KEY_COUNT = (((2**8 ) - 2) * 0.75).to_i # get close to the doubling cliff
13
+
14
+ INITIAL_VALUE_CACHE_SETUP = lambda do |options, keys|
15
+ cache = ThreadSafe::Cache.new
16
+ initial_value = options[:initial_value] || 0
17
+ keys.each {|key| cache[key] = initial_value}
18
+ cache
19
+ end
20
+ ZERO_VALUE_CACHE_SETUP = lambda do |options, keys|
21
+ INITIAL_VALUE_CACHE_SETUP.call(options.merge(:initial_value => 0), keys)
47
22
  end
48
- end
49
23
 
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)
24
+ DEFAULTS = {
25
+ :key_count => KEY_COUNT,
26
+ :thread_count => THREAD_COUNT,
27
+ :loop_count => 1,
28
+ :prelude => '',
29
+ :cache_setup => lambda {|options, keys| ThreadSafe::Cache.new}
30
+ }
31
+
32
+ LOW_KEY_COUNT_OPTIONS = {:loop_count => 150, :key_count => LOW_KEY_COUNT}
33
+ SINGLE_KEY_COUNT_OPTIONS = {:loop_count => 100_000, :key_count => 1}
34
+
35
+ def test_concurrency
36
+ code = <<-RUBY_EVAL
37
+ cache[key]
38
+ cache[key] = key
39
+ cache[key]
40
+ cache.delete(key)
41
+ RUBY_EVAL
42
+ do_thread_loop(:concurrency, code)
54
43
  end
55
- end
56
44
 
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)
45
+ def test_put_if_absent
46
+ do_thread_loop(:put_if_absent, 'acc += 1 unless cache.put_if_absent(key, key)', :key_count => 100_000) do |result, cache, options, keys|
47
+ assert_standard_accumulator_test_result(result, cache, options, keys)
63
48
  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
49
  end
68
- end
69
50
 
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
51
+ def test_compute_if_absent
52
+ code = 'cache.compute_if_absent(key) { acc += 1; key }'
53
+ do_thread_loop(:compute_if_absent, code) do |result, cache, options, keys|
54
+ assert_standard_accumulator_test_result(result, cache, options, keys)
55
+ end
56
+ end
81
57
 
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
58
+ def test_compute_put_if_absent
59
+ code = <<-RUBY_EVAL
60
+ if key.even?
61
+ cache.compute_if_absent(key) { acc += 1; key }
62
+ else
63
+ acc += 1 unless cache.put_if_absent(key, key)
64
+ end
65
+ RUBY_EVAL
66
+ do_thread_loop(:compute_put_if_absent, code) do |result, cache, options, keys|
67
+ assert_standard_accumulator_test_result(result, cache, options, keys)
68
+ end
69
+ end
87
70
 
88
- def test_add_remove
89
- add_remove
90
- add_remove(LOW_KEY_COUNT_OPTIONS)
91
- add_remove(SINGLE_KEY_COUNT_OPTIONS)
92
- end
71
+ def test_compute_if_absent_and_present
72
+ compute_if_absent_and_present
73
+ compute_if_absent_and_present(LOW_KEY_COUNT_OPTIONS)
74
+ compute_if_absent_and_present(SINGLE_KEY_COUNT_OPTIONS)
75
+ end
93
76
 
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
77
+ def test_add_remove_to_zero
78
+ add_remove_to_zero
79
+ add_remove_to_zero(LOW_KEY_COUNT_OPTIONS)
80
+ add_remove_to_zero(SINGLE_KEY_COUNT_OPTIONS)
81
+ end
99
82
 
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
83
+ def test_add_remove_to_zero_via_merge_pair
84
+ add_remove_to_zero_via_merge_pair
85
+ add_remove_to_zero_via_merge_pair(LOW_KEY_COUNT_OPTIONS)
86
+ add_remove_to_zero_via_merge_pair(SINGLE_KEY_COUNT_OPTIONS)
87
+ end
105
88
 
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
89
+ def test_add_remove
90
+ add_remove
91
+ add_remove(LOW_KEY_COUNT_OPTIONS)
92
+ add_remove(SINGLE_KEY_COUNT_OPTIONS)
93
+ end
111
94
 
112
- def test_count_up
113
- count_up
114
- count_up(LOW_KEY_COUNT_OPTIONS)
115
- count_up(SINGLE_KEY_COUNT_OPTIONS)
116
- end
95
+ def test_add_remove_via_compute
96
+ add_remove_via_compute
97
+ add_remove_via_compute(LOW_KEY_COUNT_OPTIONS)
98
+ add_remove_via_compute(SINGLE_KEY_COUNT_OPTIONS)
99
+ end
117
100
 
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
101
+ def add_remove_via_compute_if_absent_present
102
+ add_remove_via_compute_if_absent_present
103
+ add_remove_via_compute_if_absent_present(LOW_KEY_COUNT_OPTIONS)
104
+ add_remove_via_compute_if_absent_present(SINGLE_KEY_COUNT_OPTIONS)
105
+ end
123
106
 
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
107
+ def test_add_remove_indiscriminate
108
+ add_remove_indiscriminate
109
+ add_remove_indiscriminate(LOW_KEY_COUNT_OPTIONS)
110
+ add_remove_indiscriminate(SINGLE_KEY_COUNT_OPTIONS)
111
+ end
129
112
 
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)
113
+ def test_count_up
114
+ count_up
115
+ count_up(LOW_KEY_COUNT_OPTIONS)
116
+ count_up(SINGLE_KEY_COUNT_OPTIONS)
141
117
  end
142
- end
143
118
 
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)
119
+ def test_count_up_via_compute
120
+ count_up_via_compute
121
+ count_up_via_compute(LOW_KEY_COUNT_OPTIONS)
122
+ count_up_via_compute(SINGLE_KEY_COUNT_OPTIONS)
148
123
  end
149
- end
150
124
 
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)
125
+ def test_count_up_via_merge_pair
126
+ count_up_via_merge_pair
127
+ count_up_via_merge_pair(LOW_KEY_COUNT_OPTIONS)
128
+ count_up_via_merge_pair(SINGLE_KEY_COUNT_OPTIONS)
155
129
  end
156
- end
157
130
 
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 }
131
+ def test_count_race
132
+ prelude = 'change = (rand(2) == 1) ? 1 : -1'
133
+ code = <<-RUBY_EVAL
134
+ v = cache[key]
135
+ acc += change if cache.replace_pair(key, v, v + change)
136
+ RUBY_EVAL
137
+ do_thread_loop(:count_race, code, :loop_count => 5, :prelude => prelude, :cache_setup => ZERO_VALUE_CACHE_SETUP) do |result, cache, options, keys|
138
+ result_sum = sum(result)
139
+ assert_equal(sum(keys.map {|key| cache[key]}), result_sum)
140
+ assert_equal(sum(cache.values), result_sum)
141
+ assert_equal(options[:key_count], cache.size)
166
142
  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
143
+ end
144
+
145
+ def test_get_and_set_new
146
+ code = 'acc += 1 unless cache.get_and_set(key, key)'
147
+ do_thread_loop(:get_and_set_new, code) do |result, cache, options, keys|
148
+ assert_standard_accumulator_test_result(result, cache, options, keys)
176
149
  end
177
- assert_equal(stored_sum, sum(result))
178
- assert_equal(stored_key_count, cache.size)
179
150
  end
180
- end
181
151
 
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)
152
+ def test_get_and_set_existing
153
+ code = 'acc += 1 if cache.get_and_set(key, key) == -1'
154
+ do_thread_loop(:get_and_set_existing, code, :cache_setup => INITIAL_VALUE_CACHE_SETUP, :initial_value => -1) do |result, cache, options, keys|
155
+ assert_standard_accumulator_test_result(result, cache, options, keys)
189
156
  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
157
  end
195
- end
196
158
 
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
159
+ private
160
+ def compute_if_absent_and_present(opts = {})
161
+ prelude = 'on_present = rand(2) == 1'
162
+ code = <<-RUBY_EVAL
163
+ if on_present
164
+ cache.compute_if_present(key) {|old_value| acc += 1; old_value + 1}
165
+ else
166
+ cache.compute_if_absent(key) { acc += 1; 1 }
167
+ end
168
+ RUBY_EVAL
169
+ do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
170
+ stored_sum = 0
171
+ stored_key_count = 0
172
+ keys.each do |k|
173
+ if value = cache[k]
174
+ stored_sum += value
175
+ stored_key_count += 1
176
+ end
177
+ end
178
+ assert_equal(stored_sum, sum(result))
179
+ assert_equal(stored_key_count, cache.size)
208
180
  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
181
  end
214
- end
215
182
 
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 }
183
+ def add_remove(opts = {})
184
+ prelude = 'do_add = rand(2) == 1'
185
+ code = <<-RUBY_EVAL
186
+ if do_add
187
+ acc += 1 unless cache.put_if_absent(key, key)
188
+ else
189
+ acc -= 1 if cache.delete_pair(key, key)
190
+ end
191
+ RUBY_EVAL
192
+ do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
193
+ assert_all_key_mappings_exist(cache, keys, false)
194
+ assert_equal(cache.size, sum(result))
223
195
  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
196
  end
229
- end
230
197
 
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)
198
+ def add_remove_via_compute(opts = {})
199
+ prelude = 'do_add = rand(2) == 1'
200
+ code = <<-RUBY_EVAL
201
+ cache.compute(key) do |old_value|
202
+ if do_add
203
+ acc += 1 unless old_value
204
+ key
205
+ else
206
+ acc -= 1 if old_value
207
+ nil
208
+ end
209
+ end
210
+ RUBY_EVAL
211
+ do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
212
+ assert_all_key_mappings_exist(cache, keys, false)
213
+ assert_equal(cache.size, sum(result))
238
214
  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
215
  end
244
- end
245
216
 
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)
217
+ def add_remove_via_compute_if_absent_present(opts = {})
218
+ prelude = 'do_add = rand(2) == 1'
219
+ code = <<-RUBY_EVAL
220
+ if do_add
221
+ cache.compute_if_absent(key) { acc += 1; key }
222
+ else
223
+ cache.compute_if_present(key) { acc -= 1; nil }
224
+ end
225
+ RUBY_EVAL
226
+ do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
227
+ assert_all_key_mappings_exist(cache, keys, false)
228
+ assert_equal(cache.size, sum(result))
229
+ end
253
230
  end
254
- end
255
231
 
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
232
+ def add_remove_indiscriminate(opts = {})
233
+ prelude = 'do_add = rand(2) == 1'
234
+ code = <<-RUBY_EVAL
235
+ if do_add
236
+ acc += 1 unless cache.put_if_absent(key, key)
237
+ else
238
+ acc -= 1 if cache.delete(key)
239
+ end
240
+ RUBY_EVAL
241
+ do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
242
+ assert_all_key_mappings_exist(cache, keys, false)
243
+ assert_equal(cache.size, sum(result))
261
244
  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
245
+ end
246
+
247
+ def count_up(opts = {})
248
+ code = <<-RUBY_EVAL
249
+ v = cache[key]
250
+ acc += 1 if cache.replace_pair(key, v, v + 1)
251
+ RUBY_EVAL
252
+ do_thread_loop(__method__, code, {:loop_count => 5, :cache_setup => ZERO_VALUE_CACHE_SETUP}.merge(opts)) do |result, cache, options, keys|
253
+ assert_count_up(result, cache, options, keys)
268
254
  end
269
255
  end
270
- end
271
256
 
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
257
+ def count_up_via_compute(opts = {})
258
+ code = <<-RUBY_EVAL
259
+ cache.compute(key) do |old_value|
260
+ acc += 1
261
+ old_value ? old_value + 1 : 1
262
+ end
263
+ RUBY_EVAL
264
+ do_thread_loop(:count_up_via_compute, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
265
+ assert_count_up(result, cache, options, keys)
266
+ result.inject(nil) do |previous_value, next_value| # since compute guarantees atomicity all count ups should be equal
267
+ assert_equal previous_value, next_value if previous_value
268
+ next_value
269
+ end
284
270
  end
285
- assert all_match
286
271
  end
287
- end
288
272
 
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))
273
+ def count_up_via_merge_pair(opts = {})
274
+ code = <<-RUBY_EVAL
275
+ cache.merge_pair(key, 1) {|old_value| old_value + 1}
276
+ RUBY_EVAL
277
+ do_thread_loop(:count_up_via_merge_pair, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
278
+ all_match = true
279
+ expected_value = options[:loop_count] * options[:thread_count]
280
+ keys.each do |key|
281
+ if expected_value != (value = cache[key])
282
+ all_match = false
283
+ break
284
+ end
285
+ end
286
+ assert all_match
287
+ end
297
288
  end
298
- end
299
289
 
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))
290
+ def add_remove_to_zero(opts = {})
291
+ code = <<-RUBY_EVAL
292
+ acc += 1 unless cache.put_if_absent(key, key)
293
+ acc -= 1 if cache.delete_pair(key, key)
294
+ RUBY_EVAL
295
+ do_thread_loop(:add_remove_to_zero, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
296
+ assert_all_key_mappings_exist(cache, keys, false)
297
+ assert_equal(cache.size, sum(result))
298
+ end
307
299
  end
308
- end
309
300
 
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)
301
+ def add_remove_to_zero_via_merge_pair(opts = {})
302
+ code = <<-RUBY_EVAL
303
+ acc += (cache.merge_pair(key, key) {}) ? -1 : 1
304
+ RUBY_EVAL
305
+ do_thread_loop(:add_remove_to_zero_via_merge_pair, code, {:loop_count => 5}.merge(opts)) do |result, cache, options, keys|
306
+ assert_all_key_mappings_exist(cache, keys, false)
307
+ assert_equal(cache.size, sum(result))
321
308
  end
322
309
  end
323
- end
324
310
 
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])
311
+ def do_thread_loop(name, code, options = {}, &block)
312
+ options = DEFAULTS.merge(options)
313
+ meth = define_loop name, code, options[:prelude]
314
+ assert_nothing_raised do
315
+ keys = to_keys_array(options[:key_count])
316
+ run_thread_loop(meth, keys, options, &block)
317
+
318
+ if options[:key_count] > 1
319
+ options[:key_count] = (options[:key_count] / 40).to_i
320
+ keys = to_hash_collision_keys_array(options[:key_count])
321
+ run_thread_loop(meth, keys, options.merge(:loop_count => (options[:loop_count] * 5)), &block)
322
+ end
332
323
  end
333
- end.map(&:value)
334
- yield result, cache, options, keys if block_given?
335
- end
324
+ end
336
325
 
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)
326
+ def run_thread_loop(meth, keys, options)
327
+ cache = options[:cache_setup].call(options, keys)
328
+ barrier = ThreadSafe::Test::Barrier.new(options[:thread_count])
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?
345
335
  end
346
- end
347
336
 
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
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
415
347
 
416
- def to_keys_array(key_count)
417
- arr = []
418
- key_count.times {|i| arr << i}
419
- arr
420
- end
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
421
415
 
422
- def to_hash_collision_keys_array(key_count)
423
- to_keys_array(key_count).map {|key| ThreadSafe::Test::HashCollisionKey(key)}
424
- end
416
+ def to_keys_array(key_count)
417
+ arr = []
418
+ key_count.times {|i| arr << i}
419
+ arr
420
+ end
425
421
 
426
- def sum(result)
427
- result.inject(0) {|acc, i| acc + i}
428
- end
422
+ def to_hash_collision_keys_array(key_count)
423
+ to_keys_array(key_count).map {|key| ThreadSafe::Test::HashCollisionKey(key)}
424
+ end
429
425
 
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
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
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
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
440
441
  end
441
442
  end
442
- end
443
443
 
444
- def assert_count_up(result, cache, options, keys)
445
- keys.each do |key|
446
- unless value = cache[key]
447
- assert value
444
+ def assert_count_up(result, cache, options, keys)
445
+ keys.each do |key|
446
+ unless value = cache[key]
447
+ assert value
448
+ end
448
449
  end
450
+ assert_equal(sum(cache.values), sum(result))
451
+ assert_equal(options[:key_count], cache.size)
449
452
  end
450
- assert_equal(sum(cache.values), sum(result))
451
- assert_equal(options[:key_count], cache.size)
452
453
  end
453
454
  end