Skip to content
This repository was archived by the owner on Jun 27, 2023. It is now read-only.

Commit e685e0a

Browse files
committed
fix(dir): remove timeout and evaluate switch *before* removing entry
1 parent 3797036 commit e685e0a

File tree

1 file changed

+56
-90
lines changed

1 file changed

+56
-90
lines changed

io/directory.go

+56-90
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,10 @@ package io
33
import (
44
"context"
55
"fmt"
6-
"os"
7-
"time"
8-
96
mdag "github.com/ipfs/go-merkledag"
10-
117
format "github.com/ipfs/go-unixfs"
128
"github.com/ipfs/go-unixfs/hamt"
9+
"os"
1310

1411
"github.com/ipfs/go-cid"
1512
ipld "github.com/ipfs/go-ipld-format"
@@ -26,11 +23,6 @@ var log = logging.Logger("unixfs")
2623
// ProtoNode doesn't use the Data field so this estimate is pretty accurate).
2724
var HAMTShardingSize = 0
2825

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-
3426
// DefaultShardWidth is the default value used for hamt sharding width.
3527
var DefaultShardWidth = 256
3628

@@ -438,15 +430,42 @@ func (d *HAMTDirectory) removeFromSizeChange(name string, linkCid cid.Cid) {
438430
d.sizeChange -= estimatedLinkSize(name, linkCid)
439431
}
440432

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).
445464
// 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) {
450469
if HAMTShardingSize == 0 {
451470
panic("asked to compute HAMT size with HAMTShardingSize option off (0)")
452471
}
@@ -455,57 +474,25 @@ func (d *HAMTDirectory) sizeBelowThreshold(timeout time.Duration) (below bool, t
455474
// end early if we already know we're above the threshold or run out of time.
456475
partialSize := 0
457476

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)
459479
defer cancel()
480+
460481
for linkResult := range d.EnumLinksAsync(ctx) {
461482
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
476484
}
477-
partialSize += estimatedLinkSize(linkResult.Link.Name, linkResult.Link.Cid)
478485

479-
if partialSize >= HAMTShardingSize {
486+
partialSize += estimatedLinkSize(linkResult.Link.Name, linkResult.Link.Cid)
487+
if partialSize+sizeChange >= HAMTShardingSize {
480488
// We have already fetched enough shards to assert we are
481489
// above the threshold, so no need to keep fetching.
482-
return false, false
490+
return false, nil
483491
}
484492
}
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-
}
494493

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
509496
}
510497

511498
// UpgradeableDirectory wraps a Directory interface and provides extra logic
@@ -573,42 +560,21 @@ func (d *UpgradeableDirectory) getDagService() ipld.DAGService {
573560
// sure we make good on the value). Finding the right margin can be tricky
574561
// and very dependent on the use case so it might not be worth it.
575562
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-
580563
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)
607566
if err != nil {
608567
return err
609568
}
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+
}
611577
}
612578

613-
return nil
579+
return d.Directory.RemoveChild(ctx, name)
614580
}

0 commit comments

Comments
 (0)