Skip to content

Commit bd345ad

Browse files
committed
TVar: switch to lazy writes to provide proper isolation.
1 parent 57f562e commit bd345ad

File tree

3 files changed

+38
-26
lines changed

3 files changed

+38
-26
lines changed

doc/tvar.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ We implement nested transactions by flattening.
2424
We only support strong isolation if you use the API correctly. In order words,
2525
we do not support strong isolation.
2626

27-
Our implementation uses a very simple two-phased locking with versioned locks algorithm, as per [1]. In the future we will look at more advanced algorithms, contention management and using existing Java implementations when in JRuby.
27+
Our implementation uses a very simple two-phased locking with versioned locks algorithm and lazy writes, as per [1]. In the future we will look at more advanced algorithms, contention management and using existing Java implementations when in JRuby.
2828

2929
See:
3030

lib/concurrent/tvar.rb

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,12 @@ class Transaction
153153
ABORTED = Object.new
154154

155155
ReadLogEntry = Struct.new(:tvar, :version)
156-
UndoLogEntry = Struct.new(:tvar, :value)
157156

158157
AbortError = Class.new(StandardError)
159158

160159
def initialize
161-
@write_set = Set.new
162160
@read_log = []
163-
@undo_log = []
161+
@write_log = {}
164162
end
165163

166164
def read(tvar)
@@ -172,18 +170,14 @@ def read(tvar)
172170
def write(tvar, value)
173171
# Have we already written to this TVar?
174172

175-
unless @write_set.include? tvar
173+
unless @write_log.has_key? tvar
176174
# Try to lock the TVar
177175

178176
unless tvar.unsafe_lock.try_lock
179177
# Someone else is writing to this TVar - abort
180178
Concurrent::abort_transaction
181179
end
182180

183-
# We've locked it - add it to the write set
184-
185-
@write_set.add(tvar)
186-
187181
# If we previously wrote to it, check the version hasn't changed
188182

189183
@read_log.each do |log_entry|
@@ -193,27 +187,20 @@ def write(tvar, value)
193187
end
194188
end
195189

196-
# Record the current value of the TVar so we can undo it later
197-
198-
@undo_log.push(UndoLogEntry.new(tvar, tvar.unsafe_value))
190+
# Record the value written
199191

200-
# Write the new value to the TVar
201-
202-
tvar.unsafe_value = value
192+
@write_log[tvar] = value
203193
end
204194

205195
def abort
206-
@undo_log.each do |entry|
207-
entry.tvar.unsafe_value = entry.value
208-
end
209-
210196
unlock
211197
end
212198

213199
def commit
214200
return false unless valid?
215201

216-
@write_set.each do |tvar|
202+
@write_log.each_pair do |tvar, value|
203+
tvar.unsafe_value = value
217204
tvar.unsafe_increment_version
218205
end
219206

@@ -224,7 +211,7 @@ def commit
224211

225212
def valid?
226213
@read_log.each do |log_entry|
227-
unless @write_set.include? log_entry.tvar
214+
unless @write_log.has_key? log_entry.tvar
228215
if log_entry.tvar.unsafe_version > log_entry.version
229216
return false
230217
end
@@ -235,7 +222,7 @@ def valid?
235222
end
236223

237224
def unlock
238-
@write_set.each do |tvar|
225+
@write_log.each_key do |tvar|
239226
tvar.unsafe_lock.unlock
240227
end
241228
end

spec/concurrent/tvar_spec.rb

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,44 @@ module Concurrent
106106
expect(t2.value).to eq 0
107107
end
108108

109-
it 'provides isolation' do
109+
it 'provides weak isolation' do
110110
t = TVar.new(0)
111111

112+
a = CountDownLatch.new
113+
b = CountDownLatch.new
114+
112115
Thread.new do
113116
Concurrent::atomically do
114-
t1.value = 1
115-
sleep(1)
117+
t.value = 1
118+
a.count_down
119+
b.wait
116120
end
117121
end
118122

119-
sleep(0.5)
123+
a.wait
124+
Concurrent::atomically do
125+
expect(t.value).to eq 0
126+
end
127+
b.count_down
128+
end
129+
130+
it 'provides strong isolation' do
131+
t = TVar.new(0)
132+
133+
a = CountDownLatch.new
134+
b = CountDownLatch.new
135+
136+
Thread.new do
137+
Concurrent::atomically do
138+
t.value = 1
139+
a.count_down
140+
b.wait
141+
end
142+
end
120143

144+
a.wait
121145
expect(t.value).to eq 0
146+
b.count_down
122147
end
123148

124149
it 'nests' do

0 commit comments

Comments
 (0)