thread_safe 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: becd0122b50b5237811fa86271f7d33724680011
4
- data.tar.gz: 3111e146b92de7b07e7b052b3dd126aaad1aca2c
3
+ metadata.gz: a50e05427c5ceed2e32b3132918648b960b94bbf
4
+ data.tar.gz: eefa7c8cff2df4b9b85514b5a6daa2bcd778b0a5
5
5
  SHA512:
6
- metadata.gz: ae11d6069603a1f4a6ae9ce62347629b22b2841c4008f248fd05b5c5fc2449913fbc0554919f4615e0abd55fdb903a42eee1520fbc723e17618e7b6419933559
7
- data.tar.gz: 8c80390a923556d2627d89af464e51ddae6ac9b5c44a485c6c6c0ad804b01aeed631680a7ab23c63e46293d8649978eceb5dd40cc6218d1f101597baf50b6ee1
6
+ metadata.gz: f8a881a95424ab88089c4e730d3c8b8ee8df5d75322b939319d3386070f8693071febb3306408fa873ee23eab32870608e7d52fe8b80558a347d0460b500b3f7
7
+ data.tar.gz: ddd2c6635b674c9c4af25384a9cbf0b72a9f760977b072af037203d3fe46e9c21938fb12ea70d6403c5edbc2148f05d6ef563f7229aa0112d3e7d7b76e08e3a3
@@ -16,6 +16,14 @@ require 'monitor'
16
16
  # This class is currently being considered for inclusion into stdlib, via
17
17
  # https://bugs.ruby-lang.org/issues/8556
18
18
  class SynchronizedDelegator < SimpleDelegator
19
+ def setup
20
+ @old_abort = Thread.abort_on_exception
21
+ Thread.abort_on_exception = true
22
+ end
23
+
24
+ def teardown
25
+ Thread.abort_on_exception = @old_abort
26
+ end
19
27
 
20
28
  def initialize(obj)
21
29
  __setobj__(obj)
@@ -1,12 +1,45 @@
1
1
  module ThreadSafe
2
2
  module Util
3
- # An overhead-less atomic reference.
4
3
  AtomicReference =
5
4
  if defined?(Rubinius::AtomicReference)
5
+ # An overhead-less atomic reference.
6
6
  Rubinius::AtomicReference
7
7
  else
8
- require 'atomic'
9
- defined?(Atomic::InternalReference) ? Atomic::InternalReference : Atomic
8
+ begin
9
+ require 'atomic'
10
+ defined?(Atomic::InternalReference) ? Atomic::InternalReference : Atomic
11
+ rescue NameError
12
+ require 'thread' # get Mutex on 1.8
13
+ class FullLockingAtomicReference
14
+ def initialize(value = nil)
15
+ @___mutex = Mutex.new
16
+ @___value = value
17
+ end
18
+
19
+ def get
20
+ @___mutex.synchronize { @___value }
21
+ end
22
+ alias_method :value, :get
23
+
24
+ def set(new_value)
25
+ @___mutex.synchronize { @___value = new_value }
26
+ end
27
+ alias_method :value=, :set
28
+
29
+ def compare_and_set(old_value, new_value)
30
+ return false unless @___mutex.try_lock
31
+ begin
32
+ return false unless @___value.equal? old_value
33
+ @___value = new_value
34
+ ensure
35
+ @___mutex.unlock
36
+ end
37
+ true
38
+ end
39
+ end
40
+
41
+ FullLockingAtomicReference
42
+ end
10
43
  end
11
44
  end
12
45
  end
@@ -1,5 +1,5 @@
1
1
  module ThreadSafe
2
- VERSION = "0.3.1"
2
+ VERSION = "0.3.2"
3
3
  end
4
4
 
5
5
  # NOTE: <= 0.2.0 used Threadsafe::VERSION
@@ -5,450 +5,448 @@ require File.join(File.dirname(__FILE__), "test_helper")
5
5
 
6
6
  Thread.abort_on_exception = true
