Skip to content
This repository was archived by the owner on Mar 15, 2022. It is now read-only.

Commit ae81242

Browse files
committed
Merge pull request #34 from kares/fix-tests
cleanup & fix tests + make SynchronizedDelegator work on 1.8
2 parents 1773812 + 02ac203 commit ae81242

File tree

6 files changed

+118
-46
lines changed

6 files changed

+118
-46
lines changed

.travis.yml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,23 @@ language: ruby
22
rvm:
33
- jruby-18mode
44
- jruby-19mode
5-
- rbx-18mode
6-
- rbx-19mode
5+
- rbx-2
76
- 1.8.7
8-
- 1.9.2
97
- 1.9.3
108
- 2.0.0
9+
- 2.1.0
10+
jdk: # for JRuby only
11+
- openjdk7
12+
- oraclejdk8
13+
matrix:
14+
exclude:
15+
- rvm: rbx-2
16+
jdk: oraclejdk8
17+
- rvm: 1.8.7
18+
jdk: oraclejdk8
19+
- rvm: 1.9.3
20+
jdk: oraclejdk8
21+
- rvm: 2.0.0
22+
jdk: oraclejdk8
23+
- rvm: 2.1.0
24+
jdk: oraclejdk8
Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,51 @@
1+
require 'delegate'
2+
13
# 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:
4+
# by wrapping it with a `Delegator` that performs `Mutex#lock/unlock` calls
5+
# around the delegated `#send`. Example:
46
#
57
# array = [] # not thread-safe on many impls
6-
# array = MutexedDelegator.new(array) # thread-safe
8+
# array = SynchronizedDelegator.new([]) # thread-safe
79
#
8-
# A simple Mutex provides a very coarse-grained way to synchronize a given
10+
# A simple `Mutex` provides a very coarse-grained way to synchronize a given
911
# object, in that it will cause synchronization for methods that have no
1012
# need for it, but this is a trivial way to get thread-safety where none may
1113
# exist currently on some implementations.
1214
#
1315
# This class is currently being considered for inclusion into stdlib, via
1416
# https://bugs.ruby-lang.org/issues/8556
17+
class SynchronizedDelegator < SimpleDelegator
1518

16-
require 'delegate'
19+
def initialize(obj)
20+
__setobj__(obj)
21+
@mutex = Mutex.new
22+
end
1723

18-
unless defined?(SynchronizedDelegator)
19-
class SynchronizedDelegator < SimpleDelegator
20-
def initialize(*)
24+
def method_missing(method, *args, &block)
25+
mutex = @mutex
26+
begin
27+
mutex.lock
2128
super
22-
@mutex = Mutex.new
29+
ensure
30+
mutex.unlock
2331
end
32+
end
2433

25-
def method_missing(m, *args, &block)
26-
begin
27-
mutex = @mutex
28-
mutex.lock
29-
super
30-
ensure
31-
mutex.unlock
34+
# Work-around for 1.8 std-lib not passing block around to delegate.
35+
# @private
36+
def method_missing(method, *args, &block)
37+
mutex = @mutex
38+
begin
39+
mutex.lock
40+
target = self.__getobj__
41+
if target.respond_to?(method)
42+
target.__send__(method, *args, &block)
43+
else
44+
super(method, *args, &block)
3245
end
46+
ensure
47+
mutex.unlock
3348
end
34-
end
35-
end
49+
end if RUBY_VERSION[0, 3] == '1.8'
50+
51+
end unless defined?(SynchronizedDelegator)

