kubo/pin/gc/gc.go
Steven Allen 9dcec2b3e2 gx: update go-libp2p-peer
Reverts the changes that allowed small keys (ed25519 keys) to be inlined.

License: MIT
Signed-off-by: Steven Allen <steven@stebalien.com>
2018-12-07 15:37:23 -08:00

289 lines
7.8 KiB
Go

// Package gc provides garbage collection for go-ipfs.
package gc
import (
"context"
"errors"
"fmt"
"strings"
pin "github.com/ipfs/go-ipfs/pin"
bserv "gx/ipfs/QmPoh3SrQzFBWtdGK6qmHDV4EanKR6kYPj4DD3J2NLoEmZ/go-blockservice"
dag "gx/ipfs/QmdV35UHnL1FM52baPkeUo6u7Fxm2CRUkPTLRPxeF8a4Ap/go-merkledag"
cid "gx/ipfs/QmR8BauakNcBa3RbE4nbQu76PDiJgoQgz8AJdhJuiU4TAw/go-cid"
bstore "gx/ipfs/QmS2aqUZLJp8kF1ihE5rvDGE5LvmKDPnx32w9Z1BW9xLV5/go-ipfs-blockstore"
"gx/ipfs/QmYMQuypUbgsdNHmuCBSUJV6wdQVsBHRivNAp3efHJwZJD/go-verifcid"
offline "gx/ipfs/QmYZwey1thDTynSrvd6qQkX24UpTka6TFhQ2v569UpoqxD/go-ipfs-exchange-offline"
ipld "gx/ipfs/QmcKKBwfz6FyQdHR2jsXrrF6XeSBXYL86anmWNewpFpoF5/go-ipld-format"
logging "gx/ipfs/QmcuXC5cxs79ro2cUuHs4HQ2bkDLJUYokwL8aivcX6HW3C/go-log"
dstore "gx/ipfs/Qmf4xQhNomPNhrtZc67qSnfJSjxjXs9LWvknJtSXwimPrM/go-datastore"
)
var log = logging.Logger("gc")
// Result represents an incremental output from a garbage collection
// run. It contains either an error, or the cid of a removed object.
type Result struct {
KeyRemoved cid.Cid
Error error
}
// GC performs a mark and sweep garbage collection of the blocks in the blockstore
// first, it creates a 'marked' set and adds to it the following:
// - all recursively pinned blocks, plus all of their descendants (recursively)
// - bestEffortRoots, plus all of its descendants (recursively)
// - all directly pinned blocks
// - all blocks utilized internally by the pinner
//
// The routine then iterates over every block in the blockstore and
// deletes any block that is not found in the marked set.
func GC(ctx context.Context, bs bstore.GCBlockstore, dstor dstore.Datastore, pn pin.Pinner, bestEffortRoots []cid.Cid) <-chan Result {
elock := log.EventBegin(ctx, "GC.lockWait")
unlocker := bs.GCLock()
elock.Done()
elock = log.EventBegin(ctx, "GC.locked")
emark := log.EventBegin(ctx, "GC.mark")
bsrv := bserv.New(bs, offline.Exchange(bs))
ds := dag.NewDAGService(bsrv)
output := make(chan Result, 128)
go func() {
defer close(output)
defer unlocker.Unlock()
defer elock.Done()
gcs, err := ColoredSet(ctx, pn, ds, bestEffortRoots, output)
if err != nil {
select {
case output <- Result{Error: err}:
case <-ctx.Done():
}
return
}
emark.Append(logging.LoggableMap{
"blackSetSize": fmt.Sprintf("%d", gcs.Len()),
})
emark.Done()
esweep := log.EventBegin(ctx, "GC.sweep")
keychan, err := bs.AllKeysChan(ctx)
if err != nil {
select {
case output <- Result{Error: err}:
case <-ctx.Done():
}
return
}
errors := false
var removed uint64
loop:
for {
select {
case k, ok := <-keychan:
if !ok {
break loop
}
if !gcs.Has(k) {
err := bs.DeleteBlock(k)
removed++
if err != nil {
errors = true
output <- Result{Error: &CannotDeleteBlockError{k, err}}
//log.Errorf("Error removing key from blockstore: %s", err)
// continue as error is non-fatal
continue loop
}
select {
case output <- Result{KeyRemoved: k}:
case <-ctx.Done():
break loop
}
}
case <-ctx.Done():
break loop
}
}
esweep.Append(logging.LoggableMap{
"whiteSetSize": fmt.Sprintf("%d", removed),
})
esweep.Done()
if errors {
select {
case output <- Result{Error: ErrCannotDeleteSomeBlocks}:
case <-ctx.Done():
return
}
}
defer log.EventBegin(ctx, "GC.datastore").Done()
gds, ok := dstor.(dstore.GCDatastore)
if !ok {
return
}
err = gds.CollectGarbage()
if err != nil {
select {
case output <- Result{Error: err}:
case <-ctx.Done():
}
return
}
}()
return output
}
// Descendants recursively finds all the descendants of the given roots and
// adds them to the given cid.Set, using the provided dag.GetLinks function
// to walk the tree.
func Descendants(ctx context.Context, getLinks dag.GetLinks, set *cid.Set, roots []cid.Cid) error {
verifyGetLinks := func(ctx context.Context, c cid.Cid) ([]*ipld.Link, error) {
err := verifcid.ValidateCid(c)
if err != nil {
return nil, err
}
return getLinks(ctx, c)
}
verboseCidError := func(err error) error {
if strings.Contains(err.Error(), verifcid.ErrBelowMinimumHashLength.Error()) ||
strings.Contains(err.Error(), verifcid.ErrPossiblyInsecureHashFunction.Error()) {
err = fmt.Errorf("\"%s\"\nPlease run 'ipfs pin verify'"+
" to list insecure hashes. If you want to read them,"+
" please downgrade your go-ipfs to 0.4.13\n", err)
log.Error(err)
}
return err
}
for _, c := range roots {
set.Add(c)
// EnumerateChildren recursively walks the dag and adds the keys to the given set
err := dag.EnumerateChildren(ctx, verifyGetLinks, c, set.Visit)
if err != nil {
err = verboseCidError(err)
return err
}
}
return nil
}
// ColoredSet computes the set of nodes in the graph that are pinned by the
// pins in the given pinner.
func ColoredSet(ctx context.Context, pn pin.Pinner, ng ipld.NodeGetter, bestEffortRoots []cid.Cid, output chan<- Result) (*cid.Set, error) {
// KeySet currently implemented in memory, in the future, may be bloom filter or
// disk backed to conserve memory.
errors := false
gcs := cid.NewSet()
getLinks := func(ctx context.Context, cid cid.Cid) ([]*ipld.Link, error) {
links, err := ipld.GetLinks(ctx, ng, cid)
if err != nil {
errors = true
select {
case output <- Result{Error: &CannotFetchLinksError{cid, err}}:
case <-ctx.Done():
return nil, ctx.Err()
}
}
return links, nil
}
err := Descendants(ctx, getLinks, gcs, pn.RecursiveKeys())
if err != nil {
errors = true
select {
case output <- Result{Error: err}:
case <-ctx.Done():
return nil, ctx.Err()
}
}
bestEffortGetLinks := func(ctx context.Context, cid cid.Cid) ([]*ipld.Link, error) {
links, err := ipld.GetLinks(ctx, ng, cid)
if err != nil && err != ipld.ErrNotFound {
errors = true
select {
case output <- Result{Error: &CannotFetchLinksError{cid, err}}:
case <-ctx.Done():
return nil, ctx.Err()
}
}
return links, nil
}
err = Descendants(ctx, bestEffortGetLinks, gcs, bestEffortRoots)
if err != nil {
errors = true
select {
case output <- Result{Error: err}:
case <-ctx.Done():
return nil, ctx.Err()
}
}
for _, k := range pn.DirectKeys() {
gcs.Add(k)
}
err = Descendants(ctx, getLinks, gcs, pn.InternalPins())
if err != nil {
errors = true
select {
case output <- Result{Error: err}:
case <-ctx.Done():
return nil, ctx.Err()
}
}
if errors {
return nil, ErrCannotFetchAllLinks
}
return gcs, nil
}
// ErrCannotFetchAllLinks is returned as the last Result in the GC output
// channel when there was a error creating the marked set because of a
// problem when finding descendants.
var ErrCannotFetchAllLinks = errors.New("garbage collection aborted: could not retrieve some links")
// ErrCannotDeleteSomeBlocks is returned when removing blocks marked for
// deletion fails as the last Result in GC output channel.
var ErrCannotDeleteSomeBlocks = errors.New("garbage collection incomplete: could not delete some blocks")
// CannotFetchLinksError provides detailed information about which links
// could not be fetched and can appear as a Result in the GC output channel.
type CannotFetchLinksError struct {
Key cid.Cid
Err error
}
// Error implements the error interface for this type with a useful
// message.
func (e *CannotFetchLinksError) Error() string {
return fmt.Sprintf("could not retrieve links for %s: %s", e.Key, e.Err)
}
// CannotDeleteBlockError provides detailed information about which
// blocks could not be deleted and can appear as a Result in the GC output
// channel.
type CannotDeleteBlockError struct {
Key cid.Cid
Err error
}
// Error implements the error interface for this type with a
// useful message.
func (e *CannotDeleteBlockError) Error() string {
return fmt.Sprintf("could not remove %s: %s", e.Key, e.Err)
}