7
7
 
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)
22
- end
8
+ class TestCacheTorture < Test::Unit::TestCase # this is not run unless RUBY_VERSION =~ /1\.8/ || ENV['TRAVIS'] (see the end of the file)
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
23
22
 
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)
43
- end
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(__method__, code)
42
+ end
44
43
 
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)
48
- end
44
+ def test_put_if_absent
45
+ do_thread_loop(__method__, '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)
49
47
  end
48
+ end
50
49
 
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
50
+ def test_compute_if_absent
51
+ code = 'cache.compute_if_absent(key) { acc += 1; key }'
52
+ do_thread_loop(__method__, code) do |result, cache, options, keys|
53
+ assert_standard_accumulator_test_result(result, cache, options, keys)
56
54
  end
55
+ end
57
56
 
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)
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)
68
63
  end
64
+ RUBY_EVAL
65
+ do_thread_loop(__method__, code) do |result, cache, options, keys|
66
+ assert_standard_accumulator_test_result(result, cache, options, keys)
69
67
  end
68
+ end
70
69
 
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
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
76
75
 
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
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
82
81
 
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
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
88
87
 
89
- def test_add_remove
90
- add_remove
91
- add_remove(LOW_KEY_COUNT_OPTIONS)
92
- add_remove(SINGLE_KEY_COUNT_OPTIONS)
93
- end
88
+ def test_add_remove
89
+ add_remove
90
+ add_remove(LOW_KEY_COUNT_OPTIONS)
91
+ add_remove(SINGLE_KEY_COUNT_OPTIONS)
92
+ end
94
93
 
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
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
100
99
 
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
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
106
105
 
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
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
112
111
 
113
- def test_count_up
114
- count_up
115
- count_up(LOW_KEY_COUNT_OPTIONS)
116
- count_up(SINGLE_KEY_COUNT_OPTIONS)
117
- end
112
+ def test_count_up
113
+ count_up
114
+ count_up(LOW_KEY_COUNT_OPTIONS)
115
+ count_up(SINGLE_KEY_COUNT_OPTIONS)
116
+ end
118
117
 
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)
123
- end
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
124
123
 
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)
129
- end
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
130
129
 
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)
142
- end
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(__method__, 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)
143
141
  end
142
+ end
144
143
 
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)
149
- end
144
+ def test_get_and_set_new
145
+ code = 'acc += 1 unless cache.get_and_set(key, key)'
146
+ do_thread_loop(__method__, code) do |result, cache, options, keys|
147
+ assert_standard_accumulator_test_result(result, cache, options, keys)
150
148
  end
149
+ end
151
150
 
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)
156
- end
151
+ def test_get_and_set_existing
152
+ code = 'acc += 1 if cache.get_and_set(key, key) == -1'
153
+ do_thread_loop(__method__, 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)
157
155
  end
156
+ end
158
157
 
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)
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 }
180
166
  end
181
- end
182
-
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))
167
+ RUBY_EVAL
168
+ do_thread_loop(__method__, 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
195
176
  end
177
+ assert_equal(stored_sum, sum(result))
178
+ assert_equal(stored_key_count, cache.size)
196
179
  end
180
+ end
197
181
 
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))
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)
214
189
  end
190
+ RUBY_EVAL
191
+ do_thread_loop(__method__, 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))
215
194
  end
195
+ end
216
196
 
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))
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
229
208
  end
209
+ RUBY_EVAL
210
+ do_thread_loop(__method__, 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))
230
213
  end
214
+ end
231
215
 
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))
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 }
244
223
  end
224
+ RUBY_EVAL
225
+ do_thread_loop(__method__, 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))
245
228
  end
229
+ end
246
230
 
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)
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)
254
238
  end
239
+ RUBY_EVAL
240
+ do_thread_loop(__method__, 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))
255
243
  end
244
+ end
256
245
 
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
270
- end
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(__method__, 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)
271
253
  end
254
+ end
272
255
 
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
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
287
261
  end
288
- end
289
-
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))
262
+ RUBY_EVAL
263
+ do_thread_loop(__method__, 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
298
268
  end
299
269
  end
270
+ end
300
271
 
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))
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(__method__, 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
308
284
  end
285
+ assert all_match
309
286
  end
287
+ end
310
288
 
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
323
- end
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(__method__, 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))
324
297
  end
298
+ end
325
299
 
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?
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(__method__, 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))
335
307
  end
308
+ end
336
309
 
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)
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)
345
321
  end
346
322
  end
