diff --git a/loopd/daemon.go b/loopd/daemon.go index 471a344..46b00f4 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -11,6 +11,7 @@ import ( "sync" "sync/atomic" + "github.com/coreos/bbolt" proxy "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop" @@ -128,6 +129,13 @@ func (d *Daemon) Start() error { // and error handlers. If this fails, then nothing has been started yet // and we can just return the error. err = d.initialize() + if errors.Is(err, bbolt.ErrTimeout) { + // We're trying to be started as a standalone Loop daemon, most + // likely LiT is already running and blocking the DB + return fmt.Errorf("%v: make sure no other loop daemon "+ + "process (standalone or embedded in "+ + "lightning-terminal) is running", err) + } if err != nil { return err } @@ -168,7 +176,14 @@ func (d *Daemon) StartAsSubserver(lndGrpc *lndclient.GrpcLndServices) error { // the swap server client, the RPC server instance and our main swap // handlers. If this fails, then nothing has been started yet and we can // just return the error. - return d.initialize() + err := d.initialize() + if errors.Is(err, bbolt.ErrTimeout) { + // We're trying to be started inside LiT so there most likely is + // another standalone Loop process blocking the DB. + return fmt.Errorf("%v: make sure no other loop daemon "+ + "process is running", err) + } + return err } // ValidateMacaroon extracts the macaroon from the context's gRPC metadata, diff --git a/loopd/macaroons.go b/loopd/macaroons.go index a716ba4..5ac13b0 100644 --- a/loopd/macaroons.go +++ b/loopd/macaroons.go @@ -5,8 +5,9 @@ import ( "fmt" "io/ioutil" "os" - "time" + "github.com/coreos/bbolt" + "github.com/lightninglabs/loop/loopdb" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/macaroons" "google.golang.org/grpc" @@ -17,10 +18,6 @@ const ( // loopMacaroonLocation is the value we use for the loopd macaroons' // "Location" field when baking them. loopMacaroonLocation = "loop" - - // macDatabaseOpenTimeout is how long we wait for acquiring the lock on - // the macaroon database before we give up with an error. - macDatabaseOpenTimeout = time.Second * 5 ) var ( @@ -150,8 +147,14 @@ func (d *Daemon) startMacaroonService() error { var err error d.macaroonService, err = macaroons.NewService( d.cfg.DataDir, loopMacaroonLocation, false, - macDatabaseOpenTimeout, macaroons.IPLockChecker, + loopdb.DefaultLoopDBTimeout, macaroons.IPLockChecker, ) + if err == bbolt.ErrTimeout { + return fmt.Errorf("%w: couldn't obtain exclusive lock on "+ + "%s/%s, timed out after %v", bbolt.ErrTimeout, + d.cfg.DataDir, "macaroons.db", + loopdb.DefaultLoopDBTimeout) + } if err != nil { return fmt.Errorf("unable to set up macaroon authentication: "+ "%v", err) diff --git a/loopdb/store.go b/loopdb/store.go index d4651f6..420a00c 100644 --- a/loopdb/store.go +++ b/loopdb/store.go @@ -99,9 +99,18 @@ var ( keyLength = 33 ) -// DefaultLoopOutHtlcConfirmations is the default number of confirmations we -// set for a loop out htlc. -const DefaultLoopOutHtlcConfirmations uint32 = 1 +const ( + // DefaultLoopOutHtlcConfirmations is the default number of + // confirmations we set for a loop out htlc. + DefaultLoopOutHtlcConfirmations uint32 = 1 + + // DefaultLoopDBTimeout is the default maximum time we wait for the + // Loop bbolt database to be opened. If the database is already opened + // by another process, the unique lock cannot be obtained. With the + // timeout we error out after the given time instead of just blocking + // for forever. + DefaultLoopDBTimeout = 5 * time.Second +) // fileExists returns true if the file exists, and false otherwise. func fileExists(path string) bool { @@ -139,7 +148,14 @@ func NewBoltSwapStore(dbPath string, chainParams *chaincfg.Params) ( // Now that we know that path exists, we'll open up bolt, which // implements our default swap store. path := filepath.Join(dbPath, dbFileName) - bdb, err := bbolt.Open(path, 0600, nil) + bdb, err := bbolt.Open(path, 0600, &bbolt.Options{ + Timeout: DefaultLoopDBTimeout, + }) + if err == bbolt.ErrTimeout { + return nil, fmt.Errorf("%w: couldn't obtain exclusive lock on "+ + "%s, timed out after %v", bbolt.ErrTimeout, path, + DefaultLoopDBTimeout) + } if err != nil { return nil, err } diff --git a/release_notes.md b/release_notes.md index 9a87269..141c56f 100644 --- a/release_notes.md +++ b/release_notes.md @@ -19,3 +19,6 @@ This file tracks release notes for the loop client. #### Breaking Changes #### Bug Fixes + - Instead of just blocking for forever without any apparent reason if another + Loop daemon process is already running, we now exit with an error after 5 + seconds if acquiring the unique lock on the Loop `bbolt` DB fails.