Created
October 16, 2020 09:50
-
-
Save kencoba/4a22586588788356ddb56344ca1b8f4b to your computer and use it in GitHub Desktop.
Transaction simulator with Ruby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Request | |
end | |
class Read < Request | |
attr_reader :var | |
def initialize(var) | |
@var = var | |
end | |
def to_s() | |
return "Read(#{@var})" | |
end | |
end | |
class Write < Request | |
attr_reader :var, :val | |
def initialize(var, val) | |
@var = var | |
@val = val | |
end | |
def to_s() | |
return "Write(#{@var},#{@val})" | |
end | |
end | |
class Insert < Request | |
attr_reader :var, :val | |
def initialize(var, val) | |
@var = var | |
@val = val | |
end | |
def to_s() | |
return "Insert(#{@var},#{@val})" | |
end | |
end | |
class Delete < Request | |
attr_reader :var | |
def initialize(var) | |
@var = var | |
end | |
def to_s() | |
return "Delete(#{@var})" | |
end | |
end | |
class Lock < Request | |
attr_reader :tr, :var, :lock_type | |
def initialize(tr, var, lock_type) | |
@tr = tr | |
@var = var | |
@lock_type = lock_type | |
end | |
def to_s() | |
return "Lock(#{@tr},#{@var},#{@lock_type})" | |
end | |
end | |
class Unlock < Request | |
attr_reader :tr, :var | |
def initialize(tr, var) | |
@tr = tr | |
@var = var | |
end | |
def to_s() | |
return "Unlock(#{@tr},#{@var})" | |
end | |
end | |
class Begin < Request | |
def to_s() | |
return "Begin" | |
end | |
end | |
class Rollback < Request | |
def to_s() | |
return "Rollback" | |
end | |
end | |
class Commit < Request | |
def to_s() | |
return "Commit" | |
end | |
end | |
class Database | |
attr_accessor :vars, :locks | |
def initialize() | |
@vars = {} | |
@locks = [] | |
end | |
def read(tr, var) | |
if @locks.filter { |l| l.tr != tr && l.var == var && l.lock_type == :Exclusive }.empty? | |
return :success, @vars[var] | |
else | |
return :failure, nil | |
end | |
end | |
def write(tr, var, val) | |
if @locks.filter { |l| l.tr != tr && l.var == var }.empty? | |
@vars[var] = val | |
return :success | |
else | |
return :failure | |
end | |
end | |
def insert(tr, var, val) | |
write(tr, var, val) | |
end | |
def delete(tr, var) | |
if @locks.filter { |l| l.tr != tr && l.var == var }.empty? | |
unlock(tr, var) | |
@vars.delete(var) | |
return :success | |
else | |
return :failure | |
end | |
end | |
def lock(tr, var, lock_type) | |
if lock_type == :Exclusive | |
if @locks.filter { |l| l.tr != tr && l.var == var }.empty? | |
@locks << Lock.new(tr, var, lock_type) | |
return :success | |
end | |
return :failure | |
else | |
if @locks.filter { |l| l.tr != tr && l.var == var && l.lock_type == :Exclusive }.empty? | |
@locks << Lock.new(tr, var, lock_type) | |
return :success | |
end | |
return :failure | |
end | |
end | |
def unlock(tr, var) | |
@locks.filter! { |l| !(l.tr == tr && l.var == var) } | |
return :success | |
end | |
def unlock_all(tr) | |
@locks.filter! { |l| l.tr != tr } | |
return :success | |
end | |
def begin(tr) | |
unlock_all(tr) | |
return :success | |
end | |
def rollback(tr) | |
unlock_all(tr) | |
return :success | |
end | |
def commit(tr) | |
unlock_all(tr) | |
return :success | |
end | |
end | |
class Transaction | |
attr_reader :db, :name, :requests, :counter, :vars, :status | |
def initialize(db, name, requests) | |
@db = db | |
@name = name | |
@requests = requests | |
@counter = 0 | |
@vars = {} | |
@status = :running # :running or :waiting | |
end | |
def next() | |
req = @requests[@counter] | |
if req.is_a?(Read) | |
status, value = @db.read(@name, req.var) | |
if status == :success | |
@vars[req.var] = value | |
@status = :running | |
@counter += 1 | |
puts "#{@name} Read(#{req.var}) => #{value}" | |
else | |
@status = :waiting | |
puts "#{@name} Read(#{req.var}) => fail" | |
end | |
elsif req.is_a?(Write) | |
status = @db.write(@name, req.var, req.val) | |
if status == :success | |
@status = :running | |
@counter += 1 | |
puts "#{@name} Write(#{req.var},#{req.val}) => success" | |
else | |
@status = :waiting | |
puts "#{@name} Write(#{req.var},#{req.val}) => fail" | |
end | |
elsif req.is_a?(Insert) | |
status = @db.insert(@name, req.var, req.val) | |
if status == :success | |
@status = :running | |
@counter += 1 | |
puts "#{@name} Insert(#{req.var},#{req.val}) => success" | |
else | |
@status = :waiting | |
puts "#{@name} Write(#{req.var},#{req.val}) => fail" | |
end | |
elsif req.is_a?(Delete) | |
status = @db.delete(@name, req.var) | |
if status == :success | |
@status = :running | |
@counter += 1 | |
puts "#{@name} Delete(#{req.var}) => success" | |
else | |
@status = :waiting | |
puts "#{@name} Delete(#{req.var}) => fail" | |
end | |
elsif req.is_a?(Lock) | |
status = @db.lock(@name, req.var, req.lock_type) | |
if status == :success | |
@status = :running | |
@counter += 1 | |
puts "#{@name} Lock(#{req.var},#{req.lock_type}) => success" | |
else | |
@status = :waiting | |
puts "#{@name} Lock(#{req.var},#{req.lock_type}) => fail" | |
end | |
elsif req.is_a?(Unlock) | |
status = @db.unlock(@name, req.var) | |
if status == :success | |
@status = :running | |
@counter += 1 | |
puts "#{@name} Unock(#{req.var}) => success" | |
else | |
@status = :waiting | |
puts "#{@name} Unock(#{req.var}) => fail" | |
end | |
elsif req.is_a?(Begin) | |
status = @db.begin(@name) | |
if status == :success | |
@status = :running | |
@counter += 1 | |
puts "#{@name} Begin => success" | |
else | |
@status = :waiting | |
puts "#{@name} Begin => fail" | |
end | |
elsif req.is_a?(Rollback) | |
status = @db.rollback(@name) | |
if status == :success | |
@status = :running | |
@counter += 1 | |
puts "#{@name} Rollback => success" | |
else | |
@status = :waiting | |
puts "#{@name} Rollback => fail" | |
end | |
elsif req.is_a?(Commit) | |
status = @db.rollback(@name) | |
if status == :success | |
@status = :running | |
@counter += 1 | |
puts "#{@name} Commit => success" | |
else | |
@status = :waiting | |
puts "#{@name} Commit => fail" | |
end | |
end | |
end | |
def current_request_info() | |
return @requests[counter].to_s() | |
end | |
end | |
class Scheduler | |
attr_reader :trs, :schedule, :counter | |
def initialize(trs, schedule) | |
@trs = trs | |
@schedule = schedule | |
@counter = 0 | |
end | |
def next() | |
# check for dead lock. | |
if @trs.all? { |tr_name, tr| tr.status == :waiting } | |
raise("Dead lock.") | |
end | |
# re-run for all locked transactions. | |
transaction_status() | |
@trs.each { |tr_name, tr| | |
if tr.status == :waiting | |
print "re-try:" | |
tr.next() | |
end | |
} | |
# execute scheduled transaction. | |
printf("%6d:", @counter) | |
@trs[@schedule[@counter]].next() | |
@counter += 1 | |
end | |
def transaction_status() | |
@trs.each { |tr_name, tr| | |
puts "status:#{tr_name}: #{tr.status} #{tr.current_request_info()}" | |
} | |
end | |
end | |
@tr_a_req = [ | |
Begin.new(), | |
Write.new("X", 10), | |
Read.new("X"), | |
Write.new("X", 20), | |
Commit.new(), | |
] | |
@tr_b_req = [ | |
Begin.new(), | |
Lock.new("tr_a", "X", :Shared), | |
Commit.new(), | |
] | |
@schedule = [ | |
"tr_a", | |
"tr_a", | |
"tr_b", | |
"tr_b", | |
"tr_a", | |
"tr_a", | |
"tr_b", | |
] | |
@db = Database.new() | |
@tr_a = Transaction.new(@db, "tr_a", @tr_a_req) | |
@tr_b = Transaction.new(@db, "tr_b", @tr_b_req) | |
@trs = { | |
"tr_a" => @tr_a, | |
"tr_b" => @tr_b, | |
} | |
@master = Scheduler.new(@trs, @schedule) | |
@schedule.size().times { | |
puts | |
@master.next() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment