@@ -3,13 +3,10 @@ package io
3
3
import (
4
4
"context"
5
5
"fmt"
6
- "os"
7
- "time"
8
-
9
6
mdag "github.com/ipfs/go-merkledag"
10
-
11
7
format "github.com/ipfs/go-unixfs"
12
8
"github.com/ipfs/go-unixfs/hamt"
9
+ "os"
13
10
14
11
"github.com/ipfs/go-cid"
15
12
ipld "github.com/ipfs/go-ipld-format"
@@ -26,11 +23,6 @@ var log = logging.Logger("unixfs")
26
23
// ProtoNode doesn't use the Data field so this estimate is pretty accurate).
27
24
var HAMTShardingSize = 0
28
25
29
- // Time in seconds allowed to fetch the shards to compute the size before
30
- // returning an error.
31
- // FIXME: Adjust to sane value.
32
- var EvaluateHAMTTransitionTimeout = time .Duration (1 )
33
-
34
26
// DefaultShardWidth is the default value used for hamt sharding width.
35
27
var DefaultShardWidth = 256
36
28
@@ -438,15 +430,42 @@ func (d *HAMTDirectory) removeFromSizeChange(name string, linkCid cid.Cid) {
438
430
d .sizeChange -= estimatedLinkSize (name , linkCid )
439
431
}
440
432
441
- // Evaluate directory size and check if it's below HAMTShardingSize threshold
442
- // (to trigger a transition to a BasicDirectory). It returns two `bool`s:
443
- // * whether it's below (true) or equal/above (false)
444
- // * whether the passed timeout to compute the size has been exceeded
433
+ // FIXME: Will be extended later to the `AddEntry` case.
434
+ func (d * HAMTDirectory ) needsToSwitchToBasicDir (ctx context.Context , nameToRemove string ) (switchToBasic bool , err error ) {
435
+ if HAMTShardingSize == 0 { // Option disabled.
436
+ return false , nil
437
+ }
438
+
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
445
+ }
446
+ sizeToRemove := estimatedLinkSize (nameToRemove , entryToRemove .Cid )
447
+
448
+ if d .sizeChange - sizeToRemove >= 0 {
449
+ // We won't have reduced the HAMT net size.
450
+ return false , nil
451
+ }
452
+
453
+ // We have reduced the directory size, check if went below the
454
+ // 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
460
+ }
461
+
462
+ // Evaluate directory size and a future sizeChange and check if it will be below
463
+ // HAMTShardingSize threshold (to trigger a transition to a BasicDirectory).
445
464
// Instead of enumerating the entire tree we eagerly call EnumLinksAsync
446
- // until we either reach a value above the threshold (in that case no need)
447
- // to keep counting or the timeout runs out in which case the `below` return
448
- // value is not to be trusted as we didn't have time to count enough shards.
449
- func (d * HAMTDirectory ) sizeBelowThreshold (timeout time. Duration ) (below bool , timeoutExceeded bool ) {
465
+ // until we either reach a value above the threshold (in that case no need
466
+ // to keep counting) or an error occurs (like the context being canceled
467
+ // if we take too much time fetching the necessary shards) .
468
+ func (d * HAMTDirectory ) sizeBelowThreshold (ctx context. Context , sizeChange int ) (below bool , err error ) {
450
469
if HAMTShardingSize == 0 {
451
470
panic ("asked to compute HAMT size with HAMTShardingSize option off (0)" )
452
471
}
@@ -455,57 +474,25 @@ func (d *HAMTDirectory) sizeBelowThreshold(timeout time.Duration) (below bool, t
455
474
// end early if we already know we're above the threshold or run out of time.
456
475
partialSize := 0
457
476
458
- ctx , cancel := context .WithTimeout (context .Background (), time .Second * timeout )
477
+ // We stop the enumeration once we have enough information and exit this function.
478
+ ctx , cancel := context .WithCancel (ctx )
459
479
defer cancel ()
480
+
460
481
for linkResult := range d .EnumLinksAsync (ctx ) {
461
482
if linkResult .Err != nil {
462
- continue
463
- // The timeout exceeded errors will be coming through here but I'm
464
- // not sure if we can just compare against a generic DeadlineExceeded
465
- // error here to return early and avoid iterating the entire loop.
466
- // (We might confuse a specific DeadlineExceeded of an internal function
467
- // with our context here.)
468
- // Since *our* DeadlineExceeded will quickly propagate to any other
469
- // pending fetches it seems that iterating the entire loop won't add
470
- // much more cost anyway.
471
- // FIXME: Check the above reasoning.
472
- }
473
- if linkResult .Link == nil {
474
- panic ("empty link result (both values nil)" )
475
- // FIXME: Is this *ever* possible?
483
+ return false , linkResult .Err
476
484
}
477
- partialSize += estimatedLinkSize (linkResult .Link .Name , linkResult .Link .Cid )
478
485
479
- if partialSize >= HAMTShardingSize {
486
+ partialSize += estimatedLinkSize (linkResult .Link .Name , linkResult .Link .Cid )
487
+ if partialSize + sizeChange >= HAMTShardingSize {
480
488
// We have already fetched enough shards to assert we are
481
489
// above the threshold, so no need to keep fetching.
482
- return false , false
490
+ return false , nil
483
491
}
484
492
}
485
- // At this point either we enumerated all shards or run out of time.
486
- // Figure out which.
487
-
488
- if ctx .Err () == context .Canceled {
489
- panic ("the context was canceled but we're still evaluating a possible switch" )
490
- }
491
- if partialSize >= HAMTShardingSize {
492
- panic ("we reach the threshold but we're still evaluating a possible switch" )
493
- }
494
493
495
- if ctx .Err () == context .DeadlineExceeded {
496
- return false , true
497
- }
498
-
499
- // If we reach this then:
500
- // * We are below the threshold (we didn't return inside the EnumLinksAsync
501
- // loop).
502
- // * The context wasn't cancelled so we iterated *all* shards
503
- // and are sure that we have the full size.
504
- // FIXME: Can we actually verify the last claim here to be sure?
505
- // (Iterating all the shards in the HAMT as a plumbing function maybe.
506
- // If they're in memory it shouldn't be that expensive, we won't be
507
- // switching that often, probably.)
508
- return true , false
494
+ // We enumerated *all* links in all shards and didn't reach the threshold.
495
+ return true , nil
509
496
}
510
497
511
498
// UpgradeableDirectory wraps a Directory interface and provides extra logic
@@ -573,42 +560,21 @@ func (d *UpgradeableDirectory) getDagService() ipld.DAGService {
573
560
// sure we make good on the value). Finding the right margin can be tricky
574
561
// and very dependent on the use case so it might not be worth it.
575
562
func (d * UpgradeableDirectory ) RemoveChild (ctx context.Context , name string ) error {
576
- if err := d .Directory .RemoveChild (ctx , name ); err != nil {
577
- return err
578
- }
579
-
580
563
hamtDir , ok := d .Directory .(* HAMTDirectory )
581
- if ! ok { // BasicDirectory
582
- return nil
583
- }
584
-
585
- if HAMTShardingSize == 0 || // Option disabled.
586
- hamtDir .sizeChange >= 0 { // We haven't reduced the HAMT net size.
587
- return nil
588
- }
589
-
590
- // We have reduced the directory size, check if it didn't go under
591
- // the HAMTShardingSize threshold.
592
- belowThreshold , timeoutExceeded := hamtDir .sizeBelowThreshold (EvaluateHAMTTransitionTimeout )
593
-
594
- if timeoutExceeded {
595
- // We run out of time before confirming if we're indeed below the
596
- // threshold. When in doubt error to not return inconsistent structures.
597
- // FIXME: We could allow this to return without error and enforce this
598
- // timeout on a GetNode() call when we need to actually commit to a
599
- // structure/CID. (The downside is that GetNode() doesn't have a
600
- // context argument and we would have to break the API.)
601
- return fmt .Errorf ("not enought time to fetch shards" )
602
- // FIXME: Abstract in new error for testing.
603
- }
604
-
605
- if belowThreshold { // Switch.
606
- basicDir , err := hamtDir .switchToBasic (ctx )
564
+ if ok {
565
+ switchToBasic , err := hamtDir .needsToSwitchToBasicDir (ctx , name )
607
566
if err != nil {
608
567
return err
609
568
}
610
- d .Directory = basicDir
569
+
570
+ if switchToBasic {
571
+ basicDir , err := hamtDir .switchToBasic (ctx )
572
+ if err != nil {
573
+ return err
574
+ }
575
+ d .Directory = basicDir
576
+ }
611
577
}
612
578
613
- return nil
579
+ return d . Directory . RemoveChild ( ctx , name )
614
580
}
0 commit comments