more robust dbtx dispose

This commit is contained in:
andri lim 2019-02-18 11:02:26 +07:00 committed by zah
parent 7079efcae3
commit 96f29a971d
3 changed files with 204 additions and 7 deletions

View File

@ -34,11 +34,15 @@ type
containsProc: ContainsProc containsProc: ContainsProc
mostInnerTransaction: DbTransaction mostInnerTransaction: DbTransaction
TransactionFlags = enum
Committed
RolledBack
DbTransaction* = ref object DbTransaction* = ref object
db: TrieDatabaseRef db: TrieDatabaseRef
parentTransaction: DbTransaction parentTransaction: DbTransaction
modifications: MemoryLayer modifications: MemoryLayer
committed: bool flags: set[TransactionFlags]
proc put*(db: TrieDatabaseRef, key, val: openarray[byte]) {.gcsafe.} proc put*(db: TrieDatabaseRef, key, val: openarray[byte]) {.gcsafe.}
proc get*(db: TrieDatabaseRef, key: openarray[byte]): Bytes {.gcsafe.} proc get*(db: TrieDatabaseRef, key: openarray[byte]): Bytes {.gcsafe.}
@ -137,24 +141,32 @@ proc rollback*(t: DbTransaction) =
# Transactions should be handled in a strictly nested fashion. # Transactions should be handled in a strictly nested fashion.
# Any child transaction must be committed or rolled-back before # Any child transaction must be committed or rolled-back before
# its parent transactions: # its parent transactions:
doAssert t.db.mostInnerTransaction == t and not t.committed doAssert t.db.mostInnerTransaction == t and
Committed notin t.flags and
RolledBack notin t.flags
t.db.mostInnerTransaction = t.parentTransaction t.db.mostInnerTransaction = t.parentTransaction
t.flags.incl RolledBack
proc commit*(t: DbTransaction) = proc commit*(t: DbTransaction) =
# Transactions should be handled in a strictly nested fashion. # Transactions should be handled in a strictly nested fashion.
# Any child transaction must be committed or rolled-back before # Any child transaction must be committed or rolled-back before
# its parent transactions: # its parent transactions:
doAssert t.db.mostInnerTransaction == t and not t.committed doAssert t.db.mostInnerTransaction == t and
Committed notin t.flags and
RolledBack notin t.flags
t.db.mostInnerTransaction = t.parentTransaction t.db.mostInnerTransaction = t.parentTransaction
t.modifications.commit(t.db) t.modifications.commit(t.db)
t.committed = true t.flags.incl Committed
proc dispose*(t: DbTransaction) {.inline.} = proc dispose*(t: DbTransaction) {.inline.} =
if not t.committed: if Committed notin t.flags and
RolledBack notin t.flags:
t.rollback() t.rollback()
proc safeDispose*(t: DbTransaction) {.inline.} = proc safeDispose*(t: DbTransaction) {.inline.} =
if t != nil and not t.committed: if t != nil and
Committed notin t.flags and
RolledBack notin t.flags:
t.rollback() t.rollback()
proc putImpl[T](db: RootRef, key, val: openarray[byte]) = proc putImpl[T](db: RootRef, key, val: openarray[byte]) =

View File

