La validation des transactions est trop fastidieuse. Cela semble simple comme modèle, alors j'ai pensé en faire un.
Ruby
ruby.trans.rb
class Command end
class Begin < Command
end
class Rollback < Command
end
class Commit < Command
end
class Read < Command
attr_reader :var
def initialize(var)
@var = var
end
end
class Write < Command
attr_reader :var,:val
def initialize(var,val)
@var = var
@val = val
end
end
class Lock
attr_reader :tr,:var,:type
def initialize(tr,var,type)
@tr=tr
@var=var
@type=type
end
end
class Unlock
attr_reader :tr,:var
def initialize(tr,var)
@tr= tr
@var=var
end
end
class Insert
attr_reader :var,:val
def initialize(var,val)
@var=var
@val=val
end
end
class Delete
attr_reader :var
def initialize(var)
@var=var
end
end
class Event
attr_reader :tr,:cmd
# tr :String
# cmd : Command
def initialize(tr, cmd)
@tr = tr
@cmd = cmd
end
end
class Database
attr_reader :vars
# vars : Map<var_name,value>
def initialize(vars)
@vars = vars
end
end
class Transaction
attr_accessor :name,:vars
def initialize(name)
@name=name
@vars= {}
end
end
class State
attr_reader :db,:events,:pointer,:trs
def initialize(db,events,trs)
@db = db
@locks = []
@events = events
@pointer = 0
@trs = trs
end
def next()
e = @events[@pointer]
if e then
if e.cmd.is_a?(Begin) then
puts "Begin #{e.tr}"
elsif e.cmd.is_a?(Rollback) then
puts "Rollback #{e.tr}"
elsif e.cmd.is_a?(Commit) then
puts "Commit #{e.tr}"
elsif e.cmd.is_a?(Read) then
if @locks.filter {|l| l.tr != e.tr && l.var == e.cmd.var && l.type == :Exclusive}.empty? then
puts "Read #{e.tr} var=#{e.cmd.var} => val=#{@db.vars[e.cmd.var]}"
else
puts "Read #{e.tr} var=#{e.cmd.var} => Read failed. This variable is locked by another transaction."
raise("Reading failure")
end
elsif e.cmd.is_a?(Write) then
if @locks.filter {|l| l.tr != e.tr && l.var == e.cmd.var}.empty? then
pre = @db.vars[e.cmd.var]
@db.vars[e.cmd.var]=e.cmd.val
puts "Write #{e.tr} var=#{e.cmd.var} val=#{e.cmd.val} => db[#{e.cmd.var}]: #{pre} => #{@db.vars[e.cmd.var]}"
else
puts "Write #{e.tr} var=#{e.cmd.var} val=#{e.cmd.val} => This variable is locked by another transaction."
raise("Writing failure")
end
elsif e.cmd.is_a?(Lock) then
if @locks.filter {|l| l.tr != e.cmd.tr && l.var == e.cmd.var && l.type == :Exclusive}.empty? then
@locks << e.cmd
puts "Lock #{e.tr} var=#{e.cmd.var} type=#{e.cmd.type} => Success."
else
puts "Lock #{e.tr} var=#{e.cmd.var} type=#{e.cmd.type} => This variable is locked by another transaction. Getting lock was failed."
raise("Locking failure")
end
elsif e.cmd.is_a?(Unlock) then
@locks.filter!{|l| !(l.tr == e.tr && l.var == e.cmd.var)}
puts "Unlock #{e.tr}"
elsif e.cmd.is_a?(Insert) then
if @db.vars[e.cmd.var].nil? then
@db.vars[e.cmd.var] = e.cmd.val
puts "Insert #{e.tr} var=#{e.cmd.var} val=#{e.cmd.val} => Success."
else
puts "Insert #{e.tr} var=#{e.cmd.var} val=#{e.cmd.val} => This variable is already exists. Insertion was failed."
raise("Insertion failure")
end
elsif e.cmd.is_a?(Delete) then
if [email protected]{|l| l.tr != e.tr && l.var == e.cmd.var}.empty? then
puts "Delete #{e.tr} var=#{e.cmd.var} => This variable is locked by another transaction. Deletion was failed."
raise("Deletion failure")
end
if @db.vars[e.cmd.var].nil? then
puts "Delete #{e.tr} var=#{e.cmd.var} => No such variable. Deletion was failed."
raise("Deletion failure")
end
@db.vars.delete(e.cmd.var)
@locks.filter! {|l| !(l.tr == e.tr && l.var == e.cmd.var)}
puts "Delete #{e.tr} var=#{e.cmd.var} => Success"
else
puts "Other"
end
else
# the last of events
end
@pointer += 1
end
end
@events = [
Event.new("tr_a",Begin.new()),
Event.new("tr_b",Begin.new()),
Event.new("tr_a",Lock.new("tr_a","X",:Shared)),
Event.new("tr_a",Read.new("X")),
Event.new("tr_b",Read.new("X")),
Event.new("tr_a",Lock.new("tr_a","X",:Exclusive)),
Event.new("tr_a",Write.new("X",20)),
Event.new("tr_a",Unlock.new("tr_a","X")),
Event.new("tr_b",Read.new("X")),
Event.new("tr_b",Insert.new("Y",12)),
Event.new("tr_b",Delete.new("Y")),
Event.new("tr_a",Commit.new()),
Event.new("tr_b",Commit.new())
]
@vars = {"X" => 10}
@db = Database.new(@vars)
@trs = {
"tr_a" => Transaction.new("tr_a"),
"tr_b" => Transaction.new("tr_b"),
}
@state = State.new(@db,@events,@trs)
@events.each_index {|i|
printf("%3d ",i)
@state.next()
}
0 Begin tr_a
1 Begin tr_b
2 Lock tr_a var=X type=Shared => Success.
3 Read tr_a var=X => val=10
4 Read tr_b var=X => val=10
5 Lock tr_a var=X type=Exclusive => Success.
6 Write tr_a var=X val=20 => db[X]: 10 => 20
7 Unlock tr_a
8 Read tr_b var=X => val=20
9 Insert tr_b var=Y val=12 => Success.
10 Delete tr_b var=Y => Success
11 Commit tr_a
12 Commit tr_b
Comme ça.
Un autre événement de transaction. Lorsque le verrouillage exclusif de X est appliqué.
Event.new("tr_a",Begin.new()),
Event.new("tr_b",Begin.new()),
Event.new("tr_a",Lock.new("tr_a","X",:Exclusive)),
Event.new("tr_a",Read.new("X")),
Event.new("tr_b",Read.new("X")),
0 Begin tr_a
1 Begin tr_b
2 Lock tr_a var=X type=Exclusive => Success.
3 Read tr_a var=X => val=10
4 Read tr_b var=X => Read failed. This variable is locked by another transaction.
Traceback (most recent call last):
3: from tran.rb:182:in `<main>'
2: from tran.rb:182:in `each_index'
1: from tran.rb:184:in `block in <main>'
tran.rb:103:in `next': Reading failure (RuntimeError)
Le chargement de tr_b est verrouillé et cette colonne d'événements échoue.
Ensuite, modélisons la situation d'attente de verrouillage. Le modèle est que lorsqu'une transaction est terminée, l'attente de verrouillage pour l'autre est également annulée.
Vous devez écrire un test décent et valider le modèle.
Puisqu'il s'agit d'une simulation avec un mécanisme de verrouillage simple, pas MVCC, il est déroutant de voir la séquence d'événements avec l'intention de MVCC.