mirror of
https://github.com/ipfs/kubo.git
synced 2025-05-17 23:16:11 +08:00
179 lines
5.3 KiB
Go
179 lines
5.3 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ipfs/boxo/bootstrap"
|
|
"github.com/ipfs/kubo/core/node"
|
|
|
|
"github.com/ipfs/go-metrics-interface"
|
|
"go.uber.org/dig"
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
// FXNodeInfo contains information useful for adding fx options.
|
|
// This is the extension point for providing more info/context to fx plugins
|
|
// to make decisions about what options to include.
|
|
type FXNodeInfo struct {
|
|
FXOptions []fx.Option
|
|
}
|
|
|
|
// fxOptFunc takes in some info about the IPFS node and returns the full set of fx opts to use.
|
|
type fxOptFunc func(FXNodeInfo) ([]fx.Option, error)
|
|
|
|
var fxOptionFuncs []fxOptFunc
|
|
|
|
// RegisterFXOptionFunc registers a function that is run before the fx app is initialized.
|
|
// Functions are invoked in the order they are registered,
|
|
// and the resulting options are passed into the next function's FXNodeInfo.
|
|
//
|
|
// Note that these are applied globally, by all invocations of NewNode.
|
|
// There are multiple places in Kubo that construct nodes, such as:
|
|
// - Repo initialization
|
|
// - Daemon initialization
|
|
// - When running migrations
|
|
// - etc.
|
|
//
|
|
// If your fx options are doing anything sophisticated, you should keep this in mind.
|
|
//
|
|
// For example, if you plug in a blockservice that disallows non-allowlisted CIDs,
|
|
// this may break migrations that fetch migration code over IPFS.
|
|
func RegisterFXOptionFunc(optFunc fxOptFunc) {
|
|
fxOptionFuncs = append(fxOptionFuncs, optFunc)
|
|
}
|
|
|
|
// from https://stackoverflow.com/a/59348871
|
|
type valueContext struct {
|
|
context.Context
|
|
}
|
|
|
|
func (valueContext) Deadline() (deadline time.Time, ok bool) { return }
|
|
func (valueContext) Done() <-chan struct{} { return nil }
|
|
func (valueContext) Err() error { return nil }
|
|
|
|
type BuildCfg = node.BuildCfg // Alias for compatibility until we properly refactor the constructor interface
|
|
|
|
// NewNode constructs and returns an IpfsNode using the given cfg.
|
|
func NewNode(ctx context.Context, cfg *BuildCfg) (*IpfsNode, error) {
|
|
// save this context as the "lifetime" ctx.
|
|
lctx := ctx
|
|
|
|
// derive a new context that ignores cancellations from the lifetime ctx.
|
|
ctx, cancel := context.WithCancel(valueContext{ctx})
|
|
|
|
// add a metrics scope.
|
|
ctx = metrics.CtxScope(ctx, "ipfs")
|
|
|
|
n := &IpfsNode{
|
|
ctx: ctx,
|
|
}
|
|
|
|
opts := []fx.Option{
|
|
node.IPFS(ctx, cfg),
|
|
fx.NopLogger,
|
|
}
|
|
for _, optFunc := range fxOptionFuncs {
|
|
var err error
|
|
opts, err = optFunc(FXNodeInfo{FXOptions: opts})
|
|
if err != nil {
|
|
cancel()
|
|
return nil, fmt.Errorf("building fx opts: %w", err)
|
|
}
|
|
}
|
|
//nolint:staticcheck // https://github.com/ipfs/kubo/pull/9423#issuecomment-1341038770
|
|
opts = append(opts, fx.Extract(n))
|
|
|
|
app := fx.New(opts...)
|
|
|
|
var once sync.Once
|
|
var stopErr error
|
|
n.stop = func() error {
|
|
once.Do(func() {
|
|
stopErr = app.Stop(context.Background())
|
|
if stopErr != nil {
|
|
log.Error("failure on stop: ", stopErr)
|
|
}
|
|
// Cancel the context _after_ the app has stopped.
|
|
cancel()
|
|
})
|
|
return stopErr
|
|
}
|
|
n.IsOnline = cfg.Online
|
|
|
|
go func() {
|
|
// Shut down the application if the lifetime context is canceled.
|
|
// NOTE: we _should_ stop the application by calling `Close()`
|
|
// on the process. But we currently manage everything with contexts.
|
|
select {
|
|
case <-lctx.Done():
|
|
err := n.stop()
|
|
if err != nil {
|
|
log.Error("failure on stop: ", err)
|
|
}
|
|
case <-ctx.Done():
|
|
}
|
|
}()
|
|
|
|
if app.Err() != nil {
|
|
return nil, logAndUnwrapFxError(app.Err())
|
|
}
|
|
|
|
if err := app.Start(ctx); err != nil {
|
|
return nil, logAndUnwrapFxError(err)
|
|
}
|
|
|
|
// TODO: How soon will bootstrap move to libp2p?
|
|
if !cfg.Online {
|
|
return n, nil
|
|
}
|
|
|
|
return n, n.Bootstrap(bootstrap.DefaultBootstrapConfig)
|
|
}
|
|
|
|
// Log the entire `app.Err()` but return only the innermost one to the user
|
|
// given the full error can be very long (as it can expose the entire build
|
|
// graph in a single string).
|
|
//
|
|
// The fx.App error exposed through `app.Err()` normally contains un-exported
|
|
// errors from its low-level `dig` package:
|
|
// * https://github.com/uber-go/dig/blob/5e5a20d/error.go#L82
|
|
// These usually wrap themselves in many layers to expose where in the build
|
|
// chain did the error happen. Although useful for a developer that needs to
|
|
// debug it, it can be very confusing for a user that just wants the IPFS error
|
|
// that he can probably fix without being aware of the entire chain.
|
|
// Unwrapping everything is not the best solution as there can be useful
|
|
// information in the intermediate errors, mainly in the next to last error
|
|
// that locates which component is the build error coming from, but it's the
|
|
// best we can do at the moment given all errors in dig are private and we
|
|
// just have the generic `RootCause` API.
|
|
func logAndUnwrapFxError(fxAppErr error) error {
|
|
if fxAppErr == nil {
|
|
return nil
|
|
}
|
|
|
|
log.Error("constructing the node: ", fxAppErr)
|
|
|
|
err := fxAppErr
|
|
for {
|
|
extractedErr := dig.RootCause(err)
|
|
// Note that the `RootCause` name is misleading as it just unwraps only
|
|
// *one* error layer at a time, so we need to continuously call it.
|
|
if !reflect.TypeOf(extractedErr).Comparable() {
|
|
// Some internal errors are not comparable (e.g., `dig.errMissingTypes`
|
|
// which is a slice) and we can't go further.
|
|
break
|
|
}
|
|
if extractedErr == err {
|
|
// We didn't unwrap any new error in the last call, reached the innermost one.
|
|
break
|
|
}
|
|
err = extractedErr
|
|
}
|
|
|
|
return fmt.Errorf("constructing the node (see log for full detail): %w", err)
|
|
}
|