Skip to content

Commit 308f2c5

Browse files
committed
Testing multiple Exchanger implementations.
1 parent 3735bb1 commit 308f2c5

File tree

1 file changed

+94
-13
lines changed

1 file changed

+94
-13
lines changed

lib/concurrent/exchanger.rb

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module Concurrent
1212
# @see Concurrent::MVar
1313
# @see Concurrent::Dereferenceable
1414
# @see http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Exchanger.html java.util.concurrent.Exchanger
15-
class Exchanger
15+
class MVarExchanger
1616

1717
EMPTY = Object.new
1818

@@ -34,23 +34,104 @@ def initialize(opts = {})
3434
# thread. nil (default value) means no timeout
3535
# @return [Object] the value exchanged by the other thread; nil if timed out
3636
def exchange(value, timeout = nil)
37-
first = @first.take(timeout)
38-
if first == MVar::TIMEOUT
39-
nil
40-
elsif first == EMPTY
41-
@first.put value
42-
second = @second.take timeout
43-
if second == MVar::TIMEOUT
44-
nil
37+
38+
# Both threads modify the first variable
39+
first_result = @first.modify(timeout) do |first|
40+
# Does it currently contain the special empty value?
41+
if first == EMPTY
42+
# If so, modify it to contain our value
43+
value
4544
else
46-
second
45+
# Otherwise, modify it back to the empty state
46+
EMPTY
4747
end
48+
end
49+
50+
# If that timed out, the whole operation timed out
51+
return nil if first_result == MVar::TIMEOUT
52+
53+
# What was in @first before we modified it?
54+
if first_result == EMPTY
55+
# We stored our object - someone else will turn up with the second
56+
# object at some point in the future
57+
58+
# Wait for the second object to appear
59+
second_result = @second.take(timeout)
60+
61+
# If that timed out, the whole operation timed out
62+
return nil if second_result == MVar::TIMEOUT
63+
64+
# BUT HOW DO WE CANCEL OUR RESULT BEING AVAILABLE IN @first?
65+
66+
# Return that second object
67+
second_result
4868
else
49-
@first.put EMPTY
50-
@second.put value
51-
first
69+
# We reset @first to be empty again - so the other value is in
70+
# first_result and we need to tell the other thread about our value
71+
72+
# Tell the other thread about our object
73+
second_result = @second.put(value, timeout)
74+
75+
# If that timed out, the whole operation timed out
76+
return nil if second_result == MVar::TIMEOUT
77+
78+
# We already have its object
79+
first_result
5280
end
5381
end
82+
end
83+
84+
class SlotExchanger
85+
86+
def initialize
87+
@mutex = Mutex.new
88+
@condition = Condition.new
89+
@slot = new_slot
90+
end
91+
92+
def exchange(value, timeout = nil)
93+
@mutex.synchronize do
94+
95+
replace_slot_if_fulfilled
5496

97+
slot = @slot
98+
99+
if slot.state == :empty
100+
slot.value_1 = value
101+
slot.state = :waiting
102+
wait_for_value(slot, timeout)
103+
slot.value_2
104+
else
105+
slot.value_2 = value
106+
slot.state = :fulfilled
107+
@condition.broadcast
108+
slot.value_1
109+
end
110+
end
111+
end
112+
113+
Slot = Struct.new(:value_1, :value_2, :state)
114+
115+
private_constant :Slot
116+
117+
private
118+
119+
def replace_slot_if_fulfilled
120+
@slot = new_slot if @slot.state == :fulfilled
121+
end
122+
123+
def wait_for_value(slot, timeout)
124+
remaining = Condition::Result.new(timeout)
125+
while slot.state == :waiting && remaining.can_wait?
126+
remaining = @condition.wait(@mutex, remaining.remaining_time)
127+
end
128+
end
129+
130+
def new_slot
131+
Slot.new(nil, nil, :empty)
132+
end
55133
end
134+
135+
Exchanger = SlotExchanger
136+
56137
end

0 commit comments

Comments
 (0)