From a0ab7bda26aeeb8c02c37a9b3d66435bacbc0e34 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Fri, 8 Nov 2024 02:24:56 -0600 Subject: [PATCH] scripts: Avoid rereading shrub blocks This extends Rbyd.fetch to accept another rbyd, in which case we inherit the RAM-backed block without rereading it from disk. This avoids an issue where shrubs can become corrupted if the disk is being simultaneously written and debugged. Normally we can detect the checksum mismatch and toss out the rbyd during fetch, but shrub pointers don't include a checksum since they assume the containing rbyd has already been checksummed. It's interesting to note this even avoids the memory copy thanks to Python's reference counting. --- scripts/dbgbmap.py | 32 +++++++++++++++++++++----------- scripts/dbgbtree.py | 30 ++++++++++++++++++++---------- scripts/dbglfs.py | 32 +++++++++++++++++++++----------- scripts/dbgmtree.py | 30 ++++++++++++++++++++---------- 4 files changed, 82 insertions(+), 42 deletions(-) diff --git a/scripts/dbgbmap.py b/scripts/dbgbmap.py index eaa0a566..27176369 100755 --- a/scripts/dbgbmap.py +++ b/scripts/dbgbmap.py @@ -616,14 +616,14 @@ class Rbyd: self.trunk) @classmethod - def fetch(cls, f, block_size, blocks, trunk=None, cksum=None): - if isinstance(blocks, int): - blocks = (blocks,) - - if len(blocks) > 1: + def fetch(cls, f, block_size, block, trunk=None, cksum=None): + # multiple blocks? + if (not isinstance(block, int) + and not isinstance(block, Rbyd) + and len(block) > 1): # fetch all blocks rbyds = [cls.fetch(f, block_size, block, trunk, cksum) - for block in blocks] + for block in block] # determine most recent revision i = 0 for i_, rbyd in enumerate(rbyds): @@ -640,17 +640,27 @@ class Rbyd: rbyds[(i+1+j) % len(rbyds)].block for j in range(len(rbyds)-1)) return rbyd + + # block may be an rbyd, in which case we inherit the + # already-read data + # + # this helps avoid race conditions with cksums and shrubs + if isinstance(block, Rbyd): + # inherit the trunk too I guess? + if trunk is None: + trunk = block.trunk + block, data = block.block, block.data else: # block may encode a trunk - block = blocks[0] + block = block[0] if not isinstance(block, int) else block if isinstance(block, tuple): if trunk is None: trunk = block[1] block = block[0] - # seek to the block - f.seek(block * block_size) - data = f.read(block_size) + # seek to the block + f.seek(block * block_size) + data = f.read(block_size) # fetch the rbyd rev = fromle32(data[0:4]) @@ -1262,7 +1272,7 @@ def main(disk, mroots=None, *, # inlined bshrub? elif tag == TAG_BSHRUB: weight, trunk = fromshrub(data) - btree = Rbyd.fetch(f, block_size, mdir.block, trunk) + btree = Rbyd.fetch(f, block_size, mdir, trunk) shrub = True # indirect btree? diff --git a/scripts/dbgbtree.py b/scripts/dbgbtree.py index aacbfa66..332f8717 100755 --- a/scripts/dbgbtree.py +++ b/scripts/dbgbtree.py @@ -288,14 +288,14 @@ class Rbyd: self.trunk) @classmethod - def fetch(cls, f, block_size, blocks, trunk=None, cksum=None): - if isinstance(blocks, int): - blocks = (blocks,) - - if len(blocks) > 1: + def fetch(cls, f, block_size, block, trunk=None, cksum=None): + # multiple blocks? + if (not isinstance(block, int) + and not isinstance(block, Rbyd) + and len(block) > 1): # fetch all blocks rbyds = [cls.fetch(f, block_size, block, trunk, cksum) - for block in blocks] + for block in block] # determine most recent revision i = 0 for i_, rbyd in enumerate(rbyds): @@ -312,17 +312,27 @@ class Rbyd: rbyds[(i+1+j) % len(rbyds)].block for j in range(len(rbyds)-1)) return rbyd + + # block may be an rbyd, in which case we inherit the + # already-read data + # + # this helps avoid race conditions with cksums and shrubs + if isinstance(block, Rbyd): + # inherit the trunk too I guess? + if trunk is None: + trunk = block.trunk + block, data = block.block, block.data else: # block may encode a trunk - block = blocks[0] + block = block[0] if not isinstance(block, int) else block if isinstance(block, tuple): if trunk is None: trunk = block[1] block = block[0] - # seek to the block - f.seek(block * block_size) - data = f.read(block_size) + # seek to the block + f.seek(block * block_size) + data = f.read(block_size) # fetch the rbyd rev = fromle32(data[0:4]) diff --git a/scripts/dbglfs.py b/scripts/dbglfs.py index 484776c4..13d74a3b 100755 --- a/scripts/dbglfs.py +++ b/scripts/dbglfs.py @@ -319,14 +319,14 @@ class Rbyd: self.trunk) @classmethod - def fetch(cls, f, block_size, blocks, trunk=None, cksum=None): - if isinstance(blocks, int): - blocks = (blocks,) - - if len(blocks) > 1: + def fetch(cls, f, block_size, block, trunk=None, cksum=None): + # multiple blocks? + if (not isinstance(block, int) + and not isinstance(block, Rbyd) + and len(block) > 1): # fetch all blocks rbyds = [cls.fetch(f, block_size, block, trunk, cksum) - for block in blocks] + for block in block] # determine most recent revision i = 0 for i_, rbyd in enumerate(rbyds): @@ -343,17 +343,27 @@ class Rbyd: rbyds[(i+1+j) % len(rbyds)].block for j in range(len(rbyds)-1)) return rbyd + + # block may be an rbyd, in which case we inherit the + # already-read data + # + # this helps avoid race conditions with cksums and shrubs + if isinstance(block, Rbyd): + # inherit the trunk too I guess? + if trunk is None: + trunk = block.trunk + block, data = block.block, block.data else: # block may encode a trunk - block = blocks[0] + block = block[0] if not isinstance(block, int) else block if isinstance(block, tuple): if trunk is None: trunk = block[1] block = block[0] - # seek to the block - f.seek(block * block_size) - data = f.read(block_size) + # seek to the block + f.seek(block * block_size) + data = f.read(block_size) # fetch the rbyd rev = fromle32(data[0:4]) @@ -1321,7 +1331,7 @@ def dbg_fstruct(f, block_size, mdir, rid, tag, j, d, data, *, # inlined bshrub? elif tag == TAG_BSHRUB: weight, trunk = fromshrub(data) - btree = Rbyd.fetch(f, block_size, mdir.block, trunk) + btree = Rbyd.fetch(f, block_size, mdir, trunk) w = weight # indirect btree? elif tag == TAG_BTREE: diff --git a/scripts/dbgmtree.py b/scripts/dbgmtree.py index 95223b0d..2b984f5b 100755 --- a/scripts/dbgmtree.py +++ b/scripts/dbgmtree.py @@ -303,14 +303,14 @@ class Rbyd: self.trunk) @classmethod - def fetch(cls, f, block_size, blocks, trunk=None, cksum=None): - if isinstance(blocks, int): - blocks = (blocks,) - - if len(blocks) > 1: + def fetch(cls, f, block_size, block, trunk=None, cksum=None): + # multiple blocks? + if (not isinstance(block, int) + and not isinstance(block, Rbyd) + and len(block) > 1): # fetch all blocks rbyds = [cls.fetch(f, block_size, block, trunk, cksum) - for block in blocks] + for block in block] # determine most recent revision i = 0 for i_, rbyd in enumerate(rbyds): @@ -327,17 +327,27 @@ class Rbyd: rbyds[(i+1+j) % len(rbyds)].block for j in range(len(rbyds)-1)) return rbyd + + # block may be an rbyd, in which case we inherit the + # already-read data + # + # this helps avoid race conditions with cksums and shrubs + if isinstance(block, Rbyd): + # inherit the trunk too I guess? + if trunk is None: + trunk = block.trunk + block, data = block.block, block.data else: # block may encode a trunk - block = blocks[0] + block = block[0] if not isinstance(block, int) else block if isinstance(block, tuple): if trunk is None: trunk = block[1] block = block[0] - # seek to the block - f.seek(block * block_size) - data = f.read(block_size) + # seek to the block + f.seek(block * block_size) + data = f.read(block_size) # fetch the rbyd rev = fromle32(data[0:4])