@@ -196,10 +196,32 @@ func (d *BasicDirectory) AddChild(ctx context.Context, name string, node ipld.No
196
196
return d .addLinkChild (ctx , name , link )
197
197
}
198
198
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
+
199
220
// addLinkChild adds the link as an entry to this directory under the given
200
221
// name. Plumbing function for the AddChild API.
201
222
func (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).
203
225
err := d .RemoveChild (ctx , name )
204
226
if err != nil && err != os .ErrNotExist {
205
227
return err
@@ -294,7 +316,7 @@ func (d *BasicDirectory) GetCidBuilder() cid.Builder {
294
316
}
295
317
296
318
// 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 ) {
298
320
hamtDir := new (HAMTDirectory )
299
321
hamtDir .dserv = d .dserv
300
322
@@ -422,33 +444,42 @@ func (d *HAMTDirectory) removeFromSizeChange(name string, linkCid cid.Cid) {
422
444
d .sizeChange -= estimatedLinkSize (name , linkCid )
423
445
}
424
446
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 ) {
427
454
if HAMTShardingSize == 0 { // Option disabled.
428
455
return false , nil
429
456
}
430
457
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 ())
437
473
}
438
- sizeToRemove := estimatedLinkSize (nameToRemove , entryToRemove .Cid )
439
474
440
- if d .sizeChange - sizeToRemove >= 0 {
475
+ if d .sizeChange + operationSizeChange >= 0 {
441
476
// We won't have reduced the HAMT net size.
442
477
return false , nil
443
478
}
444
479
445
480
// We have reduced the directory size, check if went below the
446
481
// 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 )
452
483
}
453
484
454
485
// Evaluate directory size and a future sizeChange and check if it will be below
@@ -503,32 +534,50 @@ var _ Directory = (*UpgradeableDirectory)(nil)
503
534
// AddChild implements the `Directory` interface. We check when adding new entries
504
535
// if we should switch to HAMTDirectory according to global option(s).
505
536
func (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 )
507
566
if err != nil {
508
567
return err
509
568
}
510
-
511
- // Evaluate possible HAMT upgrade.
512
- if HAMTShardingSize == 0 {
513
- return nil
569
+ if ! switchToHAMT {
570
+ return basicDir .AddChild (ctx , name , nd )
514
571
}
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
518
575
}
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
530
579
}
531
-
580
+ d . Directory = hamtDir
532
581
return nil
533
582
}
534
583
@@ -557,7 +606,7 @@ func (d *UpgradeableDirectory) RemoveChild(ctx context.Context, name string) err
557
606
return d .Directory .RemoveChild (ctx , name )
558
607
}
559
608
560
- switchToBasic , err := hamtDir .needsToSwitchToBasicDir (ctx , name )
609
+ switchToBasic , err := hamtDir .needsToSwitchToBasicDir (ctx , name , nil )
561
610
if err != nil {
562
611
return err
563
612
}
0 commit comments