@@ -200,10 +200,32 @@ func (d *BasicDirectory) AddChild(ctx context.Context, name string, node ipld.No
200
200
return d .addLinkChild (ctx , name , link )
201
201
}
202
202
203
+ func (d * BasicDirectory ) needsToSwitchToHAMTDir (name string , nodeToAdd ipld.Node ) (bool , error ) {
204
+ if HAMTShardingSize == 0 { // Option disabled.
205
+ return false , nil
206
+ }
207
+
208
+ operationSizeChange := 0
209
+ // Find if there is an old entry under that name that will be overwritten.
210
+ entryToRemove , err := d .node .GetNodeLink (name )
211
+ if err != mdag .ErrLinkNotFound {
212
+ if err != nil {
213
+ return false , err
214
+ }
215
+ operationSizeChange -= estimatedLinkSize (name , entryToRemove .Cid )
216
+ }
217
+ if nodeToAdd != nil {
218
+ operationSizeChange += estimatedLinkSize (name , nodeToAdd .Cid ())
219
+ }
220
+
221
+ return d .estimatedSize + operationSizeChange >= HAMTShardingSize , nil
222
+ }
223
+
203
224
// addLinkChild adds the link as an entry to this directory under the given
204
225
// name. Plumbing function for the AddChild API.
205
226
func (d * BasicDirectory ) addLinkChild (ctx context.Context , name string , link * ipld.Link ) error {
206
- // Remove old link (if it existed; ignore `ErrNotExist` otherwise).
227
+ // Remove old link and account for size change (if it existed; ignore
228
+ // `ErrNotExist` otherwise).
207
229
err := d .RemoveChild (ctx , name )
208
230
if err != nil && err != os .ErrNotExist {
209
231
return err
@@ -298,7 +320,7 @@ func (d *BasicDirectory) GetCidBuilder() cid.Builder {
298
320
}
299
321
300
322
// SwitchToSharding returns a HAMT implementation of this directory.
301
- func (d * BasicDirectory ) SwitchToSharding (ctx context.Context ) (Directory , error ) {
323
+ func (d * BasicDirectory ) SwitchToSharding (ctx context.Context ) (* HAMTDirectory , error ) {
302
324
hamtDir := new (HAMTDirectory )
303
325
hamtDir .dserv = d .dserv
304
326
@@ -430,33 +452,42 @@ func (d *HAMTDirectory) removeFromSizeChange(name string, linkCid cid.Cid) {
430
452
d .sizeChange -= estimatedLinkSize (name , linkCid )
431
453
}
432
454
433
- // FIXME: Will be extended later to the `AddEntry` case.
434
- func (d * HAMTDirectory ) needsToSwitchToBasicDir (ctx context.Context , nameToRemove string ) (switchToBasic bool , err error ) {
455
+ // Evaluate a switch from HAMTDirectory to BasicDirectory in case the size will
456
+ // go above the threshold when we are adding or removing an entry.
457
+ // In both the add/remove operations any old name will be removed, and for the
458
+ // add operation in particular a new entry will be added under that name (otherwise
459
+ // nodeToAdd is nil). We compute both (potential) future subtraction and
460
+ // addition to the size change.
461
+ func (d * HAMTDirectory ) needsToSwitchToBasicDir (ctx context.Context , name string , nodeToAdd ipld.Node ) (switchToBasic bool , err error ) {
435
462
if HAMTShardingSize == 0 { // Option disabled.
436
463
return false , nil
437
464
}
438
465
439
- entryToRemove , err := d .shard .Find (ctx , nameToRemove )
440
- if err == os .ErrNotExist {
441
- // Nothing to remove, no point in evaluating a switch.
442
- return false , nil
443
- } else if err != nil {
444
- return false , err
466
+ operationSizeChange := 0
467
+
468
+ // Find if there is an old entry under that name that will be overwritten
469
+ // (AddEntry) or flat out removed (RemoveEntry).
470
+ entryToRemove , err := d .shard .Find (ctx , name )
471
+ if err != os .ErrNotExist {
472
+ if err != nil {
473
+ return false , err
474
+ }
475
+ operationSizeChange -= estimatedLinkSize (name , entryToRemove .Cid )
476
+ }
477
+
478
+ // For the AddEntry case compute the size addition of the new entry.
479
+ if nodeToAdd != nil {
480
+ operationSizeChange += estimatedLinkSize (name , nodeToAdd .Cid ())
445
481
}
446
- sizeToRemove := estimatedLinkSize (nameToRemove , entryToRemove .Cid )
447
482
448
- if d .sizeChange - sizeToRemove >= 0 {
483
+ if d .sizeChange + operationSizeChange >= 0 {
449
484
// We won't have reduced the HAMT net size.
450
485
return false , nil
451
486
}
452
487
453
488
// We have reduced the directory size, check if went below the
454
489
// HAMTShardingSize threshold to trigger a switch.
455
- belowThreshold , err := d .sizeBelowThreshold (ctx , - sizeToRemove )
456
- if err != nil {
457
- return false , err
458
- }
459
- return belowThreshold , nil
490
+ return d .sizeBelowThreshold (ctx , operationSizeChange )
460
491
}
461
492
462
493
// Evaluate directory size and a future sizeChange and check if it will be below
@@ -511,32 +542,50 @@ var _ Directory = (*UpgradeableDirectory)(nil)
511
542
// AddChild implements the `Directory` interface. We check when adding new entries
512
543
// if we should switch to HAMTDirectory according to global option(s).
513
544
func (d * UpgradeableDirectory ) AddChild (ctx context.Context , name string , nd ipld.Node ) error {
514
- err := d .Directory .AddChild (ctx , name , nd )
545
+ hamtDir , ok := d .Directory .(* HAMTDirectory )
546
+ if ok {
547
+ // We evaluate a switch in the HAMTDirectory case even for an AddChild
548
+ // as it may overwrite an existing entry and end up actually reducing
549
+ // the directory size.
550
+ switchToBasic , err := hamtDir .needsToSwitchToBasicDir (ctx , name , nd )
551
+ if err != nil {
552
+ return err
553
+ }
554
+
555
+ if switchToBasic {
556
+ basicDir , err := hamtDir .switchToBasic (ctx )
557
+ if err != nil {
558
+ return err
559
+ }
560
+ err = basicDir .AddChild (ctx , name , nd )
561
+ if err != nil {
562
+ return err
563
+ }
564
+ d .Directory = basicDir
565
+ return nil
566
+ }
567
+
568
+ return d .Directory .AddChild (ctx , name , nd )
569
+ }
570
+
571
+ // BasicDirectory
572
+ basicDir := d .Directory .(* BasicDirectory )
573
+ switchToHAMT , err := basicDir .needsToSwitchToHAMTDir (name , nd )
515
574
if err != nil {
516
575
return err
517
576
}
518
-
519
- // Evaluate possible HAMT upgrade.
520
- if HAMTShardingSize == 0 {
521
- return nil
577
+ if ! switchToHAMT {
578
+ return basicDir .AddChild (ctx , name , nd )
522
579
}
523
- basicDir , ok := d . Directory .( * BasicDirectory )
524
- if ! ok {
525
- return nil
580
+ hamtDir , err = basicDir . SwitchToSharding ( ctx )
581
+ if err != nil {
582
+ return err
526
583
}
527
- if basicDir .estimatedSize >= HAMTShardingSize {
528
- // Ideally to minimize performance we should check if this last
529
- // `AddChild` call would bring the directory size over the threshold
530
- // *before* executing it since we would end up switching anyway and
531
- // that call would be "wasted". This is a minimal performance impact
532
- // and we prioritize a simple code base.
533
- hamtDir , err := basicDir .SwitchToSharding (ctx )
534
- if err != nil {
535
- return err
536
- }
537
- d .Directory = hamtDir
584
+ hamtDir .AddChild (ctx , name , nd )
585
+ if err != nil {
586
+ return err
538
587
}
539
-
588
+ d . Directory = hamtDir
540
589
return nil
541
590
}
542
591
@@ -565,7 +614,7 @@ func (d *UpgradeableDirectory) RemoveChild(ctx context.Context, name string) err
565
614
return d .Directory .RemoveChild (ctx , name )
566
615
}
567
616
568
- switchToBasic , err := hamtDir .needsToSwitchToBasicDir (ctx , name )
617
+ switchToBasic , err := hamtDir .needsToSwitchToBasicDir (ctx , name , nil )
569
618
if err != nil {
570
619
return err
571
620
}
0 commit comments