@ -0,0 +1,179 @@
import
unittest, strutils, sequtils, os,
eth/trie/[db, trie_defs], ./testutils,
eth/rlp/types as rlpTypes
suite "transaction db":
setup:
const
listLength = 30
var
keysA = randList(Bytes, randGen(3, 33), randGen(listLength))
valuesA = randList(Bytes, randGen(5, 77), randGen(listLength))
keysB = randList(Bytes, randGen(3, 33), randGen(listLength))
valuesB = randList(Bytes, randGen(5, 77), randGen(listLength))
proc populateA(db: TrieDatabaseRef) =
for i in 0 ..< listLength:
db.put(keysA[i], valuesA[i])
proc checkContentsA(db: TrieDatabaseRef): bool =
for i in 0 ..< listLength:
let v = db.get(keysA[i])
if v != valuesA[i]: return false
result = true
proc checkEmptyContentsA(db: TrieDatabaseRef): bool {.used.} =
for i in 0 ..< listLength:
let v = db.get(keysA[i])
if v.len != 0: return false
result = true
proc populateB(db: TrieDatabaseRef) {.used.} =
for i in 0 ..< listLength:
db.put(keysB[i], valuesB[i])
proc checkContentsB(db: TrieDatabaseRef): bool {.used.} =
for i in 0 ..< listLength:
let v = db.get(keysB[i])
if v != valuesB[i]: return false
result = true
proc checkEmptyContentsB(db: TrieDatabaseRef): bool {.used.} =
for i in 0 ..< listLength:
let v = db.get(keysB[i])
if v.len != 0: return false
result = true
test "commit":
var db = newMemoryDB()
var tx = db.beginTransaction()
db.populateA()
check checkContentsA(db)
tx.commit()
check checkContentsA(db)
test "rollback":
var db = newMemoryDB()
var tx = db.beginTransaction()
db.populateA()
check checkContentsA(db)
tx.rollback()
check checkEmptyContentsA(db)
test "dispose":
var db = newMemoryDB()
var tx = db.beginTransaction()
db.populateA()
check checkContentsA(db)
tx.dispose()
check checkEmptyContentsA(db)
test "commit dispose":
var db = newMemoryDB()
var tx = db.beginTransaction()
db.populateA()
check checkContentsA(db)
tx.commit()
tx.dispose()
check checkContentsA(db)
test "rollback dispose":
var db = newMemoryDB()
var tx = db.beginTransaction()
db.populateA()
check checkContentsA(db)
tx.rollback()
tx.dispose()
check checkEmptyContentsA(db)
test "dispose dispose":
var db = newMemoryDB()
var tx = db.beginTransaction()
db.populateA()
check checkContentsA(db)
tx.dispose()
tx.dispose()
check checkEmptyContentsA(db)
test "commit commit":
var db = newMemoryDB()
var txA = db.beginTransaction()
db.populateA()
var txB = db.beginTransaction()
db.populateB()
check checkContentsA(db)
check checkContentsB(db)
txB.commit()
txA.commit()
check checkContentsA(db)
check checkContentsB(db)
test "commit rollback":
var db = newMemoryDB()
var txA = db.beginTransaction()
db.populateA()
var txB = db.beginTransaction()
db.populateB()
check checkContentsA(db)
check checkContentsB(db)
txB.rollback()
txA.commit()
check checkContentsA(db)
check checkEmptyContentsB(db)
test "rollback commit":
var db = newMemoryDB()
var txA = db.beginTransaction()
db.populateA()
var txB = db.beginTransaction()
db.populateB()
check checkContentsA(db)
check checkContentsB(db)
txB.commit()
txA.rollback()
check checkEmptyContentsB(db)
check checkEmptyContentsA(db)
test "rollback rollback":
var db = newMemoryDB()
var txA = db.beginTransaction()
db.populateA()
var txB = db.beginTransaction()
db.populateB()
check checkContentsA(db)
check checkContentsB(db)
txB.rollback()
txA.rollback()
check checkEmptyContentsB(db)
check checkEmptyContentsA(db)
test "commit rollback dispose":
var db = newMemoryDB()
var txA = db.beginTransaction()
db.populateA()
var txB = db.beginTransaction()
db.populateB()
check checkContentsA(db)
check checkContentsB(db)
txB.rollback()
txA.commit()
txA.dispose()
check checkContentsA(db)
check checkEmptyContentsB(db)

View File

@ -27,6 +27,11 @@ proc randString*(len: int): string =
for i in 0..<len: for i in 0..<len:
result[i] = rand(255).char result[i] = rand(255).char
proc randBytes*(len: int): Bytes =
result = newSeq[byte](len)
for i in 0..<len:
result[i] = rand(255).byte
proc toBytesRange*(str: string): BytesRange = proc toBytesRange*(str: string): BytesRange =
var s: seq[byte] var s: seq[byte]
if str[0] == '0' and str[1] == 'x': if str[0] == '0' and str[1] == 'x':
@ -44,6 +49,8 @@ proc randPrimitives*[T](val: int): T =
result = val result = val
elif T is BytesRange: elif T is BytesRange:
result = randString(val).toRange result = randString(val).toRange
elif T is Bytes:
result = randBytes(val)
proc randList*(T: typedesc, strGen, listGen: RandGen, unique: bool = true): seq[T] = proc randList*(T: typedesc, strGen, listGen: RandGen, unique: bool = true): seq[T] =
let listLen = listGen.getVal() let listLen = listGen.getVal()
@ -82,4 +89,3 @@ proc genBitVec*(len: int): BitRange =
result = bits(s, len) result = bits(s, len)
for i in 0..<len: for i in 0..<len:
result[i] = rand(2) == 1 result[i] = rand(2) == 1