2021-07-19 13:51:46 +00:00
|
|
|
package dagstore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-07-20 09:04:47 +00:00
|
|
|
"errors"
|
2021-07-19 13:51:46 +00:00
|
|
|
"io"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
2021-07-26 14:14:59 +00:00
|
|
|
"github.com/filecoin-project/dagstore/index"
|
2021-07-19 13:51:46 +00:00
|
|
|
"github.com/ipfs/go-cid"
|
|
|
|
ds "github.com/ipfs/go-datastore"
|
|
|
|
bstore "github.com/ipfs/go-ipfs-blockstore"
|
|
|
|
logging "github.com/ipfs/go-log/v2"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
|
|
|
|
"github.com/filecoin-project/dagstore"
|
|
|
|
"github.com/filecoin-project/dagstore/mount"
|
|
|
|
"github.com/filecoin-project/dagstore/shard"
|
|
|
|
"github.com/filecoin-project/go-fil-markets/carstore"
|
|
|
|
"github.com/filecoin-project/go-fil-markets/shared"
|
|
|
|
)
|
|
|
|
|
2021-07-22 04:14:14 +00:00
|
|
|
const maxRecoverAttempts = 1
|
|
|
|
|
2021-07-19 13:51:46 +00:00
|
|
|
var log = logging.Logger("dagstore-wrapper")
|
|
|
|
|
|
|
|
// MarketDAGStoreConfig is the config the market needs to then construct a DAG Store.
|
|
|
|
type MarketDAGStoreConfig struct {
|
2021-07-30 14:28:20 +00:00
|
|
|
TransientsDir string
|
|
|
|
IndexDir string
|
|
|
|
Datastore ds.Datastore
|
|
|
|
MaxConcurrentIndex int
|
|
|
|
MaxConcurrentReadyFetches int
|
|
|
|
GCInterval time.Duration
|
2021-07-20 13:02:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DAGStore provides an interface for the DAG store that can be mocked out
|
|
|
|
// by tests
|
|
|
|
type DAGStore interface {
|
|
|
|
RegisterShard(ctx context.Context, key shard.Key, mnt mount.Mount, out chan dagstore.ShardResult, opts dagstore.RegisterOpts) error
|
|
|
|
AcquireShard(ctx context.Context, key shard.Key, out chan dagstore.ShardResult, _ dagstore.AcquireOpts) error
|
|
|
|
RecoverShard(ctx context.Context, key shard.Key, out chan dagstore.ShardResult, _ dagstore.RecoverOpts) error
|
2021-07-26 14:14:59 +00:00
|
|
|
GC(ctx context.Context) (*dagstore.GCResult, error)
|
2021-07-20 13:02:50 +00:00
|
|
|
Close() error
|
2021-07-29 20:13:34 +00:00
|
|
|
Start(ctx context.Context) error
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type closableBlockstore struct {
|
|
|
|
bstore.Blockstore
|
|
|
|
io.Closer
|
|
|
|
}
|
|
|
|
|
2021-07-20 10:45:20 +00:00
|
|
|
type Wrapper struct {
|
2021-07-20 09:04:47 +00:00
|
|
|
ctx context.Context
|
|
|
|
cancel context.CancelFunc
|
|
|
|
backgroundWg sync.WaitGroup
|
2021-07-19 13:51:46 +00:00
|
|
|
|
2021-07-20 13:02:50 +00:00
|
|
|
dagStore DAGStore
|
|
|
|
mountApi LotusAccessor
|
|
|
|
failureCh chan dagstore.ShardResult
|
|
|
|
traceCh chan dagstore.Trace
|
|
|
|
gcInterval time.Duration
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
|
|
|
|
2021-07-20 10:45:20 +00:00
|
|
|
var _ shared.DagStoreWrapper = (*Wrapper)(nil)
|
2021-07-19 13:51:46 +00:00
|
|
|
|
2021-07-20 10:45:20 +00:00
|
|
|
func NewDagStoreWrapper(cfg MarketDAGStoreConfig, mountApi LotusAccessor) (*Wrapper, error) {
|
2021-07-19 13:51:46 +00:00
|
|
|
// construct the DAG Store.
|
|
|
|
registry := mount.NewRegistry()
|
|
|
|
if err := registry.Register(lotusScheme, NewLotusMountTemplate(mountApi)); err != nil {
|
|
|
|
return nil, xerrors.Errorf("failed to create registry: %w", err)
|
|
|
|
}
|
|
|
|
|
2021-07-20 09:04:47 +00:00
|
|
|
// The dagstore will write Shard failures to the `failureCh` here.
|
2021-07-19 13:51:46 +00:00
|
|
|
failureCh := make(chan dagstore.ShardResult, 1)
|
2021-07-20 10:37:52 +00:00
|
|
|
// The dagstore will write Trace events to the `traceCh` here.
|
|
|
|
traceCh := make(chan dagstore.Trace, 32)
|
2021-07-26 14:14:59 +00:00
|
|
|
|
|
|
|
irepo, err := index.NewFSRepo(cfg.IndexDir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("failed to initialise dagstore index repo")
|
|
|
|
}
|
|
|
|
|
2021-07-19 13:51:46 +00:00
|
|
|
dcfg := dagstore.Config{
|
2021-07-27 16:24:48 +00:00
|
|
|
TransientsDir: cfg.TransientsDir,
|
|
|
|
IndexRepo: irepo,
|
|
|
|
Datastore: cfg.Datastore,
|
|
|
|
MountRegistry: registry,
|
|
|
|
FailureCh: failureCh,
|
|
|
|
TraceCh: traceCh,
|
|
|
|
// not limiting fetches globally, as the Lotus mount does
|
|
|
|
// conditional throttling.
|
2021-07-30 14:28:20 +00:00
|
|
|
MaxConcurrentIndex: cfg.MaxConcurrentIndex,
|
|
|
|
MaxConcurrentReadyFetches: cfg.MaxConcurrentReadyFetches,
|
|
|
|
RecoverOnStart: dagstore.RecoverOnAcquire,
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
|
|
|
dagStore, err := dagstore.NewDAGStore(dcfg)
|
|
|
|
if err != nil {
|
|
|
|
return nil, xerrors.Errorf("failed to create DAG store: %w", err)
|
|
|
|
}
|
|
|
|
|
2021-07-20 10:45:20 +00:00
|
|
|
return &Wrapper{
|
2021-07-20 13:02:50 +00:00
|
|
|
dagStore: dagStore,
|
|
|
|
mountApi: mountApi,
|
|
|
|
failureCh: failureCh,
|
|
|
|
traceCh: traceCh,
|
|
|
|
gcInterval: cfg.GCInterval,
|
2021-07-20 09:04:47 +00:00
|
|
|
}, nil
|
|
|
|
}
|
2021-07-19 13:51:46 +00:00
|
|
|
|
2021-07-29 20:13:34 +00:00
|
|
|
func (ds *Wrapper) Start(ctx context.Context) error {
|
2021-07-20 09:04:47 +00:00
|
|
|
ds.ctx, ds.cancel = context.WithCancel(ctx)
|
2021-07-19 13:51:46 +00:00
|
|
|
|
2021-07-22 04:14:14 +00:00
|
|
|
// Run a go-routine to do DagStore GC.
|
|
|
|
ds.backgroundWg.Add(1)
|
|
|
|
go ds.dagStoreGCLoop()
|
|
|
|
|
|
|
|
// run a go-routine to read the trace for debugging.
|
2021-07-20 09:04:47 +00:00
|
|
|
ds.backgroundWg.Add(1)
|
2021-07-22 04:14:14 +00:00
|
|
|
go ds.traceLoop()
|
2021-07-19 13:51:46 +00:00
|
|
|
|
2021-07-22 04:14:14 +00:00
|
|
|
// Run a go-routine for shard recovery
|
|
|
|
if dss, ok := ds.dagStore.(*dagstore.DAGStore); ok {
|
|
|
|
ds.backgroundWg.Add(1)
|
|
|
|
go dagstore.RecoverImmediately(ds.ctx, dss, ds.failureCh, maxRecoverAttempts, ds.backgroundWg.Done)
|
|
|
|
}
|
2021-07-29 20:13:34 +00:00
|
|
|
|
2021-07-29 20:14:47 +00:00
|
|
|
return ds.dagStore.Start(ctx)
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
|
|
|
|
2021-07-22 04:14:14 +00:00
|
|
|
func (ds *Wrapper) traceLoop() {
|
2021-07-20 09:04:47 +00:00
|
|
|
defer ds.backgroundWg.Done()
|
|
|
|
|
2021-07-20 13:02:50 +00:00
|
|
|
for ds.ctx.Err() == nil {
|
2021-07-20 09:04:47 +00:00
|
|
|
select {
|
2021-07-20 10:37:52 +00:00
|
|
|
// Log trace events from the DAG store
|
|
|
|
case tr := <-ds.traceCh:
|
|
|
|
log.Debugw("trace",
|
|
|
|
"shard-key", tr.Key.String(),
|
|
|
|
"op-type", tr.Op.String(),
|
|
|
|
"after", tr.After.String())
|
|
|
|
|
2021-07-22 04:14:14 +00:00
|
|
|
case <-ds.ctx.Done():
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-20 09:50:40 +00:00
|
|
|
|
2021-07-22 04:14:14 +00:00
|
|
|
func (ds *Wrapper) dagStoreGCLoop() {
|
|
|
|
defer ds.backgroundWg.Done()
|
|
|
|
|
|
|
|
gcTicker := time.NewTicker(ds.gcInterval)
|
|
|
|
defer gcTicker.Stop()
|
|
|
|
|
|
|
|
for ds.ctx.Err() == nil {
|
|
|
|
select {
|
|
|
|
// GC the DAG store on every tick
|
|
|
|
case <-gcTicker.C:
|
|
|
|
_, _ = ds.dagStore.GC(ds.ctx)
|
2021-07-20 09:04:47 +00:00
|
|
|
|
|
|
|
// Exit when the DAG store wrapper is shutdown
|
|
|
|
case <-ds.ctx.Done():
|
|
|
|
return
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-20 10:45:20 +00:00
|
|
|
func (ds *Wrapper) LoadShard(ctx context.Context, pieceCid cid.Cid) (carstore.ClosableBlockstore, error) {
|
2021-07-21 15:17:29 +00:00
|
|
|
log.Debugf("acquiring shard for piece CID %s", pieceCid)
|
2021-07-19 13:51:46 +00:00
|
|
|
key := shard.KeyFromCID(pieceCid)
|
|
|
|
resch := make(chan dagstore.ShardResult, 1)
|
|
|
|
err := ds.dagStore.AcquireShard(ctx, key, resch, dagstore.AcquireOpts{})
|
2021-07-22 04:14:14 +00:00
|
|
|
log.Debugf("sent message to acquire shard for piece CID %s", pieceCid)
|
2021-07-19 13:51:46 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2021-07-20 09:04:47 +00:00
|
|
|
if !errors.Is(err, dagstore.ErrShardUnknown) {
|
2021-07-19 13:51:46 +00:00
|
|
|
return nil, xerrors.Errorf("failed to schedule acquire shard for piece CID %s: %w", pieceCid, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the DAGStore does not know about the Shard -> register it and then try to acquire it again.
|
|
|
|
log.Warnw("failed to load shard as shard is not registered, will re-register", "pieceCID", pieceCid)
|
2021-07-20 10:52:45 +00:00
|
|
|
// The path of a transient file that we can ask the DAG Store to use
|
|
|
|
// to perform the Indexing rather than fetching it via the Mount if
|
|
|
|
// we already have a transient file. However, we don't have it here
|
|
|
|
// and therefore we pass an empty file path.
|
|
|
|
carPath := ""
|
|
|
|
if err := shared.RegisterShardSync(ctx, ds, pieceCid, carPath, false); err != nil {
|
2021-07-19 13:51:46 +00:00
|
|
|
return nil, xerrors.Errorf("failed to re-register shard during loading piece CID %s: %w", pieceCid, err)
|
|
|
|
}
|
|
|
|
log.Warnw("successfully re-registered shard", "pieceCID", pieceCid)
|
|
|
|
|
|
|
|
resch = make(chan dagstore.ShardResult, 1)
|
|
|
|
if err := ds.dagStore.AcquireShard(ctx, key, resch, dagstore.AcquireOpts{}); err != nil {
|
|
|
|
return nil, xerrors.Errorf("failed to acquire Shard for piece CID %s after re-registering: %w", pieceCid, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-20 09:04:47 +00:00
|
|
|
// TODO: The context is not yet being actively monitored by the DAG store,
|
|
|
|
// so we need to select against ctx.Done() until the following issue is
|
|
|
|
// implemented:
|
|
|
|
// https://github.com/filecoin-project/dagstore/issues/39
|
2021-07-19 13:51:46 +00:00
|
|
|
var res dagstore.ShardResult
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return nil, ctx.Err()
|
|
|
|
case res = <-resch:
|
|
|
|
if res.Error != nil {
|
|
|
|
return nil, xerrors.Errorf("failed to acquire shard for piece CID %s: %w", pieceCid, res.Error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bs, err := res.Accessor.Blockstore()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2021-07-21 15:17:29 +00:00
|
|
|
log.Debugf("successfully loaded blockstore for piece CID %s", pieceCid)
|
2021-07-19 13:51:46 +00:00
|
|
|
return &closableBlockstore{Blockstore: NewReadOnlyBlockstore(bs), Closer: res.Accessor}, nil
|
|
|
|
}
|
|
|
|
|
2021-07-20 10:45:20 +00:00
|
|
|
func (ds *Wrapper) RegisterShard(ctx context.Context, pieceCid cid.Cid, carPath string, eagerInit bool, resch chan dagstore.ShardResult) error {
|
2021-07-19 13:51:46 +00:00
|
|
|
// Create a lotus mount with the piece CID
|
|
|
|
key := shard.KeyFromCID(pieceCid)
|
|
|
|
mt, err := NewLotusMount(pieceCid, ds.mountApi)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed to create lotus mount for piece CID %s: %w", pieceCid, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register the shard
|
|
|
|
opts := dagstore.RegisterOpts{
|
|
|
|
ExistingTransient: carPath,
|
|
|
|
LazyInitialization: !eagerInit,
|
|
|
|
}
|
|
|
|
err = ds.dagStore.RegisterShard(ctx, key, mt, resch, opts)
|
|
|
|
if err != nil {
|
|
|
|
return xerrors.Errorf("failed to schedule register shard for piece CID %s: %w", pieceCid, err)
|
|
|
|
}
|
2021-07-22 04:14:14 +00:00
|
|
|
log.Debugf("successfully submitted Register Shard request for piece CID %s with eagerInit=%t", pieceCid, eagerInit)
|
2021-07-19 13:51:46 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-07-20 10:45:20 +00:00
|
|
|
func (ds *Wrapper) Close() error {
|
2021-07-20 09:04:47 +00:00
|
|
|
// Cancel the context
|
|
|
|
ds.cancel()
|
|
|
|
|
|
|
|
// Close the DAG store
|
2021-07-22 04:14:14 +00:00
|
|
|
log.Info("will close the dagstore")
|
2021-07-19 13:51:46 +00:00
|
|
|
if err := ds.dagStore.Close(); err != nil {
|
2021-07-20 09:04:47 +00:00
|
|
|
return xerrors.Errorf("failed to close DAG store: %w", err)
|
2021-07-19 13:51:46 +00:00
|
|
|
}
|
2021-07-22 04:14:14 +00:00
|
|
|
log.Info("dagstore closed")
|
2021-07-19 13:51:46 +00:00
|
|
|
|
2021-07-20 09:04:47 +00:00
|
|
|
// Wait for the background go routine to exit
|
2021-07-22 04:14:14 +00:00
|
|
|
log.Info("waiting for dagstore background wrapper routines to exist")
|
2021-07-20 09:04:47 +00:00
|
|
|
ds.backgroundWg.Wait()
|
2021-07-22 04:14:14 +00:00
|
|
|
log.Info("exited dagstore background warpper routines")
|
2021-07-19 13:51:46 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|