From dc6c0408031d7b64ae9c1f7dd0b85405d34c639b Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 29 Nov 2019 11:35:37 +0100 Subject: [PATCH 1/2] lnd: make Ready signal for custom listeners This allows the caller to know when lnd is ready to accept RPC calls, which is inmportant for mobile applications where eveything happens in process. --- lnd.go | 48 +++++++++++++++++++++++++++++++++--------------- rpcserver.go | 8 +++++--- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/lnd.go b/lnd.go index ccce8f9f..ee9cd268 100644 --- a/lnd.go +++ b/lnd.go @@ -59,16 +59,25 @@ var ( networkDir string ) +// ListnerWithSignal is a net.Listner that has an additional Ready channel that +// will be closed when a server starts listening. +type ListenerWithSignal struct { + net.Listener + + // Ready will be closed by the server listening on Listener. + Ready chan struct{} +} + // ListenerCfg is a wrapper around custom listeners that can be passed to lnd // when calling its main method. type ListenerCfg struct { // WalletUnlocker can be set to the listener to use for the wallet // unlocker. If nil a regular network listener will be created. - WalletUnlocker net.Listener + WalletUnlocker *ListenerWithSignal // RPCListener can be set to the listener to use for the RPC server. If // nil a regular network listener will be created. - RPCListener net.Listener + RPCListener *ListenerWithSignal } // rpcListeners is a function type used for closures that fetches a set of RPC @@ -76,7 +85,8 @@ type ListenerCfg struct { // with these listeners. If no custom listeners are present, this should return // normal listeners from the RPC endpoints defined in the config, and server // options specifying TLS. -type rpcListeners func() ([]net.Listener, func(), []grpc.ServerOption, error) +type rpcListeners func() ([]*ListenerWithSignal, func(), []grpc.ServerOption, + error) // Main is the true entry point for lnd. This function is required since defers // created in the top-level scope of a main method aren't executed if os.Exit() @@ -235,10 +245,10 @@ func Main(lisCfg ListenerCfg) error { // getListeners is a closure that creates listeners from the // RPCListeners defined in the config. It also returns a cleanup // closure and the server options to use for the GRPC server. - getListeners := func() ([]net.Listener, func(), []grpc.ServerOption, - error) { + getListeners := func() ([]*ListenerWithSignal, func(), + []grpc.ServerOption, error) { - var grpcListeners []net.Listener + var grpcListeners []*ListenerWithSignal for _, grpcEndpoint := range cfg.RPCListeners { // Start a gRPC server listening for HTTP/2 // connections. @@ -248,7 +258,11 @@ func Main(lisCfg ListenerCfg) error { grpcEndpoint) return nil, nil, nil, err } - grpcListeners = append(grpcListeners, lis) + grpcListeners = append( + grpcListeners, &ListenerWithSignal{ + Listener: lis, + Ready: make(chan struct{}), + }) } cleanup := func() { @@ -262,7 +276,7 @@ func Main(lisCfg ListenerCfg) error { // walletUnlockerListeners is a closure we'll hand to the wallet // unlocker, that will be called when it needs listeners for its GPRC // server. - walletUnlockerListeners := func() ([]net.Listener, func(), + walletUnlockerListeners := func() ([]*ListenerWithSignal, func(), []grpc.ServerOption, error) { // If we have chosen to start with a dedicated listener for the @@ -271,8 +285,8 @@ func Main(lisCfg ListenerCfg) error { // TODO(halseth): any point in adding TLS support for custom // listeners? if lisCfg.WalletUnlocker != nil { - return []net.Listener{lisCfg.WalletUnlocker}, func() {}, - []grpc.ServerOption{}, nil + return []*ListenerWithSignal{lisCfg.WalletUnlocker}, + func() {}, []grpc.ServerOption{}, nil } // Otherwise we'll return the regular listeners. @@ -501,8 +515,8 @@ func Main(lisCfg ListenerCfg) error { // rpcListeners is a closure we'll hand to the rpc server, that will be // called when it needs listeners for its GPRC server. - rpcListeners := func() ([]net.Listener, func(), []grpc.ServerOption, - error) { + rpcListeners := func() ([]*ListenerWithSignal, func(), + []grpc.ServerOption, error) { // If we have chosen to start with a dedicated listener for the // rpc server, we return it directly, and empty server options @@ -510,8 +524,8 @@ func Main(lisCfg ListenerCfg) error { // TODO(halseth): any point in adding TLS support for custom // listeners? if lisCfg.RPCListener != nil { - return []net.Listener{lisCfg.RPCListener}, func() {}, - []grpc.ServerOption{}, nil + return []*ListenerWithSignal{lisCfg.RPCListener}, + func() {}, []grpc.ServerOption{}, nil } // Otherwise we'll return the regular listeners. @@ -841,9 +855,13 @@ func waitForWalletPassword(restEndpoints []net.Addr, for _, lis := range listeners { wg.Add(1) - go func(lis net.Listener) { + go func(lis *ListenerWithSignal) { rpcsLog.Infof("password RPC server listening on %s", lis.Addr()) + + // Close the ready chan to indicate we are listening. + close(lis.Ready) + wg.Done() grpcServer.Serve(lis) }(lis) diff --git a/rpcserver.go b/rpcserver.go index 63d9d8f5..c9b1eee4 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -9,7 +9,6 @@ import ( "fmt" "io" "math" - "net" "net/http" "sort" "strings" @@ -456,7 +455,7 @@ type rpcServer struct { // listeners is a list of listeners to use when starting the grpc // server. We make it configurable such that the grpc server can listen // on custom interfaces. - listeners []net.Listener + listeners []*ListenerWithSignal // listenerCleanUp are a set of closures functions that will allow this // main RPC server to clean up all the listening socket created for the @@ -706,8 +705,11 @@ func (r *rpcServer) Start() error { // With all the sub-servers started, we'll spin up the listeners for // the main RPC server itself. for _, lis := range r.listeners { - go func(lis net.Listener) { + go func(lis *ListenerWithSignal) { rpcsLog.Infof("RPC server listening on %s", lis.Addr()) + + // Close the ready chan to indicate we are listening. + close(lis.Ready) r.grpcServer.Serve(lis) }(lis) } From 260704f3cdba8829fe34b23b1672ae0668bc089b Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 29 Nov 2019 11:51:50 +0100 Subject: [PATCH 2/2] mobile: call Start callback when lnd ready, add unlock callback Use the custom listeners' Ready channels to provide callbacks to the mobile application when the gRPC services are ready. NOTE: this changes the Start() API by adding one extra callback. --- mobile/bindings.go | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/mobile/bindings.go b/mobile/bindings.go index 9fb79215..7619837a 100644 --- a/mobile/bindings.go +++ b/mobile/bindings.go @@ -16,7 +16,11 @@ import ( // extraArgs can be used to pass command line arguments to lnd that will // override what is found in the config file. Example: // extraArgs = "--bitcoin.testnet --lnddir=\"/tmp/folder name/\" --profile=5050" -func Start(extraArgs string, callback Callback) { +// +// The unlockerReady callback is called when the WalletUnlocker service is +// ready, and rpcReady is called after the wallet has been unlocked and lnd is +// ready to accept RPC calls. +func Start(extraArgs string, unlockerReady, rpcReady Callback) { // Split the argument string on "--" to get separated command line // arguments. var splitArgs []string @@ -33,11 +37,24 @@ func Start(extraArgs string, callback Callback) { // startup. os.Args = append(os.Args, splitArgs...) + // Set up channels that will be notified when the RPC servers are ready + // to accept calls. + var ( + unlockerListening = make(chan struct{}) + rpcListening = make(chan struct{}) + ) + // We call the main method with the custom in-memory listeners called // by the mobile APIs, such that the grpc server will use these. cfg := lnd.ListenerCfg{ - WalletUnlocker: walletUnlockerLis, - RPCListener: lightningLis, + WalletUnlocker: &lnd.ListenerWithSignal{ + Listener: walletUnlockerLis, + Ready: unlockerListening, + }, + RPCListener: &lnd.ListenerWithSignal{ + Listener: lightningLis, + Ready: rpcListening, + }, } // Call the "real" main in a nested manner so the defers will properly @@ -53,9 +70,15 @@ func Start(extraArgs string, callback Callback) { } }() - // TODO(halseth): callback when RPC server is actually running. Since - // the RPC server might take a while to start up, the client might - // assume it is ready to accept calls when this callback is sent, while - // it's not. - callback.OnResponse([]byte("started")) + // Finally we start two go routines that will call the provided + // callbacks when the RPC servers are ready to accept calls. + go func() { + <-unlockerListening + unlockerReady.OnResponse([]byte{}) + }() + + go func() { + <-rpcListening + rpcReady.OnResponse([]byte{}) + }() }