323
+ end
347
324
 
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
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
+ result = (1..options[:thread_count]).map do
329
+ Thread.new do
330
+ setup_sync_and_start_loop(meth, cache, keys, barrier, options[:loop_count])
331
+ end
332
+ end.map(&:value)
333
+ yield result, cache, options, keys if block_given?
334
+ end
415
335
 
416
- def to_keys_array(key_count)
417
- arr = []
418
- key_count.times {|i| arr << i}
419
- arr
336
+ def setup_sync_and_start_loop(meth, cache, keys, barrier, loop_count)
337
+ my_keys = keys.shuffle
338
+ barrier.await
339
+ if my_keys.size == 1
340
+ key = my_keys.first
341
+ send("#{meth}_single_key", cache, key, loop_count)
342
+ else
343
+ send("#{meth}_multiple_keys", cache, my_keys, loop_count)
420
344
  end
345
+ end
421
346
 
422
- def to_hash_collision_keys_array(key_count)
423
- to_keys_array(key_count).map {|key| ThreadSafe::Test::HashCollisionKey(key)}
424
- end
347
+ def define_loop(name, body, prelude)
348
+ inner_meth_name = :"_#{name}_loop_inner"
349
+ outer_meth_name = :"_#{name}_loop_outer"
350
+ # looping is splitted into the "loop methods" to trigger the JIT
351
+ self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
352
+ def #{inner_meth_name}_multiple_keys(cache, keys, i, length, acc)
353
+ #{prelude}
354
+ target = i + length
355
+ while i < target
356
+ key = keys[i]
357
+ #{body}
358
+ i += 1
359
+ end
360
+ acc
361
+ end unless method_defined?(:#{inner_meth_name}_multiple_keys)
362
+ RUBY_EVAL
363
+
364
+ self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
365
+ def #{inner_meth_name}_single_key(cache, key, i, length, acc)
366
+ #{prelude}
367
+ target = i + length
368
+ while i < target
369
+ #{body}
370
+ i += 1
371
+ end
372
+ acc
373
+ end unless method_defined?(:#{inner_meth_name}_single_key)
374
+ RUBY_EVAL
375
+
376
+ self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
377
+ def #{outer_meth_name}_multiple_keys(cache, keys, loop_count)
378
+ total_length = keys.size
379
+ acc = 0
380
+ inc = 100
381
+ loop_count.times do
382
+ i = 0
383
+ pre_loop_inc = total_length % inc
384
+ acc = #{inner_meth_name}_multiple_keys(cache, keys, i, pre_loop_inc, acc)
385
+ i += pre_loop_inc
386
+ while i < total_length
387
+ acc = #{inner_meth_name}_multiple_keys(cache, keys, i, inc, acc)
388
+ i += inc
389
+ end
390
+ end
391
+ acc
392
+ end unless method_defined?(:#{outer_meth_name}_multiple_keys)
393
+ RUBY_EVAL
394
+
395
+ self.class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
396
+ def #{outer_meth_name}_single_key(cache, key, loop_count)
397
+ acc = 0
398
+ i = 0
399
+ inc = 100
400
+
401
+ pre_loop_inc = loop_count % inc
402
+ acc = #{inner_meth_name}_single_key(cache, key, i, pre_loop_inc, acc)
403
+ i += pre_loop_inc
404
+
405
+ while i < loop_count
406
+ acc = #{inner_meth_name}_single_key(cache, key, i, inc, acc)
407
+ i += inc
408
+ end
409
+ acc
410
+ end unless method_defined?(:#{outer_meth_name}_single_key)
411
+ RUBY_EVAL
412
+ outer_meth_name
413
+ end
425
414
 
426
- def sum(result)
427
- result.inject(0) {|acc, i| acc + i}
428
- end
415
+ def to_keys_array(key_count)
416
+ arr = []
417
+ key_count.times {|i| arr << i}
418
+ arr
419
+ end
429
420
 
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
421
+ def to_hash_collision_keys_array(key_count)
422
+ to_keys_array(key_count).map {|key| ThreadSafe::Test::HashCollisionKey(key)}
423
+ end
435
424
 
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
425
+ def sum(result)
426
+ result.inject(0) {|acc, i| acc + i}
427
+ end
428
+
429
+ def assert_standard_accumulator_test_result(result, cache, options, keys)
430
+ assert_all_key_mappings_exist(cache, keys)
431
+ assert_equal(options[:key_count], sum(result))
432
+ assert_equal(options[:key_count], cache.size)
433
+ end
434
+
435
+ def assert_all_key_mappings_exist(cache, keys, all_must_exist = true)
436
+ keys.each do |key|
437
+ if (value = cache[key]) || all_must_exist
438
+ assert_equal key, value unless key == value # don't do a bazzilion assertions unless necessary
441
439
  end
442
440
  end
441
+ end
443
442
 
444
- def assert_count_up(result, cache, options, keys)
445
- keys.each do |key|
446
- unless value = cache[key]
447
- assert value
448
- end
443
+ def assert_count_up(result, cache, options, keys)
444
+ keys.each do |key|
445
+ unless value = cache[key]
446
+ assert value
449
447
  end
450
- assert_equal(sum(cache.values), sum(result))
451
- assert_equal(options[:key_count], cache.size)
452
448
  end
449
+ assert_equal(sum(cache.values), sum(result))
450
+ assert_equal(options[:key_count], cache.size)
453
451
  end
454
- end
452
+ end unless RUBY_VERSION =~ /1\.8/ || ENV['TRAVIS']
@@ -2,14 +2,6 @@ require 'test/unit'
2
2
  require 'thread_safe/synchronized_delegator.rb'
3
3
 
4
4
  class TestSynchronizedDelegator < Test::Unit::TestCase
5
- def setup
6
- @old_abort = Thread.abort_on_exception
7
- Thread.abort_on_exception = true
8
- end
9
-
10
- def teardown
11
- Thread.abort_on_exception = @old_abort
12
- end
13
5
 
14
6
  def test_wraps_array
15
7
  sync_array = SynchronizedDelegator.new(array = [])
data/thread_safe.gemspec CHANGED
@@ -19,6 +19,6 @@ Gem::Specification.new do |gem|
19
19
  gem.version = ThreadSafe::VERSION
20
20
  gem.license = "Apache-2.0"
21
21
 
22
- gem.add_dependency 'atomic', ['>= 1.1.7', '< 2']
22
+ gem.add_development_dependency 'atomic', ['>= 1.1.7', '< 2']
23
23
  gem.add_development_dependency 'rake'
24
24
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: thread_safe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Oliver Nutter
@@ -9,40 +9,40 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-03-21 00:00:00.000000000 Z
12
+ date: 2014-04-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: atomic
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - ">="
18
+ - - '>='
19
19
  - !ruby/object:Gem::Version
20
20
  version: 1.1.7
21
- - - "<"
21
+ - - <
22
22
  - !ruby/object:Gem::Version
23
23
  version: '2'
24
- type: :runtime
24
+ type: :development
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
27
27
  requirements:
28
- - - ">="
28
+ - - '>='
29
29
  - !ruby/object:Gem::Version
30
30
  version: 1.1.7
31
- - - "<"
31
+ - - <
32
32
  - !ruby/object:Gem::Version
33
33
  version: '2'
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: rake
36
36
  requirement: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  type: :development
42
42
  prerelease: false
43
43
  version_requirements: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - '>='
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  description: Thread-safe collections and utilities for Ruby
@@ -53,8 +53,8 @@ executables: []
53
53
  extensions: []
54
54
  extra_rdoc_files: []
55
55
  files:
56
- - ".gitignore"
57
- - ".travis.yml"
56
+ - .gitignore
57
+ - .travis.yml
58
58
  - Gemfile
59
59
  - LICENSE
60
60
  - README.md
@@ -105,17 +105,17 @@ require_paths:
105
105
  - lib
106
106
  required_ruby_version: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ">="
108
+ - - '>='
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  required_rubygems_version: !ruby/object:Gem::Requirement
112
112
  requirements:
113
- - - ">="
113
+ - - '>='
114
114
  - !ruby/object:Gem::Version
115
115
  version: '0'
116
116
  requirements: []
117
117
  rubyforge_project:
118
- rubygems_version: 2.2.2
118
+ rubygems_version: 2.0.14
119
119
  signing_key:
120
120
  specification_version: 4
121
121
  summary: A collection of data structures and utilities to make thread-safe programming