@@ -196,10 +196,32 @@ func (d *BasicDirectory) AddChild(ctx context.Context, name string, node ipld.No
196196 return d .addLinkChild (ctx , name , link )
197197}
198198
199+ func (d * BasicDirectory ) needsToSwitchToHAMTDir (name string , nodeToAdd ipld.Node ) (bool , error ) {
200+ if HAMTShardingSize == 0 { // Option disabled.
201+ return false , nil
202+ }
203+
204+ operationSizeChange := 0
205+ // Find if there is an old entry under that name that will be overwritten.
206+ entryToRemove , err := d .node .GetNodeLink (name )
207+ if err != mdag .ErrLinkNotFound {
208+ if err != nil {
209+ return false , err
210+ }
211+ operationSizeChange -= estimatedLinkSize (name , entryToRemove .Cid )
212+ }
213+ if nodeToAdd != nil {
214+ operationSizeChange += estimatedLinkSize (name , nodeToAdd .Cid ())
215+ }
216+
217+ return d .estimatedSize + operationSizeChange >= HAMTShardingSize , nil
218+ }
219+
199220// addLinkChild adds the link as an entry to this directory under the given
200221// name. Plumbing function for the AddChild API.
201222func (d * BasicDirectory ) addLinkChild (ctx context.Context , name string , link * ipld.Link ) error {
202- // Remove old link (if it existed; ignore `ErrNotExist` otherwise).
223+ // Remove old link and account for size change (if it existed; ignore
224+ // `ErrNotExist` otherwise).
203225 err := d .RemoveChild (ctx , name )
204226 if err != nil && err != os .ErrNotExist {
205227 return err
@@ -294,7 +316,7 @@ func (d *BasicDirectory) GetCidBuilder() cid.Builder {
294316}
295317
296318// SwitchToSharding returns a HAMT implementation of this directory.
297- func (d * BasicDirectory ) SwitchToSharding (ctx context.Context ) (Directory , error ) {
319+ func (d * BasicDirectory ) SwitchToSharding (ctx context.Context ) (* HAMTDirectory , error ) {
298320 hamtDir := new (HAMTDirectory )
299321 hamtDir .dserv = d .dserv
300322
@@ -422,33 +444,42 @@ func (d *HAMTDirectory) removeFromSizeChange(name string, linkCid cid.Cid) {
422444 d .sizeChange -= estimatedLinkSize (name , linkCid )
423445}
424446
425- // FIXME: Will be extended later to the `AddEntry` case.
426- func (d * HAMTDirectory ) needsToSwitchToBasicDir (ctx context.Context , nameToRemove string ) (switchToBasic bool , err error ) {
447+ // Evaluate a switch from HAMTDirectory to BasicDirectory in case the size will
448+ // go above the threshold when we are adding or removing an entry.
449+ // In both the add/remove operations any old name will be removed, and for the
450+ // add operation in particular a new entry will be added under that name (otherwise
451+ // nodeToAdd is nil). We compute both (potential) future subtraction and
452+ // addition to the size change.
453+ func (d * HAMTDirectory ) needsToSwitchToBasicDir (ctx context.Context , name string , nodeToAdd ipld.Node ) (switchToBasic bool , err error ) {
427454 if HAMTShardingSize == 0 { // Option disabled.
428455 return false , nil
429456 }
430457
431- entryToRemove , err := d .shard .Find (ctx , nameToRemove )
432- if err == os .ErrNotExist {
433- // Nothing to remove, no point in evaluating a switch.
434- return false , nil
435- } else if err != nil {
436- return false , err
458+ operationSizeChange := 0
459+
460+ // Find if there is an old entry under that name that will be overwritten
461+ // (AddEntry) or flat out removed (RemoveEntry).
462+ entryToRemove , err := d .shard .Find (ctx , name )
463+ if err != os .ErrNotExist {
464+ if err != nil {
465+ return false , err
466+ }
467+ operationSizeChange -= estimatedLinkSize (name , entryToRemove .Cid )
468+ }
469+
470+ // For the AddEntry case compute the size addition of the new entry.
471+ if nodeToAdd != nil {
472+ operationSizeChange += estimatedLinkSize (name , nodeToAdd .Cid ())
437473 }
438- sizeToRemove := estimatedLinkSize (nameToRemove , entryToRemove .Cid )
439474
440- if d .sizeChange - sizeToRemove >= 0 {
475+ if d .sizeChange + operationSizeChange >= 0 {
441476 // We won't have reduced the HAMT net size.
442477 return false , nil
443478 }
444479
445480 // We have reduced the directory size, check if went below the
446481 // HAMTShardingSize threshold to trigger a switch.
447- belowThreshold , err := d .sizeBelowThreshold (ctx , - sizeToRemove )
448- if err != nil {
449- return false , err
450- }
451- return belowThreshold , nil
482+ return d .sizeBelowThreshold (ctx , operationSizeChange )
452483}
453484
454485// Evaluate directory size and a future sizeChange and check if it will be below
@@ -503,32 +534,50 @@ var _ Directory = (*UpgradeableDirectory)(nil)
503534// AddChild implements the `Directory` interface. We check when adding new entries
504535// if we should switch to HAMTDirectory according to global option(s).
505536func (d * UpgradeableDirectory ) AddChild (ctx context.Context , name string , nd ipld.Node ) error {
506- err := d .Directory .AddChild (ctx , name , nd )
537+ hamtDir , ok := d .Directory .(* HAMTDirectory )
538+ if ok {
539+ // We evaluate a switch in the HAMTDirectory case even for an AddChild
540+ // as it may overwrite an existing entry and end up actually reducing
541+ // the directory size.
542+ switchToBasic , err := hamtDir .needsToSwitchToBasicDir (ctx , name , nd )
543+ if err != nil {
544+ return err
545+ }
546+
547+ if switchToBasic {
548+ basicDir , err := hamtDir .switchToBasic (ctx )
549+ if err != nil {
550+ return err
551+ }
552+ err = basicDir .AddChild (ctx , name , nd )
553+ if err != nil {
554+ return err
555+ }
556+ d .Directory = basicDir
557+ return nil
558+ }
559+
560+ return d .Directory .AddChild (ctx , name , nd )
561+ }
562+
563+ // BasicDirectory
564+ basicDir := d .Directory .(* BasicDirectory )
565+ switchToHAMT , err := basicDir .needsToSwitchToHAMTDir (name , nd )
507566 if err != nil {
508567 return err
509568 }
510-
511- // Evaluate possible HAMT upgrade.
512- if HAMTShardingSize == 0 {
513- return nil
569+ if ! switchToHAMT {
570+ return basicDir .AddChild (ctx , name , nd )
514571 }
515- basicDir , ok := d . Directory .( * BasicDirectory )
516- if ! ok {
517- return nil
572+ hamtDir , err = basicDir . SwitchToSharding ( ctx )
573+ if err != nil {
574+ return err
518575 }
519- if basicDir .estimatedSize >= HAMTShardingSize {
520- // Ideally to minimize performance we should check if this last
521- // `AddChild` call would bring the directory size over the threshold
522- // *before* executing it since we would end up switching anyway and
523- // that call would be "wasted". This is a minimal performance impact
524- // and we prioritize a simple code base.
525- hamtDir , err := basicDir .SwitchToSharding (ctx )
526- if err != nil {
527- return err
528- }
529- d .Directory = hamtDir
576+ hamtDir .AddChild (ctx , name , nd )
577+ if err != nil {
578+ return err
530579 }
531-
580+ d . Directory = hamtDir
532581 return nil
533582}
534583
@@ -557,7 +606,7 @@ func (d *UpgradeableDirectory) RemoveChild(ctx context.Context, name string) err
557606 return d .Directory .RemoveChild (ctx , name )
558607 }
559608
560- switchToBasic , err := hamtDir .needsToSwitchToBasicDir (ctx , name )
609+ switchToBasic , err := hamtDir .needsToSwitchToBasicDir (ctx , name , nil )
561610 if err != nil {
562611 return err
563612 }
0 commit comments