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

Commit cfb7255

Browse files
committed
refactor our synchronized delegator to work with MRI/JRuby 1.8 std-lib
this due delegator.rb library doing eval on `initialize` - thus `method_missing` is not actually called for __send__ delegation to the target. we avoid this by unpatching the work done by MRI as well as JRuby in 1.8 mode (which is slightly different since it tries to pre-generate a module that it includes based on target's class do not do that much eval). changed tests due a bug in delegator.rb that affects 1.8 - we've been expecting a delegated Array#each (with a block) which won't work since the block is not passed to the target ...
1 parent e301df3 commit cfb7255

File tree

2 files changed

+116
-34
lines changed

2 files changed

+116
-34
lines changed
Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,75 @@
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+
super # __setobj__(obj)
21+
@mutex = Mutex.new
22+
undef_cached_methods!
23+
end
1724

18-
unless defined?(SynchronizedDelegator)
19-
class SynchronizedDelegator < SimpleDelegator
20-
def initialize(*)
25+
def method_missing(method, *args, &block)
26+
mutex = @mutex
27+
begin
28+
mutex.lock
2129
super
22-
@mutex = Mutex.new
30+
ensure
31+
mutex.unlock
2332
end
33+
end
34+
35+
private
36+
37+
if RUBY_VERSION[0, 3] == '1.8'
2438

25-
def method_missing(m, *args, &block)
26-
begin
27-
mutex = @mutex
28-
mutex.lock
29-
super
30-
ensure
31-
mutex.unlock
39+
def singleton_class
40+
class << self; self end
41+
end unless respond_to?(:singleton_class)
42+
43+
# The 1.8 delegator library does (instance) "eval" all methods
44+
# delegated on {#initialize}.
45+
# @see http://rubydoc.info/stdlib/delegate/1.8.7/Delegator
46+
# @private
47+
def undef_cached_methods!
48+
self_class = singleton_class
49+
for method in self_class.instance_methods(false)
50+
self_class.send :undef_method, method
3251
end
3352
end
53+
54+
# JRuby 1.8 mode stdlib internals - caching generated modules
55+
# methods under `Delegator::DelegatorModules` based on class.
56+
# @private
57+
def undef_cached_methods!
58+
gen_mod = DelegatorModules[[__getobj__.class, self.class]]
59+
if gen_mod && singleton_class.include?(gen_mod)
60+
self_class = singleton_class
61+
for method in gen_mod.instance_methods(false)
62+
self_class.send :undef_method, method
63+
end
64+
end
65+
end if constants.include?('DelegatorModules')
66+
67+
else
68+
69+
# Nothing to do since 1.9 {#method_missing} will get called.
70+
# @private
71+
def undef_cached_methods!; end
72+
3473
end
35-
end
74+
75+
end unless defined?(SynchronizedDelegator)

test/test_synchronized_delegator.rb

Lines changed: 59 additions & 17 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
18+
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'
28+
29+
t1 = Thread.new 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
1638

17-
t1_continue = false
18-
t2_continue = false
39+
sleep(0.05) # sleep some to allow threads to boot up
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 = [])
1957

2058
t1 = Thread.new do
21-
sync_ary << 1
22-
sync_ary.each 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
70+
end
71+
72+
until t2.status == 'sleep' || t2.status == false do
73+
Thread.pass
3174
end
3275

33-
Thread.pass until t2.status == 'sleep'
34-
assert_equal 1, ary.size
76+
assert_equal 1, array.size
3577

3678
t1_continue = true
37-
t1.join
38-
t2.join
79+
t1.join; t2.join
80+
81+
assert_equal [1, 2], array
82+
end if RUBY_VERSION !~ /1\.8/
3983

40-
assert_equal 2, sync_ary.size
41-
end
4284
end

0 commit comments

Comments
 (0)