Skip to content

Instantly share code, notes, and snippets.

@kencoba
Created October 16, 2020 09:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kencoba/4a22586588788356ddb56344ca1b8f4b to your computer and use it in GitHub Desktop.
Save kencoba/4a22586588788356ddb56344ca1b8f4b to your computer and use it in GitHub Desktop.
Transaction simulator with Ruby
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