test/test_cache_loops.rb

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def compute_if_absent_and_present(opts = {})
165165
cache.compute_if_absent(key) { acc += 1; 1 }
166166
end
167167
RUBY_EVAL
168-
do_thread_loop(:compute_if_absent_and_present, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
168+
do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
169169
stored_sum = 0
170170
stored_key_count = 0
171171
keys.each do |k|
@@ -188,7 +188,7 @@ def add_remove(opts = {})
188188
acc -= 1 if cache.delete_pair(key, key)
189189
end
190190
RUBY_EVAL
191-
do_thread_loop(:add_remove, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
191+
do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
192192
assert_all_key_mappings_exist(cache, keys, false)
193193
assert_equal(cache.size, sum(result))
194194
end
@@ -207,7 +207,7 @@ def add_remove_via_compute(opts = {})
207207
end
208208
end
209209
RUBY_EVAL
210-
do_thread_loop(:add_remove_via_compute, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
210+
do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
211211
assert_all_key_mappings_exist(cache, keys, false)
212212
assert_equal(cache.size, sum(result))
213213
end
@@ -222,7 +222,7 @@ def add_remove_via_compute_if_absent_present(opts = {})
222222
cache.compute_if_present(key) { acc -= 1; nil }
223223
end
224224
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|
225+
do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
226226
assert_all_key_mappings_exist(cache, keys, false)
227227
assert_equal(cache.size, sum(result))
228228
end
@@ -237,7 +237,7 @@ def add_remove_indiscriminate(opts = {})
237237
acc -= 1 if cache.delete(key)
238238
end
239239
RUBY_EVAL
240-
do_thread_loop(:add_remove, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
240+
do_thread_loop(__method__, code, {:loop_count => 5, :prelude => prelude}.merge(opts)) do |result, cache, options, keys|
241241
assert_all_key_mappings_exist(cache, keys, false)
242242
assert_equal(cache.size, sum(result))
243243
end
@@ -248,7 +248,7 @@ def count_up(opts = {})
248248
v = cache[key]
249249
acc += 1 if cache.replace_pair(key, v, v + 1)
250250
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|
251+
do_thread_loop(__method__, code, {:loop_count => 5, :cache_setup => ZERO_VALUE_CACHE_SETUP}.merge(opts)) do |result, cache, options, keys|
252252
assert_count_up(result, cache, options, keys)
253253
end
254254
end
@@ -325,7 +325,6 @@ def do_thread_loop(name, code, options = {}, &block)
325325
def run_thread_loop(meth, keys, options)
326326
cache = options[:cache_setup].call(options, keys)
327327
barrier = ThreadSafe::Test::Barrier.new(options[:thread_count])
328-
t = Time.now
329328
result = (1..options[:thread_count]).map do
330329
Thread.new do
331330
setup_sync_and_start_loop(meth, cache, keys, barrier, options[:loop_count])

test/test_helper.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
if defined?(JRUBY_VERSION) && ENV['TEST_NO_UNSAFE']
44
# to be used like this: rake test TEST_NO_UNSAFE=true
5-
require 'test/package.jar'
5+
load 'test/package.jar'
66
java_import 'thread_safe.SecurityManager'
77
manager = SecurityManager.new
88

test/test_synchronized_delegator.rb

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,83 @@
22
require 'thread_safe/synchronized_delegator.rb'
33

44
class TestSynchronizedDelegator < Test::Unit::TestCase
5+
56
def test_wraps_array
6-
ary = []
7-
sync_ary = SynchronizedDelegator.new(ary)
7+
sync_array = SynchronizedDelegator.new(array = [])
8+
9+
array << 1
10+
assert_equal 1, sync_array[0]
811

9-
ary << 1
10-
assert_equal 1, sync_ary[0]
12+
sync_array << 2
13+
assert_equal 2, array[1]
1114
end
1215

1316
def test_synchronizes_access
14-
ary = []
15-
sync_ary = SynchronizedDelegator.new(ary)
17+
t1_continue, t2_continue = false, false
1618

17-
t1_continue = false
18-
t2_continue = false
19+
hash = Hash.new do |hash, key|
20+
t2_continue = true
21+
unless hash.find { |e| e[1] == key.to_s } # just to do something
22+
hash[key] = key.to_s
23+
Thread.pass until t1_continue
24+
end
25+
end
26+
sync_hash = SynchronizedDelegator.new(hash)
27+
sync_hash[1] = 'egy'
1928

2029
t1 = Thread.new do
21-
sync_ary << 1
22-
sync_ary.each do
30+
sync_hash[2] = 'dva'
31+
sync_hash[3] # triggers t2_continue
32+
end
33+
34+
t2 = Thread.new do
35+
Thread.pass until t2_continue
36+
sync_hash[4] = '42'
37+
end
38+
39+
sleep(0.05) # sleep some to allow threads to boot
40+
41+
until t2.status == 'sleep' do
42+
Thread.pass
43+
end
44+
45+
assert_equal 3, hash.keys.size
46+
47+
t1_continue = true
48+
t1.join; t2.join
49+
50+
assert_equal 4, sync_hash.size
51+
end
52+
53+
def test_synchronizes_access_with_block
54+
t1_continue, t2_continue = false, false
55+
56+
sync_array = SynchronizedDelegator.new(array = [])
57+
58+
t1 = Thread.new do
59+
sync_array << 1
60+
sync_array.each do
2361
t2_continue = true
2462
Thread.pass until t1_continue
2563
end
2664
end
2765

2866
t2 = Thread.new do
67+
# sleep(0.01)
2968
Thread.pass until t2_continue
30-
sync_ary << 2
69+
sync_array << 2
3170
end
3271

33-
Thread.pass until t2.status == 'sleep'
34-
assert_equal 1, ary.size
72+
until t2.status == 'sleep' || t2.status == false do
73+
Thread.pass
74+
end
75+
76+
assert_equal 1, array.size
3577

3678
t1_continue = true
37-
t1.join
38-
t2.join
79+
t1.join; t2.join
3980

40-
assert_equal 2, sync_ary.size
81+
assert_equal [1, 2], array
4182
end
83+
4284
end

thread_safe.gemspec

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- encoding: utf-8 -*-
2-
require File.expand_path('../lib/thread_safe/version', __FILE__)
2+
$:.push File.expand_path('../lib', __FILE__) unless $:.include?('lib')
3+
require 'thread_safe/version'
34

45
Gem::Specification.new do |gem|
56
gem.authors = ["Charles Oliver Nutter", "thedarkone"]

0 commit comments

Comments
 (0)