mirror of
https://github.com/ipfs/kubo.git
synced 2026-02-21 10:27:46 +08:00
138 lines
3.8 KiB
Go
138 lines
3.8 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ipfs/go-ipfs/core/bootstrap"
|
|
"github.com/ipfs/go-ipfs/core/node"
|
|
|
|
"github.com/ipfs/go-metrics-interface"
|
|
"go.uber.org/dig"
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
// 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,
|
|
}
|
|
|
|
app := fx.New(
|
|
node.IPFS(ctx, cfg),
|
|
|
|
fx.NopLogger,
|
|
fx.Extract(n),
|
|
)
|
|
|
|
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)
|
|
}
|