Skip to content

Commit bda33a6

Browse files
committed
TVar: basic STM seems to work.
1 parent 582a575 commit bda33a6

File tree

2 files changed

+70
-9
lines changed

2 files changed

+70
-9
lines changed

demos/tvar-demo.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def grand_total
117117

118118
GRAND_TOTAL = ACCOUNT_TOTALS.inject(0, :+)
119119

120-
TRANSFERS = (0..1_000_000).map do
120+
TRANSFERS = (0..10_000_000).map do
121121
Transfer.new(
122122
RANDOM.rand(ACCOUNT_TOTALS.size),
123123
RANDOM.rand(ACCOUNT_TOTALS.size),

lib/concurrent/tvar.rb

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'set'
2+
13
require 'concurrent/threadlocalvar'
24

35
module Concurrent
@@ -6,12 +8,15 @@ module Concurrent
68

79
CURRENT_TRANSACTION = ThreadLocalVar.new(nil)
810

11+
ReadLogEntry = Struct.new(:tvar, :version)
912
UndoLogEntry = Struct.new(:tvar, :value)
1013

1114
class TVar
1215

1316
def initialize(value)
1417
@value = value
18+
@version = 0
19+
@lock = Mutex.new
1520
end
1621

1722
def value
@@ -34,25 +39,64 @@ def unsafe_value=(value)
3439
@value = value
3540
end
3641

42+
def unsafe_version
43+
@version
44+
end
45+
46+
def unsafe_increment_version
47+
@version += 1
48+
end
49+
50+
def unsafe_lock
51+
@lock
52+
end
53+
3754
end
3855

3956
class Transaction
4057

41-
LOCK = Mutex.new
42-
4358
def initialize
59+
@write_set = Set.new
60+
@read_log = []
4461
@undo_log = []
45-
46-
LOCK.lock
4762
end
4863

4964
def read(tvar)
50-
validate
65+
Concurrent::abort_transaction unless valid?
66+
@read_log.push(ReadLogEntry.new(tvar, tvar.unsafe_version))
5167
tvar.unsafe_value
5268
end
5369

5470
def write(tvar, value)
71+
# Have we already written to this TVar?
72+
73+
unless @write_set.include? tvar
74+
# Try to lock the TVar
75+
76+
unless tvar.unsafe_lock.try_lock
77+
# Someone else is writing to this TVar - abort
78+
Concurrent::abort_transaction
79+
end
80+
81+
# We've locked it - add it to the write set
82+
83+
@write_set.add(tvar)
84+
85+
# If we previously wrote to it, check the version hasn't changed
86+
87+
@read_log.each do |log_entry|
88+
if log_entry.tvar == tvar and tvar.unsafe_version > log_entry.version
89+
Concurrent::abort_transaction
90+
end
91+
end
92+
end
93+
94+
# Record the current value of the TVar so we can undo it later
95+
5596
@undo_log.push(UndoLogEntry.new(tvar, tvar.unsafe_value))
97+
98+
# Write the new value to the TVar
99+
56100
tvar.unsafe_value = value
57101
end
58102

@@ -65,16 +109,33 @@ def abort
65109
end
66110

67111
def commit
68-
validate
112+
return false unless valid?
113+
114+
@write_set.each do |tvar|
115+
tvar.unsafe_increment_version
116+
end
117+
69118
unlock
119+
70120
true
71121
end
72122

73-
def validate
123+
def valid?
124+
@read_log.each do |log_entry|
125+
unless @write_set.include? log_entry.tvar
126+
if log_entry.tvar.unsafe_version > log_entry.version
127+
return false
128+
end
129+
end
130+
end
131+
132+
true
74133
end
75134

76135
def unlock
77-
LOCK.unlock
136+
@write_set.each do |tvar|
137+
tvar.unsafe_lock.unlock
138+
end
78139
end
79140

80141
def self.current

0 commit comments

Comments
 (0)