From 8530a8b7b5f9f27bb5b5c319f285bb5a26470285 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 6 Jan 2023 02:03:50 -0700 Subject: rebuildnodes: Rework to be clear about what went wrong when reading --- .../btrfsinspect/rebuildnodes/rebuild.go | 24 +++------------------- 1 file changed, 3 insertions(+), 21 deletions(-) (limited to 'lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go') diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go index 624441f..d11ca58 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go @@ -91,12 +91,6 @@ func NewRebuilder(ctx context.Context, fs *btrfs.FS, nodeScanResults btrfsinspec return o, nil } -func (o *rebuilder) ioErr(ctx context.Context, err error) { - err = fmt.Errorf("should not happen: i/o error: %w", err) - dlog.Error(ctx, err) - panic(err) -} - func (o *rebuilder) ListRoots() map[btrfsprim.ObjID]containers.Set[btrfsvol.LogicalAddr] { return o.rebuilt.ListRoots() } @@ -174,13 +168,9 @@ func (o *rebuilder) Rebuild(_ctx context.Context) error { return err } itemCtx := dlog.WithField(stepCtx, "btrfsinspect.rebuild-nodes.rebuild.process.item", key) - itemBody, ok := o.rebuilt.Tree(itemCtx, key.TreeID).ReadItem(itemCtx, key.Key) - if !ok { - o.ioErr(itemCtx, fmt.Errorf("could not read previously read item: %v", key)) - } itemChan <- keyAndBody{ keyAndTree: key, - Body: itemBody, + Body: o.rebuilt.Tree(itemCtx, key.TreeID).ReadItem(itemCtx, key.Key), } } return nil @@ -285,11 +275,7 @@ func (o *rebuilder) cbLookupRoot(ctx context.Context, tree btrfsprim.ObjID) (off o.enqueueRetry() return 0, btrfsitem.Root{}, false } - itemBody, ok := o.rebuilt.Tree(ctx, key.TreeID).ReadItem(ctx, key.Key) - if !ok { - o.ioErr(ctx, fmt.Errorf("could not read previously read item: %v", key)) - } - switch itemBody := itemBody.(type) { + switch itemBody := o.rebuilt.Tree(ctx, key.TreeID).ReadItem(ctx, key.Key).(type) { case *btrfsitem.Root: return btrfsprim.Generation(key.Offset), *itemBody, true case *btrfsitem.Error: @@ -310,11 +296,7 @@ func (o *rebuilder) cbLookupUUID(ctx context.Context, uuid btrfsprim.UUID) (id b o.enqueueRetry() return 0, false } - itemBody, ok := o.rebuilt.Tree(ctx, key.TreeID).ReadItem(ctx, key.Key) - if !ok { - o.ioErr(ctx, fmt.Errorf("could not read previously read item: %v", key)) - } - switch itemBody := itemBody.(type) { + switch itemBody := o.rebuilt.Tree(ctx, key.TreeID).ReadItem(ctx, key.Key).(type) { case *btrfsitem.UUIDMap: return itemBody.ObjID, true case *btrfsitem.Error: -- cgit v1.2.3-2-g168b From 5a3785b5f43f17ee8766260732523cf65470fbe8 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 6 Jan 2023 02:09:56 -0700 Subject: rebuildnodes: Explicitly track whether curKey.Key is valid --- .../btrfsinspect/rebuildnodes/rebuild.go | 24 ++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) (limited to 'lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go') diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go index d11ca58..931b9f3 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go @@ -54,7 +54,10 @@ type rebuilder struct { rebuilt *btrees.RebuiltForrest - curKey keyAndTree + curKey struct { + TreeID btrfsprim.ObjID + Key containers.Optional[btrfsprim.Key] + } treeQueue containers.Set[btrfsprim.ObjID] itemQueue containers.Set[keyAndTree] augmentQueue map[btrfsprim.ObjID]*treeAugmentQueue @@ -133,12 +136,12 @@ func (o *rebuilder) Rebuild(_ctx context.Context) error { o.treeQueue = make(containers.Set[btrfsprim.ObjID]) // Because trees can be wildly different sizes, it's impossible to have a meaningful // progress percentage here. - for _, treeID := range maps.SortedKeys(treeQueue) { + o.curKey.Key.OK = false + for _, o.curKey.TreeID = range maps.SortedKeys(treeQueue) { if err := _ctx.Err(); err != nil { return err } - o.curKey = keyAndTree{TreeID: treeID} - o.rebuilt.Tree(stepCtx, treeID) + o.rebuilt.Tree(stepCtx, o.curKey.TreeID) } } runtime.GC() @@ -177,9 +180,11 @@ func (o *rebuilder) Rebuild(_ctx context.Context) error { }) grp.Go("cpu", func(stepCtx context.Context) error { defer progressWriter.Done() + o.curKey.Key.OK = true for item := range itemChan { itemCtx := dlog.WithField(stepCtx, "btrfsinspect.rebuild-nodes.rebuild.process.item", item.keyAndTree) - o.curKey = item.keyAndTree + o.curKey.TreeID = item.TreeID + o.curKey.Key.Val = item.Key handleItem(o, itemCtx, item.TreeID, btrfstree.Item{ Key: item.Key, Body: item.Body, @@ -242,10 +247,13 @@ func (o *rebuilder) Rebuild(_ctx context.Context) error { } func (o *rebuilder) enqueueRetry() { - if o.curKey.Key == (btrfsprim.Key{}) { - o.treeQueue.Insert(o.curKey.TreeID) + if o.curKey.Key.OK { + o.itemQueue.Insert(keyAndTree{ + TreeID: o.curKey.TreeID, + Key: o.curKey.Key.Val, + }) } else { - o.itemQueue.Insert(o.curKey) + o.treeQueue.Insert(o.curKey.TreeID) } } -- cgit v1.2.3-2-g168b From c9549dc423a38839d9f8b919877009abba74c607 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 6 Jan 2023 02:20:29 -0700 Subject: rebuildnodes: Move each substep in to its own method --- .../btrfsinspect/rebuildnodes/rebuild.go | 219 +++++++++++---------- 1 file changed, 117 insertions(+), 102 deletions(-) (limited to 'lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go') diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go index 931b9f3..da3d79f 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go @@ -129,120 +129,135 @@ func (o *rebuilder) Rebuild(_ctx context.Context) error { for passNum := 0; len(o.treeQueue) > 0 || len(o.itemQueue) > 0 || len(o.augmentQueue) > 0; passNum++ { passCtx := dlog.WithField(_ctx, "btrfsinspect.rebuild-nodes.rebuild.pass", passNum) - // Add items to the queue (drain o.treeQueue, fill o.itemQueue) - if true { - stepCtx := dlog.WithField(passCtx, "btrfsinspect.rebuild-nodes.rebuild.substep", "collect-items") - treeQueue := o.treeQueue - o.treeQueue = make(containers.Set[btrfsprim.ObjID]) - // Because trees can be wildly different sizes, it's impossible to have a meaningful - // progress percentage here. - o.curKey.Key.OK = false - for _, o.curKey.TreeID = range maps.SortedKeys(treeQueue) { - if err := _ctx.Err(); err != nil { - return err - } - o.rebuilt.Tree(stepCtx, o.curKey.TreeID) - } + // Crawl trees (Drain o.treeQueue, fill o.itemQueue). + if err := o.processTreeQueue(passCtx); err != nil { + return err } runtime.GC() - // Handle items in the queue (drain o.itemQueue, fill o.augmentQueue and o.treeQueue) - if true { - stepCtx := dlog.WithField(passCtx, "btrfsinspect.rebuild-nodes.rebuild.substep", "process-items") - itemQueue := maps.Keys(o.itemQueue) - o.itemQueue = make(containers.Set[keyAndTree]) - sort.Slice(itemQueue, func(i, j int) bool { - return itemQueue[i].Compare(itemQueue[j]) < 0 - }) - var progress itemStats - progress.D = len(itemQueue) - progressWriter := textui.NewProgress[itemStats](stepCtx, dlog.LogLevelInfo, textui.Tunable(1*time.Second)) - stepCtx = dlog.WithField(stepCtx, "btrfsinspect.rebuild-nodes.rebuild.substep.progress", &progress) - type keyAndBody struct { - keyAndTree - Body btrfsitem.Item + // Process items (drain o.itemQueue, fill o.augmentQueue and o.treeQueue). + if err := o.processItemQueue(passCtx); err != nil { + return err + } + runtime.GC() + + // Apply augments (drain o.augmentQueue, fill o.itemQueue). + if err := o.processAugmentQueue(passCtx); err != nil { + return err + } + runtime.GC() + } + return nil +} + +// processTreeQueue drains o.treeQueue, filling o.itemQueue. +func (o *rebuilder) processTreeQueue(ctx context.Context) error { + ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep", "collect-items") + treeQueue := o.treeQueue + o.treeQueue = make(containers.Set[btrfsprim.ObjID]) + // Because trees can be wildly different sizes, it's impossible to have a meaningful + // progress percentage here. + o.curKey.Key.OK = false + for _, o.curKey.TreeID = range maps.SortedKeys(treeQueue) { + if err := ctx.Err(); err != nil { + return err + } + o.rebuilt.Tree(ctx, o.curKey.TreeID) + } + return nil +} + +// processItemQueue drains o.itemQueue, filling o.augmentQueue and o.treeQueue. +func (o *rebuilder) processItemQueue(ctx context.Context) error { + ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep", "process-items") + itemQueue := maps.Keys(o.itemQueue) + o.itemQueue = make(containers.Set[keyAndTree]) + sort.Slice(itemQueue, func(i, j int) bool { + return itemQueue[i].Compare(itemQueue[j]) < 0 + }) + var progress itemStats + progress.D = len(itemQueue) + progressWriter := textui.NewProgress[itemStats](ctx, dlog.LogLevelInfo, textui.Tunable(1*time.Second)) + ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep.progress", &progress) + type keyAndBody struct { + keyAndTree + Body btrfsitem.Item + } + itemChan := make(chan keyAndBody, textui.Tunable(300)) // average items-per-node≈100; let's have a buffer of ~3 nodes + grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{}) + grp.Go("io", func(ctx context.Context) error { + defer close(itemChan) + for _, key := range itemQueue { + if err := ctx.Err(); err != nil { + return err } - itemChan := make(chan keyAndBody, textui.Tunable(300)) // average items-per-node≈100; let's have a buffer of ~3 nodes - grp := dgroup.NewGroup(stepCtx, dgroup.GroupConfig{}) - grp.Go("io", func(stepCtx context.Context) error { - defer close(itemChan) - for _, key := range itemQueue { - if err := stepCtx.Err(); err != nil { - return err - } - itemCtx := dlog.WithField(stepCtx, "btrfsinspect.rebuild-nodes.rebuild.process.item", key) - itemChan <- keyAndBody{ - keyAndTree: key, - Body: o.rebuilt.Tree(itemCtx, key.TreeID).ReadItem(itemCtx, key.Key), - } - } - return nil - }) - grp.Go("cpu", func(stepCtx context.Context) error { - defer progressWriter.Done() - o.curKey.Key.OK = true - for item := range itemChan { - itemCtx := dlog.WithField(stepCtx, "btrfsinspect.rebuild-nodes.rebuild.process.item", item.keyAndTree) - o.curKey.TreeID = item.TreeID - o.curKey.Key.Val = item.Key - handleItem(o, itemCtx, item.TreeID, btrfstree.Item{ - Key: item.Key, - Body: item.Body, - }) - if item.ItemType == btrfsitem.ROOT_ITEM_KEY { - o.treeQueue.Insert(item.ObjectID) - } - progress.N++ - progress.NumAugments = o.numAugments - progress.NumFailures = o.numAugmentFailures - progress.NumAugmentTrees = len(o.augmentQueue) - progressWriter.Set(progress) - } - return nil + itemCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.process.item", key) + itemChan <- keyAndBody{ + keyAndTree: key, + Body: o.rebuilt.Tree(itemCtx, key.TreeID).ReadItem(itemCtx, key.Key), + } + } + return nil + }) + grp.Go("cpu", func(ctx context.Context) error { + defer progressWriter.Done() + o.curKey.Key.OK = true + for item := range itemChan { + itemCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.process.item", item.keyAndTree) + o.curKey.TreeID = item.TreeID + o.curKey.Key.Val = item.Key + handleItem(o, itemCtx, item.TreeID, btrfstree.Item{ + Key: item.Key, + Body: item.Body, }) - if err := grp.Wait(); err != nil { - return err + if item.ItemType == btrfsitem.ROOT_ITEM_KEY { + o.treeQueue.Insert(item.ObjectID) } + progress.N++ + progress.NumAugments = o.numAugments + progress.NumFailures = o.numAugmentFailures + progress.NumAugmentTrees = len(o.augmentQueue) + progressWriter.Set(progress) } - runtime.GC() + return nil + }) + return grp.Wait() +} - // Apply augments (drain o.augmentQueue, fill o.itemQueue) - if true { - stepCtx := dlog.WithField(passCtx, "btrfsinspect.rebuild-nodes.rebuild.substep", "apply-augments") - resolvedAugments := make(map[btrfsprim.ObjID]containers.Set[btrfsvol.LogicalAddr], len(o.augmentQueue)) - var progress textui.Portion[int] - for _, treeID := range maps.SortedKeys(o.augmentQueue) { - if err := _ctx.Err(); err != nil { - return err - } - treeCtx := dlog.WithField(stepCtx, "btrfsinspect.rebuild-nodes.rebuild.augment.tree", treeID) - resolvedAugments[treeID] = o.resolveTreeAugments(treeCtx, treeID) - progress.D += len(resolvedAugments[treeID]) - } - o.augmentQueue = make(map[btrfsprim.ObjID]*treeAugmentQueue) - o.numAugments = 0 - o.numAugmentFailures = 0 - runtime.GC() - progressWriter := textui.NewProgress[textui.Portion[int]](stepCtx, dlog.LogLevelInfo, textui.Tunable(1*time.Second)) - stepCtx = dlog.WithField(stepCtx, "btrfsinspect.rebuild-nodes.rebuild.substep.progress", &progress) - for _, treeID := range maps.SortedKeys(resolvedAugments) { - treeCtx := dlog.WithField(stepCtx, "btrfsinspect.rebuild-nodes.rebuild.augment.tree", treeID) - for _, nodeAddr := range maps.SortedKeys(resolvedAugments[treeID]) { - if err := _ctx.Err(); err != nil { - progressWriter.Set(progress) - progressWriter.Done() - return err - } - progressWriter.Set(progress) - o.rebuilt.Tree(treeCtx, treeID).AddRoot(treeCtx, nodeAddr) - progress.N++ - } +// processAugmentQueue drains o.augmentQueue, filling o.itemQueue. +func (o *rebuilder) processAugmentQueue(ctx context.Context) error { + ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep", "apply-augments") + resolvedAugments := make(map[btrfsprim.ObjID]containers.Set[btrfsvol.LogicalAddr], len(o.augmentQueue)) + var progress textui.Portion[int] + for _, treeID := range maps.SortedKeys(o.augmentQueue) { + if err := ctx.Err(); err != nil { + return err + } + treeCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.augment.tree", treeID) + resolvedAugments[treeID] = o.resolveTreeAugments(treeCtx, treeID) + progress.D += len(resolvedAugments[treeID]) + } + o.augmentQueue = make(map[btrfsprim.ObjID]*treeAugmentQueue) + o.numAugments = 0 + o.numAugmentFailures = 0 + runtime.GC() + progressWriter := textui.NewProgress[textui.Portion[int]](ctx, dlog.LogLevelInfo, textui.Tunable(1*time.Second)) + ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep.progress", &progress) + for _, treeID := range maps.SortedKeys(resolvedAugments) { + treeCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.augment.tree", treeID) + for _, nodeAddr := range maps.SortedKeys(resolvedAugments[treeID]) { + if err := ctx.Err(); err != nil { + progressWriter.Set(progress) + progressWriter.Done() + return err } progressWriter.Set(progress) - progressWriter.Done() + o.rebuilt.Tree(treeCtx, treeID).AddRoot(treeCtx, nodeAddr) + progress.N++ } - runtime.GC() } + progressWriter.Set(progress) + progressWriter.Done() return nil } -- cgit v1.2.3-2-g168b From 0ffd1a5540201c4b0161b7fea32564adff01f4b2 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 6 Jan 2023 02:49:05 -0700 Subject: rebuildnodes: Having the treeAugmentQueue instance be named treeQueue is confusing Since there's also Rebuilder.treeQueue. --- .../btrfsinspect/rebuildnodes/rebuild.go | 48 +++++++++++----------- 1 file changed, 24 insertions(+), 24 deletions(-) (limited to 'lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go') diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go index da3d79f..a088d84 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go @@ -498,21 +498,21 @@ func shortenWantKey(wantKey string) string { return wantKey[sp+1:] } -func (treeQueue *treeAugmentQueue) has(wantKey string) bool { - if treeQueue != nil { +func (queue *treeAugmentQueue) has(wantKey string) bool { + if queue != nil { wantKey = shortenWantKey(wantKey) - if treeQueue.zero != nil { - if _, ok := treeQueue.zero[wantKey]; ok { + if queue.zero != nil { + if _, ok := queue.zero[wantKey]; ok { return true } } - if treeQueue.single != nil { - if _, ok := treeQueue.single[wantKey]; ok { + if queue.single != nil { + if _, ok := queue.single[wantKey]; ok { return true } } - if treeQueue.multi != nil { - if _, ok := treeQueue.multi[wantKey]; ok { + if queue.multi != nil { + if _, ok := queue.multi[wantKey]; ok { return true } } @@ -520,37 +520,37 @@ func (treeQueue *treeAugmentQueue) has(wantKey string) bool { return false } -func (treeQueue *treeAugmentQueue) store(wantKey string, choices containers.Set[btrfsvol.LogicalAddr]) { +func (queue *treeAugmentQueue) store(wantKey string, choices containers.Set[btrfsvol.LogicalAddr]) { if len(choices) == 0 && (strings.Contains(wantKey, " name=") || strings.Contains(wantKey, "-")) { // This wantKey is unlikely to come up again, so it's not worth storing a negative result. return } wantKey = shortenWantKey(wantKey) - beg := treeQueue.keyBuf.Len() - if treeQueue.keyBuf.Cap()-beg < len(wantKey) { - treeQueue.keyBuf.Reset() - treeQueue.keyBuf.Grow(textui.Tunable(4096)) + beg := queue.keyBuf.Len() + if queue.keyBuf.Cap()-beg < len(wantKey) { + queue.keyBuf.Reset() + queue.keyBuf.Grow(textui.Tunable(4096)) beg = 0 } - treeQueue.keyBuf.WriteString(wantKey) - wantKey = treeQueue.keyBuf.String()[beg:] + queue.keyBuf.WriteString(wantKey) + wantKey = queue.keyBuf.String()[beg:] switch len(choices) { case 0: - if treeQueue.zero == nil { - treeQueue.zero = make(map[string]struct{}) + if queue.zero == nil { + queue.zero = make(map[string]struct{}) } - treeQueue.zero[wantKey] = struct{}{} + queue.zero[wantKey] = struct{}{} case 1: - if treeQueue.single == nil { - treeQueue.single = make(map[string]btrfsvol.LogicalAddr) + if queue.single == nil { + queue.single = make(map[string]btrfsvol.LogicalAddr) } - treeQueue.single[wantKey] = choices.TakeOne() + queue.single[wantKey] = choices.TakeOne() default: - if treeQueue.multi == nil { - treeQueue.multi = make(map[string]containers.Set[btrfsvol.LogicalAddr]) + if queue.multi == nil { + queue.multi = make(map[string]containers.Set[btrfsvol.LogicalAddr]) } - treeQueue.multi[wantKey] = choices + queue.multi[wantKey] = choices } } -- cgit v1.2.3-2-g168b From 6f3ec83248920f5638108fc672696996459b6beb Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 6 Jan 2023 02:51:59 -0700 Subject: rebuildnodes: Split rebuild.go in to separate files --- .../btrfsinspect/rebuildnodes/rebuild.go | 431 --------------------- 1 file changed, 431 deletions(-) (limited to 'lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go') diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go index a088d84..902d810 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go @@ -5,7 +5,6 @@ package rebuildnodes import ( - "bytes" "context" "fmt" "runtime" @@ -19,7 +18,6 @@ import ( "git.lukeshu.com/btrfs-progs-ng/lib/btrfs" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsitem" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsprim" - "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfssum" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfstree" "git.lukeshu.com/btrfs-progs-ng/lib/btrfs/btrfsvol" "git.lukeshu.com/btrfs-progs-ng/lib/btrfsprogs/btrfsinspect" @@ -272,66 +270,6 @@ func (o *rebuilder) enqueueRetry() { } } -func (o *rebuilder) cbAddedItem(ctx context.Context, tree btrfsprim.ObjID, key btrfsprim.Key) { - if handleWouldBeNoOp(key.ItemType) { - return - } - o.itemQueue.Insert(keyAndTree{ - TreeID: tree, - Key: key, - }) -} - -func (o *rebuilder) cbLookupRoot(ctx context.Context, tree btrfsprim.ObjID) (offset btrfsprim.Generation, item btrfsitem.Root, ok bool) { - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.add-tree.want.reason", "tree Root") - key := keyAndTree{ - TreeID: btrfsprim.ROOT_TREE_OBJECTID, - Key: btrfsprim.Key{ - ObjectID: tree, - ItemType: btrfsitem.ROOT_ITEM_KEY, - }, - } - wantKey := fmt.Sprintf("tree=%v key={%v %v ?}", key.TreeID, key.ObjectID, key.ItemType) - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.add-tree.want.key", wantKey) - key.Key, ok = o._want(ctx, key.TreeID, wantKey, key.ObjectID, key.ItemType) - if !ok { - o.enqueueRetry() - return 0, btrfsitem.Root{}, false - } - switch itemBody := o.rebuilt.Tree(ctx, key.TreeID).ReadItem(ctx, key.Key).(type) { - case *btrfsitem.Root: - return btrfsprim.Generation(key.Offset), *itemBody, true - case *btrfsitem.Error: - o.fsErr(ctx, fmt.Errorf("error decoding item: %v: %w", key, itemBody.Err)) - return 0, btrfsitem.Root{}, false - default: - // This is a panic because the item decoder should not emit ROOT_ITEM items as anything but - // btrfsitem.Root or btrfsitem.Error without this code also being updated. - panic(fmt.Errorf("should not happen: ROOT_ITEM item has unexpected type: %T", itemBody)) - } -} - -func (o *rebuilder) cbLookupUUID(ctx context.Context, uuid btrfsprim.UUID) (id btrfsprim.ObjID, ok bool) { - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.add-tree.want.reason", "resolve parent UUID") - key := keyAndTree{TreeID: btrfsprim.UUID_TREE_OBJECTID, Key: btrfsitem.UUIDToKey(uuid)} - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.add-tree.want.key", key.String()) - if !o._wantOff(ctx, key.TreeID, key.String(), key.Key) { - o.enqueueRetry() - return 0, false - } - switch itemBody := o.rebuilt.Tree(ctx, key.TreeID).ReadItem(ctx, key.Key).(type) { - case *btrfsitem.UUIDMap: - return itemBody.ObjID, true - case *btrfsitem.Error: - o.fsErr(ctx, fmt.Errorf("error decoding item: %v: %w", key, itemBody.Err)) - return 0, false - default: - // This is a panic because the item decoder should not emit UUID_SUBVOL items as anything but - // btrfsitem.UUIDMap or btrfsitem.Error without this code also being updated. - panic(fmt.Errorf("should not happen: UUID_SUBVOL item has unexpected type: %T", itemBody)) - } -} - func (o *rebuilder) resolveTreeAugments(ctx context.Context, treeID btrfsprim.ObjID) containers.Set[btrfsvol.LogicalAddr] { // Define an algorithm that takes several lists of items, and returns a // set of those items such that each input list contains zero or one of @@ -571,372 +509,3 @@ func (o *rebuilder) wantAugment(ctx context.Context, treeID btrfsprim.ObjID, wan o.numAugments++ } } - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// fsErr implements rebuildCallbacks. -func (o *rebuilder) fsErr(ctx context.Context, e error) { - dlog.Errorf(ctx, "filesystem error: %v", e) -} - -// want implements rebuildCallbacks. -func (o *rebuilder) want(ctx context.Context, reason string, treeID btrfsprim.ObjID, objID btrfsprim.ObjID, typ btrfsprim.ItemType) { - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.reason", reason) - wantKey := fmt.Sprintf("tree=%v key={%v %v ?}", treeID, objID, typ) - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.key", wantKey) - o._want(ctx, treeID, wantKey, objID, typ) -} - -func (o *rebuilder) _want(ctx context.Context, treeID btrfsprim.ObjID, wantKey string, objID btrfsprim.ObjID, typ btrfsprim.ItemType) (key btrfsprim.Key, ok bool) { - if o.rebuilt.Tree(ctx, treeID) == nil { - o.enqueueRetry() - return btrfsprim.Key{}, false - } - - // check if we already have it - - tgt := btrfsprim.Key{ - ObjectID: objID, - ItemType: typ, - } - if key, _, ok := o.rebuilt.Tree(ctx, treeID).Items(ctx).Search(func(key btrfsprim.Key, _ keyio.ItemPtr) int { - key.Offset = 0 - return tgt.Compare(key) - }); ok { - return key, true - } - - // OK, we need to insert it - - if o.hasAugment(treeID, wantKey) { - return btrfsprim.Key{}, false - } - wants := make(containers.Set[btrfsvol.LogicalAddr]) - o.rebuilt.Tree(ctx, treeID).PotentialItems(ctx).Subrange( - func(k btrfsprim.Key, _ keyio.ItemPtr) int { k.Offset = 0; return tgt.Compare(k) }, - func(_ btrfsprim.Key, v keyio.ItemPtr) bool { - wants.InsertFrom(o.rebuilt.Tree(ctx, treeID).LeafToRoots(ctx, v.Node)) - return true - }) - o.wantAugment(ctx, treeID, wantKey, wants) - return btrfsprim.Key{}, false -} - -// wantOff implements rebuildCallbacks. -func (o *rebuilder) wantOff(ctx context.Context, reason string, treeID btrfsprim.ObjID, objID btrfsprim.ObjID, typ btrfsprim.ItemType, off uint64) { - key := btrfsprim.Key{ - ObjectID: objID, - ItemType: typ, - Offset: off, - } - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.reason", reason) - wantKey := keyAndTree{TreeID: treeID, Key: key}.String() - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.key", wantKey) - o._wantOff(ctx, treeID, wantKey, key) -} - -func (o *rebuilder) _wantOff(ctx context.Context, treeID btrfsprim.ObjID, wantKey string, tgt btrfsprim.Key) (ok bool) { - if o.rebuilt.Tree(ctx, treeID) == nil { - o.enqueueRetry() - return false - } - - // check if we already have it - - if _, ok := o.rebuilt.Tree(ctx, treeID).Items(ctx).Load(tgt); ok { - return true - } - - // OK, we need to insert it - - if o.hasAugment(treeID, wantKey) { - return false - } - wants := make(containers.Set[btrfsvol.LogicalAddr]) - o.rebuilt.Tree(ctx, treeID).PotentialItems(ctx).Subrange( - func(k btrfsprim.Key, _ keyio.ItemPtr) int { return tgt.Compare(k) }, - func(_ btrfsprim.Key, v keyio.ItemPtr) bool { - wants.InsertFrom(o.rebuilt.Tree(ctx, treeID).LeafToRoots(ctx, v.Node)) - return true - }) - o.wantAugment(ctx, treeID, wantKey, wants) - return false -} - -func (o *rebuilder) _wantFunc(ctx context.Context, treeID btrfsprim.ObjID, wantKey string, objID btrfsprim.ObjID, typ btrfsprim.ItemType, fn func(keyio.ItemPtr) bool) { - if o.rebuilt.Tree(ctx, treeID) == nil { - o.enqueueRetry() - return - } - - // check if we already have it - - tgt := btrfsprim.Key{ - ObjectID: objID, - ItemType: typ, - } - found := false - o.rebuilt.Tree(ctx, treeID).Items(ctx).Subrange( - func(key btrfsprim.Key, _ keyio.ItemPtr) int { - key.Offset = 0 - return tgt.Compare(key) - }, - func(_ btrfsprim.Key, itemPtr keyio.ItemPtr) bool { - if fn(itemPtr) { - found = true - } - return !found - }) - if found { - return - } - - // OK, we need to insert it - - if o.hasAugment(treeID, wantKey) { - return - } - wants := make(containers.Set[btrfsvol.LogicalAddr]) - o.rebuilt.Tree(ctx, treeID).PotentialItems(ctx).Subrange( - func(k btrfsprim.Key, _ keyio.ItemPtr) int { k.Offset = 0; return tgt.Compare(k) }, - func(k btrfsprim.Key, v keyio.ItemPtr) bool { - if fn(v) { - wants.InsertFrom(o.rebuilt.Tree(ctx, treeID).LeafToRoots(ctx, v.Node)) - } - return true - }) - o.wantAugment(ctx, treeID, wantKey, wants) -} - -// wantDirIndex implements rebuildCallbacks. -func (o *rebuilder) wantDirIndex(ctx context.Context, reason string, treeID btrfsprim.ObjID, objID btrfsprim.ObjID, name []byte) { - typ := btrfsitem.DIR_INDEX_KEY - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.reason", reason) - wantKey := fmt.Sprintf("tree=%v key={%v %v ?} name=%q", treeID, objID, typ, name) - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.key", wantKey) - o._wantFunc(ctx, treeID, wantKey, objID, typ, func(ptr keyio.ItemPtr) bool { - itemName, ok := o.keyIO.Names[ptr] - return ok && bytes.Equal(itemName, name) - }) -} - -func (o *rebuilder) _walkRange( - ctx context.Context, - items *containers.SortedMap[btrfsprim.Key, keyio.ItemPtr], - treeID, objID btrfsprim.ObjID, typ btrfsprim.ItemType, - beg, end uint64, - fn func(key btrfsprim.Key, ptr keyio.ItemPtr, beg, end uint64), -) { - min := btrfsprim.Key{ - ObjectID: objID, - ItemType: typ, - Offset: 0, // *NOT* `beg` - } - max := btrfsprim.Key{ - ObjectID: objID, - ItemType: typ, - Offset: end - 1, - } - items.Subrange( - func(runKey btrfsprim.Key, _ keyio.ItemPtr) int { - switch { - case min.Compare(runKey) < 0: - return 1 - case max.Compare(runKey) > 0: - return -1 - default: - return 0 - } - }, - func(runKey btrfsprim.Key, runPtr keyio.ItemPtr) bool { - runSizeAndErr, ok := o.keyIO.Sizes[runPtr] - if !ok { - panic(fmt.Errorf("should not happen: %v (%v) did not have a size recorded", - runPtr, keyAndTree{TreeID: treeID, Key: runKey})) - } - if runSizeAndErr.Err != nil { - o.fsErr(ctx, fmt.Errorf("get size: %v (%v): %w", - runPtr, keyAndTree{TreeID: treeID, Key: runKey}, - runSizeAndErr.Err)) - return true - } - runSize := runSizeAndErr.Size - if runSize == 0 { - return true - } - runBeg := runKey.Offset - runEnd := runBeg + runSize - if runEnd <= beg { - return true - } - - fn(runKey, runPtr, runBeg, runEnd) - return true - }) -} - -type gap struct { - // range is [Beg,End) - Beg, End uint64 -} - -// Compare implements containers.Ordered. -func (a gap) Compare(b gap) int { - return containers.NativeCompare(a.Beg, b.Beg) -} - -func (o *rebuilder) _wantRange( - ctx context.Context, - treeID btrfsprim.ObjID, objID btrfsprim.ObjID, typ btrfsprim.ItemType, - beg, end uint64, -) { - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.key", - fmt.Sprintf("tree=%v key={%v %v ?}", treeID, objID, typ)) - - if o.rebuilt.Tree(ctx, treeID) == nil { - o.enqueueRetry() - return - } - - // Step 1: Build a listing of the gaps. - // - // Start with a gap of the whole range, then subtract each run - // from it. - gaps := new(containers.RBTree[gap]) - gaps.Insert(gap{ - Beg: beg, - End: end, - }) - o._walkRange( - ctx, - o.rebuilt.Tree(ctx, treeID).Items(ctx), - treeID, objID, typ, beg, end, - func(runKey btrfsprim.Key, _ keyio.ItemPtr, runBeg, runEnd uint64) { - var overlappingGaps []*containers.RBNode[gap] - gaps.Subrange( - func(gap gap) int { - switch { - case gap.End <= runBeg: - return 1 - case runEnd <= gap.Beg: - return -1 - default: - return 0 - } - }, - func(node *containers.RBNode[gap]) bool { - overlappingGaps = append(overlappingGaps, node) - return true - }) - if len(overlappingGaps) == 0 { - return - } - gapsBeg := overlappingGaps[0].Value.Beg - gapsEnd := overlappingGaps[len(overlappingGaps)-1].Value.End - for _, gap := range overlappingGaps { - gaps.Delete(gap) - } - if gapsBeg < runBeg { - gaps.Insert(gap{ - Beg: gapsBeg, - End: runBeg, - }) - } - if gapsEnd > runEnd { - gaps.Insert(gap{ - Beg: runEnd, - End: gapsEnd, - }) - } - }) - - // Step 2: Fill each gap. - if gaps.Len() == 0 { - return - } - potentialItems := o.rebuilt.Tree(ctx, treeID).PotentialItems(ctx) - gaps.Range(func(rbNode *containers.RBNode[gap]) bool { - gap := rbNode.Value - last := gap.Beg - o._walkRange( - ctx, - potentialItems, - treeID, objID, typ, gap.Beg, gap.End, - func(k btrfsprim.Key, v keyio.ItemPtr, runBeg, runEnd uint64) { - // TODO: This is dumb and greedy. - if last < runBeg { - // log an error - wantKey := fmt.Sprintf("tree=%v key={%v %v %v-%v}", treeID, objID, typ, last, runBeg) - if !o.hasAugment(treeID, wantKey) { - wantCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.key", wantKey) - o.wantAugment(wantCtx, treeID, wantKey, nil) - } - } - wantKey := fmt.Sprintf("tree=%v key={%v %v %v-%v}", treeID, objID, typ, gap.Beg, gap.End) - if !o.hasAugment(treeID, wantKey) { - wantCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.key", wantKey) - o.wantAugment(wantCtx, treeID, wantKey, o.rebuilt.Tree(wantCtx, treeID).LeafToRoots(wantCtx, v.Node)) - } - last = runEnd - }) - if last < gap.End { - // log an error - wantKey := fmt.Sprintf("tree=%v key={%v, %v, %v-%v}", treeID, objID, typ, last, gap.End) - if !o.hasAugment(treeID, wantKey) { - wantCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.key", wantKey) - o.wantAugment(wantCtx, treeID, wantKey, nil) - } - } - return true - }) -} - -// func implements rebuildCallbacks. -// -// interval is [beg, end) -func (o *rebuilder) wantCSum(ctx context.Context, reason string, inodeTree, inode btrfsprim.ObjID, beg, end btrfsvol.LogicalAddr) { - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.reason", reason) - - inodeKey := keyAndTree{ - TreeID: inodeTree, - Key: btrfsprim.Key{ - ObjectID: inode, - ItemType: btrfsitem.INODE_ITEM_KEY, - Offset: 0, - }, - } - inodeCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.key", inodeKey.String()) - if !o._wantOff(inodeCtx, inodeKey.TreeID, inodeKey.String(), inodeKey.Key) { - o.enqueueRetry() - return - } - inodePtr, ok := o.rebuilt.Tree(inodeCtx, inodeKey.TreeID).Items(inodeCtx).Load(inodeKey.Key) - if !ok { - panic(fmt.Errorf("should not happen: could not load key: %v", inodeKey)) - } - inodeFlags, ok := o.keyIO.Flags[inodePtr] - if !ok { - panic(fmt.Errorf("should not happen: INODE_ITEM did not have flags recorded")) - } - if inodeFlags.Err != nil { - o.fsErr(inodeCtx, inodeFlags.Err) - return - } - - if inodeFlags.NoDataSum { - return - } - - beg = roundDown(beg, btrfssum.BlockSize) - end = roundUp(end, btrfssum.BlockSize) - const treeID = btrfsprim.CSUM_TREE_OBJECTID - o._wantRange(ctx, treeID, btrfsprim.EXTENT_CSUM_OBJECTID, btrfsprim.EXTENT_CSUM_KEY, - uint64(beg), uint64(end)) -} - -// wantFileExt implements rebuildCallbacks. -func (o *rebuilder) wantFileExt(ctx context.Context, reason string, treeID btrfsprim.ObjID, ino btrfsprim.ObjID, size int64) { - ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.want.reason", reason) - o._wantRange(ctx, treeID, ino, btrfsprim.EXTENT_DATA_KEY, - 0, uint64(size)) -} -- cgit v1.2.3-2-g168b From e62111bae947070586aa5ca3c98f7ec13ba3ab88 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 6 Jan 2023 04:16:10 -0700 Subject: rebuildnodes: Don't have wantKeys be stringly-typed --- .../btrfsinspect/rebuildnodes/rebuild.go | 62 +++++++--------------- 1 file changed, 20 insertions(+), 42 deletions(-) (limited to 'lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go') diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go index 902d810..6d610ee 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go @@ -9,7 +9,6 @@ import ( "fmt" "runtime" "sort" - "strings" "time" "github.com/datawire/dlib/dgroup" @@ -64,10 +63,9 @@ type rebuilder struct { } type treeAugmentQueue struct { - keyBuf strings.Builder - zero map[string]struct{} - single map[string]btrfsvol.LogicalAddr - multi map[string]containers.Set[btrfsvol.LogicalAddr] + zero map[Want]struct{} + single map[Want]btrfsvol.LogicalAddr + multi map[Want]containers.Set[btrfsvol.LogicalAddr] } type Rebuilder interface { @@ -401,7 +399,9 @@ func (o *rebuilder) resolveTreeAugments(ctx context.Context, treeID btrfsprim.Ob wantKeys := append( maps.Keys(o.augmentQueue[treeID].single), maps.Keys(o.augmentQueue[treeID].multi)...) - sort.Strings(wantKeys) + sort.Slice(wantKeys, func(i, j int) bool { + return wantKeys[i].Cmp(wantKeys[j]) < 0 + }) for _, wantKey := range wantKeys { list, ok := o.augmentQueue[treeID].multi[wantKey] if !ok { @@ -418,27 +418,14 @@ func (o *rebuilder) resolveTreeAugments(ctx context.Context, treeID btrfsprim.Ob // Free some memory o.augmentQueue[treeID].single = nil o.augmentQueue[treeID].multi = nil - o.augmentQueue[treeID].keyBuf.Reset() return ret } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -func shortenWantKey(wantKey string) string { - if !strings.HasPrefix(wantKey, "tree=") { - panic("should not happen") - } - sp := strings.IndexByte(wantKey, ' ') - if sp < 0 { - panic("should not happen") - } - return wantKey[sp+1:] -} - -func (queue *treeAugmentQueue) has(wantKey string) bool { +func (queue *treeAugmentQueue) has(wantKey Want) bool { if queue != nil { - wantKey = shortenWantKey(wantKey) if queue.zero != nil { if _, ok := queue.zero[wantKey]; ok { return true @@ -458,49 +445,40 @@ func (queue *treeAugmentQueue) has(wantKey string) bool { return false } -func (queue *treeAugmentQueue) store(wantKey string, choices containers.Set[btrfsvol.LogicalAddr]) { - if len(choices) == 0 && (strings.Contains(wantKey, " name=") || strings.Contains(wantKey, "-")) { - // This wantKey is unlikely to come up again, so it's not worth storing a negative result. +func (queue *treeAugmentQueue) store(wantKey Want, choices containers.Set[btrfsvol.LogicalAddr]) { + if len(choices) == 0 && wantKey.OffsetType > offsetExact { + // This wantKey is unlikely to come up again, so it's + // not worth the RAM of storing a negative result. return } - wantKey = shortenWantKey(wantKey) - beg := queue.keyBuf.Len() - if queue.keyBuf.Cap()-beg < len(wantKey) { - queue.keyBuf.Reset() - queue.keyBuf.Grow(textui.Tunable(4096)) - beg = 0 - } - queue.keyBuf.WriteString(wantKey) - wantKey = queue.keyBuf.String()[beg:] - switch len(choices) { case 0: if queue.zero == nil { - queue.zero = make(map[string]struct{}) + queue.zero = make(map[Want]struct{}) } queue.zero[wantKey] = struct{}{} case 1: if queue.single == nil { - queue.single = make(map[string]btrfsvol.LogicalAddr) + queue.single = make(map[Want]btrfsvol.LogicalAddr) } queue.single[wantKey] = choices.TakeOne() default: if queue.multi == nil { - queue.multi = make(map[string]containers.Set[btrfsvol.LogicalAddr]) + queue.multi = make(map[Want]containers.Set[btrfsvol.LogicalAddr]) } queue.multi[wantKey] = choices } } -func (o *rebuilder) hasAugment(treeID btrfsprim.ObjID, wantKey string) bool { - return o.augmentQueue[treeID].has(wantKey) +func (o *rebuilder) hasAugment(wantKey WantWithTree) bool { + return o.augmentQueue[wantKey.TreeID].has(wantKey.Key) } -func (o *rebuilder) wantAugment(ctx context.Context, treeID btrfsprim.ObjID, wantKey string, choices containers.Set[btrfsvol.LogicalAddr]) { - if o.augmentQueue[treeID] == nil { - o.augmentQueue[treeID] = new(treeAugmentQueue) +func (o *rebuilder) wantAugment(ctx context.Context, wantKey WantWithTree, choices containers.Set[btrfsvol.LogicalAddr]) { + if o.augmentQueue[wantKey.TreeID] == nil { + o.augmentQueue[wantKey.TreeID] = new(treeAugmentQueue) } - o.augmentQueue[treeID].store(wantKey, choices) + o.augmentQueue[wantKey.TreeID].store(wantKey.Key, choices) if len(choices) == 0 { dlog.Error(ctx, "could not find wanted item") o.numAugmentFailures++ -- cgit v1.2.3-2-g168b From a6aa47f2eb6cceb83b77477d6f9bd5f25561911f Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 6 Jan 2023 04:26:37 -0700 Subject: rebuildnodes/btrees: Take a Callbacks interface, instead of func pointers --- lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go') diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go index 6d610ee..ffa8d59 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go @@ -85,8 +85,7 @@ func NewRebuilder(ctx context.Context, fs *btrfs.FS, nodeScanResults btrfsinspec graph: nodeGraph, keyIO: keyIO, } - o.rebuilt = btrees.NewRebuiltForrest(sb, nodeGraph, keyIO, - o.cbAddedItem, o.cbLookupRoot, o.cbLookupUUID) + o.rebuilt = btrees.NewRebuiltForrest(sb, nodeGraph, keyIO, o) return o, nil } -- cgit v1.2.3-2-g168b From a4eb75f4a869b0737c919968fec5b90d9d5b1af7 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 6 Jan 2023 13:13:08 -0700 Subject: rebuildnodes: rebuild.go: Tidy up --- .../btrfsinspect/rebuildnodes/rebuild.go | 79 ++++++++++++---------- 1 file changed, 45 insertions(+), 34 deletions(-) (limited to 'lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go') diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go index ffa8d59..472f6cd 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go @@ -93,21 +93,8 @@ func (o *rebuilder) ListRoots() map[btrfsprim.ObjID]containers.Set[btrfsvol.Logi return o.rebuilt.ListRoots() } -type itemStats struct { - textui.Portion[int] - NumAugments int - NumFailures int - NumAugmentTrees int -} - -func (s itemStats) String() string { - // return textui.Sprintf("%v (queued %v augments and %v failures across %v trees)", - return textui.Sprintf("%v (aug:%v fail:%v trees:%v)", - s.Portion, s.NumAugments, s.NumFailures, s.NumAugmentTrees) -} - -func (o *rebuilder) Rebuild(_ctx context.Context) error { - _ctx = dlog.WithField(_ctx, "btrfsinspect.rebuild-nodes.step", "rebuild") +func (o *rebuilder) Rebuild(ctx context.Context) error { + ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.step", "rebuild") // Initialize o.itemQueue = make(containers.Set[keyAndTree]) @@ -121,59 +108,80 @@ func (o *rebuilder) Rebuild(_ctx context.Context) error { btrfsprim.BLOCK_GROUP_TREE_OBJECTID, ) + // Run for passNum := 0; len(o.treeQueue) > 0 || len(o.itemQueue) > 0 || len(o.augmentQueue) > 0; passNum++ { - passCtx := dlog.WithField(_ctx, "btrfsinspect.rebuild-nodes.rebuild.pass", passNum) + ctx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.pass", passNum) // Crawl trees (Drain o.treeQueue, fill o.itemQueue). - if err := o.processTreeQueue(passCtx); err != nil { + if err := o.processTreeQueue(ctx); err != nil { return err } runtime.GC() // Process items (drain o.itemQueue, fill o.augmentQueue and o.treeQueue). - if err := o.processItemQueue(passCtx); err != nil { + if err := o.processItemQueue(ctx); err != nil { return err } runtime.GC() // Apply augments (drain o.augmentQueue, fill o.itemQueue). - if err := o.processAugmentQueue(passCtx); err != nil { + if err := o.processAugmentQueue(ctx); err != nil { return err } runtime.GC() } + return nil } // processTreeQueue drains o.treeQueue, filling o.itemQueue. func (o *rebuilder) processTreeQueue(ctx context.Context) error { ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep", "collect-items") - treeQueue := o.treeQueue + + queue := maps.SortedKeys(o.treeQueue) o.treeQueue = make(containers.Set[btrfsprim.ObjID]) + // Because trees can be wildly different sizes, it's impossible to have a meaningful // progress percentage here. o.curKey.Key.OK = false - for _, o.curKey.TreeID = range maps.SortedKeys(treeQueue) { + for _, o.curKey.TreeID = range queue { if err := ctx.Err(); err != nil { return err } o.rebuilt.Tree(ctx, o.curKey.TreeID) } + return nil } +type itemStats struct { + textui.Portion[int] + NumAugments int + NumFailures int + NumAugmentTrees int +} + +func (s itemStats) String() string { + // return textui.Sprintf("%v (queued %v augments and %v failures across %v trees)", + return textui.Sprintf("%v (aug:%v fail:%v trees:%v)", + s.Portion, s.NumAugments, s.NumFailures, s.NumAugmentTrees) +} + // processItemQueue drains o.itemQueue, filling o.augmentQueue and o.treeQueue. func (o *rebuilder) processItemQueue(ctx context.Context) error { ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep", "process-items") - itemQueue := maps.Keys(o.itemQueue) + + queue := maps.Keys(o.itemQueue) o.itemQueue = make(containers.Set[keyAndTree]) - sort.Slice(itemQueue, func(i, j int) bool { - return itemQueue[i].Compare(itemQueue[j]) < 0 + sort.Slice(queue, func(i, j int) bool { + return queue[i].Compare(queue[j]) < 0 }) + var progress itemStats - progress.D = len(itemQueue) + progress.D = len(queue) progressWriter := textui.NewProgress[itemStats](ctx, dlog.LogLevelInfo, textui.Tunable(1*time.Second)) ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep.progress", &progress) + type keyAndBody struct { keyAndTree Body btrfsitem.Item @@ -182,14 +190,14 @@ func (o *rebuilder) processItemQueue(ctx context.Context) error { grp := dgroup.NewGroup(ctx, dgroup.GroupConfig{}) grp.Go("io", func(ctx context.Context) error { defer close(itemChan) - for _, key := range itemQueue { + for _, key := range queue { if err := ctx.Err(); err != nil { return err } - itemCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.process.item", key) + ctx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.process.item", key) itemChan <- keyAndBody{ keyAndTree: key, - Body: o.rebuilt.Tree(itemCtx, key.TreeID).ReadItem(itemCtx, key.Key), + Body: o.rebuilt.Tree(ctx, key.TreeID).ReadItem(ctx, key.Key), } } return nil @@ -198,10 +206,10 @@ func (o *rebuilder) processItemQueue(ctx context.Context) error { defer progressWriter.Done() o.curKey.Key.OK = true for item := range itemChan { - itemCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.process.item", item.keyAndTree) + ctx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.process.item", item.keyAndTree) o.curKey.TreeID = item.TreeID o.curKey.Key.Val = item.Key - handleItem(o, itemCtx, item.TreeID, btrfstree.Item{ + handleItem(o, ctx, item.TreeID, btrfstree.Item{ Key: item.Key, Body: item.Body, }) @@ -222,24 +230,26 @@ func (o *rebuilder) processItemQueue(ctx context.Context) error { // processAugmentQueue drains o.augmentQueue, filling o.itemQueue. func (o *rebuilder) processAugmentQueue(ctx context.Context) error { ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep", "apply-augments") + resolvedAugments := make(map[btrfsprim.ObjID]containers.Set[btrfsvol.LogicalAddr], len(o.augmentQueue)) var progress textui.Portion[int] for _, treeID := range maps.SortedKeys(o.augmentQueue) { if err := ctx.Err(); err != nil { return err } - treeCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.augment.tree", treeID) - resolvedAugments[treeID] = o.resolveTreeAugments(treeCtx, treeID) + ctx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.augment.tree", treeID) + resolvedAugments[treeID] = o.resolveTreeAugments(ctx, treeID) progress.D += len(resolvedAugments[treeID]) } o.augmentQueue = make(map[btrfsprim.ObjID]*treeAugmentQueue) o.numAugments = 0 o.numAugmentFailures = 0 runtime.GC() + progressWriter := textui.NewProgress[textui.Portion[int]](ctx, dlog.LogLevelInfo, textui.Tunable(1*time.Second)) ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep.progress", &progress) for _, treeID := range maps.SortedKeys(resolvedAugments) { - treeCtx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.augment.tree", treeID) + ctx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.augment.tree", treeID) for _, nodeAddr := range maps.SortedKeys(resolvedAugments[treeID]) { if err := ctx.Err(); err != nil { progressWriter.Set(progress) @@ -247,12 +257,13 @@ func (o *rebuilder) processAugmentQueue(ctx context.Context) error { return err } progressWriter.Set(progress) - o.rebuilt.Tree(treeCtx, treeID).AddRoot(treeCtx, nodeAddr) + o.rebuilt.Tree(ctx, treeID).AddRoot(ctx, nodeAddr) progress.N++ } } progressWriter.Set(progress) progressWriter.Done() + return nil } -- cgit v1.2.3-2-g168b From f99c76b597e37a94f6a23345c36f42e70e71c67c Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 6 Jan 2023 13:08:27 -0700 Subject: rebuildnodes: Add a settle-items step --- .../btrfsinspect/rebuildnodes/rebuild.go | 113 +++++++++++++++++---- 1 file changed, 92 insertions(+), 21 deletions(-) (limited to 'lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go') diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go index 472f6cd..9f7ce9f 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go @@ -56,7 +56,8 @@ type rebuilder struct { Key containers.Optional[btrfsprim.Key] } treeQueue containers.Set[btrfsprim.ObjID] - itemQueue containers.Set[keyAndTree] + addedItemQueue containers.Set[keyAndTree] + settledItemQueue containers.Set[keyAndTree] augmentQueue map[btrfsprim.ObjID]*treeAugmentQueue numAugments int numAugmentFailures int @@ -97,7 +98,8 @@ func (o *rebuilder) Rebuild(ctx context.Context) error { ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.step", "rebuild") // Initialize - o.itemQueue = make(containers.Set[keyAndTree]) + o.addedItemQueue = make(containers.Set[keyAndTree]) + o.settledItemQueue = make(containers.Set[keyAndTree]) o.augmentQueue = make(map[btrfsprim.ObjID]*treeAugmentQueue) // Seed the queue @@ -109,22 +111,29 @@ func (o *rebuilder) Rebuild(ctx context.Context) error { ) // Run - for passNum := 0; len(o.treeQueue) > 0 || len(o.itemQueue) > 0 || len(o.augmentQueue) > 0; passNum++ { + for passNum := 0; len(o.treeQueue) > 0 || len(o.addedItemQueue) > 0 || len(o.settledItemQueue) > 0 || len(o.augmentQueue) > 0; passNum++ { ctx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.pass", passNum) - // Crawl trees (Drain o.treeQueue, fill o.itemQueue). + // Crawl trees (Drain o.treeQueue, fill o.addedItemQueue). if err := o.processTreeQueue(ctx); err != nil { return err } runtime.GC() - // Process items (drain o.itemQueue, fill o.augmentQueue and o.treeQueue). - if err := o.processItemQueue(ctx); err != nil { - return err + if len(o.addedItemQueue) > 0 { + // Settle items (drain o.addedItemQueue, fill o.augmentQueue and o.settledItemQueue). + if err := o.processAddedItemQueue(ctx); err != nil { + return err + } + } else { + // Process items (drain o.settledItemQueue, fill o.augmentQueue and o.treeQueue). + if err := o.processSettledItemQueue(ctx); err != nil { + return err + } } runtime.GC() - // Apply augments (drain o.augmentQueue, fill o.itemQueue). + // Apply augments (drain o.augmentQueue, fill o.addedItemQueue). if err := o.processAugmentQueue(ctx); err != nil { return err } @@ -134,7 +143,7 @@ func (o *rebuilder) Rebuild(ctx context.Context) error { return nil } -// processTreeQueue drains o.treeQueue, filling o.itemQueue. +// processTreeQueue drains o.treeQueue, filling o.addedItemQueue. func (o *rebuilder) processTreeQueue(ctx context.Context) error { ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep", "collect-items") @@ -148,38 +157,98 @@ func (o *rebuilder) processTreeQueue(ctx context.Context) error { if err := ctx.Err(); err != nil { return err } - o.rebuilt.Tree(ctx, o.curKey.TreeID) + // This will call o.AddedItem as nescessary, which + // inserts to o.addedItemQueue. + _ = o.rebuilt.Tree(ctx, o.curKey.TreeID) + } + + return nil +} + +type settleItemStats struct { + textui.Portion[int] + NumAugments int + NumAugmentTrees int +} + +func (s settleItemStats) String() string { + // return textui.Sprintf("%v (queued %v augments across %v trees)", + return textui.Sprintf("%v (aug:%v trees:%v)", + s.Portion, s.NumAugments, s.NumAugmentTrees) +} + +// processAddedItemQueue drains o.addedItemQueue, filling o.augmentQueue and o.settledItemQueue. +func (o *rebuilder) processAddedItemQueue(ctx context.Context) error { + ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep", "settle-items") + + queue := maps.Keys(o.addedItemQueue) + o.addedItemQueue = make(containers.Set[keyAndTree]) + sort.Slice(queue, func(i, j int) bool { + return queue[i].Compare(queue[j]) < 0 + }) + + var progress settleItemStats + progress.D = len(queue) + progressWriter := textui.NewProgress[settleItemStats](ctx, dlog.LogLevelInfo, textui.Tunable(1*time.Second)) + ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep.progress", &progress) + + for i, key := range queue { + progress.N = i + progressWriter.Set(progress) + + ctx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.settle.item", key) + tree := o.rebuilt.Tree(ctx, key.TreeID) + incPtr, ok := tree.Items(ctx).Load(key.Key) + if !ok { + panic(fmt.Errorf("should not happen: failed to load already-added item: %v", key)) + } + excPtr, ok := tree.PotentialItems(ctx).Load(key.Key) + if ok && tree.ShouldReplace(incPtr.Node, excPtr.Node) { + wantKey := WantWithTree{ + TreeID: key.TreeID, + Key: wantFromKey(key.Key), + } + o.wantAugment(ctx, wantKey, tree.LeafToRoots(ctx, excPtr.Node)) + progress.NumAugments = o.numAugments + progress.NumAugmentTrees = len(o.augmentQueue) + progressWriter.Set(progress) + } else if !handleWouldBeNoOp(key.ItemType) { + o.settledItemQueue.Insert(key) + } } + progress.N = len(queue) + progressWriter.Set(progress) + progressWriter.Done() return nil } -type itemStats struct { +type processItemStats struct { textui.Portion[int] NumAugments int NumFailures int NumAugmentTrees int } -func (s itemStats) String() string { +func (s processItemStats) String() string { // return textui.Sprintf("%v (queued %v augments and %v failures across %v trees)", return textui.Sprintf("%v (aug:%v fail:%v trees:%v)", s.Portion, s.NumAugments, s.NumFailures, s.NumAugmentTrees) } -// processItemQueue drains o.itemQueue, filling o.augmentQueue and o.treeQueue. -func (o *rebuilder) processItemQueue(ctx context.Context) error { +// processSettledItemQueue drains o.settledItemQueue, filling o.augmentQueue and o.treeQueue. +func (o *rebuilder) processSettledItemQueue(ctx context.Context) error { ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep", "process-items") - queue := maps.Keys(o.itemQueue) - o.itemQueue = make(containers.Set[keyAndTree]) + queue := maps.Keys(o.settledItemQueue) + o.settledItemQueue = make(containers.Set[keyAndTree]) sort.Slice(queue, func(i, j int) bool { return queue[i].Compare(queue[j]) < 0 }) - var progress itemStats + var progress processItemStats progress.D = len(queue) - progressWriter := textui.NewProgress[itemStats](ctx, dlog.LogLevelInfo, textui.Tunable(1*time.Second)) + progressWriter := textui.NewProgress[processItemStats](ctx, dlog.LogLevelInfo, textui.Tunable(1*time.Second)) ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep.progress", &progress) type keyAndBody struct { @@ -227,7 +296,7 @@ func (o *rebuilder) processItemQueue(ctx context.Context) error { return grp.Wait() } -// processAugmentQueue drains o.augmentQueue, filling o.itemQueue. +// processAugmentQueue drains o.augmentQueue, filling o.addedItemQueue. func (o *rebuilder) processAugmentQueue(ctx context.Context) error { ctx = dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.substep", "apply-augments") @@ -257,6 +326,8 @@ func (o *rebuilder) processAugmentQueue(ctx context.Context) error { return err } progressWriter.Set(progress) + // This will call o.AddedItem as nescessary, which + // inserts to o.addedItemQueue. o.rebuilt.Tree(ctx, treeID).AddRoot(ctx, nodeAddr) progress.N++ } @@ -269,7 +340,7 @@ func (o *rebuilder) processAugmentQueue(ctx context.Context) error { func (o *rebuilder) enqueueRetry() { if o.curKey.Key.OK { - o.itemQueue.Insert(keyAndTree{ + o.settledItemQueue.Insert(keyAndTree{ TreeID: o.curKey.TreeID, Key: o.curKey.Key.Val, }) @@ -410,7 +481,7 @@ func (o *rebuilder) resolveTreeAugments(ctx context.Context, treeID btrfsprim.Ob maps.Keys(o.augmentQueue[treeID].single), maps.Keys(o.augmentQueue[treeID].multi)...) sort.Slice(wantKeys, func(i, j int) bool { - return wantKeys[i].Cmp(wantKeys[j]) < 0 + return wantKeys[i].Compare(wantKeys[j]) < 0 }) for _, wantKey := range wantKeys { list, ok := o.augmentQueue[treeID].multi[wantKey] -- cgit v1.2.3-2-g168b From 6097c4c634b8644e4ac1e0520f39a6bc501d227d Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sat, 7 Jan 2023 14:03:47 -0700 Subject: rebuildnodes: Don't deadlock if the cpu goroutine panics --- lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go') diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go index 9f7ce9f..bc4d7c9 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go @@ -264,10 +264,14 @@ func (o *rebuilder) processSettledItemQueue(ctx context.Context) error { return err } ctx := dlog.WithField(ctx, "btrfsinspect.rebuild-nodes.rebuild.process.item", key) - itemChan <- keyAndBody{ + item := keyAndBody{ keyAndTree: key, Body: o.rebuilt.Tree(ctx, key.TreeID).ReadItem(ctx, key.Key), } + select { + case itemChan <- item: + case <-ctx.Done(): + } } return nil }) -- cgit v1.2.3-2-g168b From 2b72cef74ea368b39d976866944d8279293be9de Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sun, 8 Jan 2023 18:02:07 -0700 Subject: rebuildnodes/btrees: Have the concurrency story for .trees make more sense --- lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go') diff --git a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go index bc4d7c9..dc78c2e 100644 --- a/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go +++ b/lib/btrfsprogs/btrfsinspect/rebuildnodes/rebuild.go @@ -71,7 +71,7 @@ type treeAugmentQueue struct { type Rebuilder interface { Rebuild(context.Context) error - ListRoots() map[btrfsprim.ObjID]containers.Set[btrfsvol.LogicalAddr] + ListRoots(context.Context) map[btrfsprim.ObjID]containers.Set[btrfsvol.LogicalAddr] } func NewRebuilder(ctx context.Context, fs *btrfs.FS, nodeScanResults btrfsinspect.ScanDevicesResult) (Rebuilder, error) { @@ -90,8 +90,8 @@ func NewRebuilder(ctx context.Context, fs *btrfs.FS, nodeScanResults btrfsinspec return o, nil } -func (o *rebuilder) ListRoots() map[btrfsprim.ObjID]containers.Set[btrfsvol.LogicalAddr] { - return o.rebuilt.ListRoots() +func (o *rebuilder) ListRoots(ctx context.Context) map[btrfsprim.ObjID]containers.Set[btrfsvol.LogicalAddr] { + return o.rebuilt.ListRoots(ctx) } func (o *rebuilder) Rebuild(ctx context.Context) error { -- cgit v1.2.3-2-g168b