lnwallet: add ability to cancel the channel's closeObserver

This commit adds the ability to cancel a channel’s internal
closeObserver goroutine by adding a new public facing Stop method.

Additionally, we now make passing a ChainNotifier interface completely
optional. If the ChainNotifier isn’t passed in as a constructor, then
the closeObserver goroutine will never be launched. This new feature
lets the caller ensure that only a single closeObsever for any given
channels exists.
This commit is contained in:
Olaoluwa Osuntokun 2017-02-02 17:10:57 -08:00
parent f4b403679b
commit 62bcd59db4
No known key found for this signature in database
GPG Key ID: 9CC5B105D03521A2

@ -441,11 +441,8 @@ type LightningChannel struct {
// channel. // channel.
RemoteFundingKey *btcec.PublicKey RemoteFundingKey *btcec.PublicKey
started int32
shutdown int32 shutdown int32
quit chan struct{} quit chan struct{}
wg sync.WaitGroup
} }
// NewLightningChannel creates a new, active payment channel given an // NewLightningChannel creates a new, active payment channel given an
@ -477,6 +474,7 @@ func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier,
ContractBreach: make(chan *BreachRetribution, 1), ContractBreach: make(chan *BreachRetribution, 1),
LocalFundingKey: state.OurMultiSigKey, LocalFundingKey: state.OurMultiSigKey,
RemoteFundingKey: state.TheirMultiSigKey, RemoteFundingKey: state.TheirMultiSigKey,
quit: make(chan struct{}),
} }
// Initialize both of our chains the current un-revoked commitment for // Initialize both of our chains the current un-revoked commitment for
@ -519,19 +517,25 @@ func NewLightningChannel(signer Signer, events chainntnfs.ChainNotifier,
InputIndex: 0, InputIndex: 0,
} }
// Register for a notification to be dispatched if the funding outpoint // We'll only launch a close observer if the ChainNotifier
// has been spent. This indicates that either us or the remote party // implementation is non-nil. Passing a nil value indicates that the
// has broadcasted a commitment transaction on-chain. // channel shouldn't be actively watched for.
if lc.channelEvents != nil {
// Register for a notification to be dispatched if the funding
// outpoint has been spent. This indicates that either us or
// the remote party has broadcasted a commitment transaction
// on-chain.
fundingOut := &lc.fundingTxIn.PreviousOutPoint fundingOut := &lc.fundingTxIn.PreviousOutPoint
channelCloseNtfn, err := lc.channelEvents.RegisterSpendNtfn(fundingOut) channelCloseNtfn, err := lc.channelEvents.RegisterSpendNtfn(fundingOut)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Launch the close observer which will vigilantly watch the network // Launch the close observer which will vigilantly watch the
// for any broadcasts the current or prior commitment transactions, // network for any broadcasts the current or prior commitment
// taking action accordingly. // transactions, taking action accordingly.
go lc.closeObserver(channelCloseNtfn) go lc.closeObserver(channelCloseNtfn)
}
return lc, nil return lc, nil
} }
@ -682,13 +686,28 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// //
// NOTE: This MUST be run as a goroutine. // NOTE: This MUST be run as a goroutine.
func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEvent) { func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEvent) {
walletLog.Infof("Close observer for ChannelPoint(%v) active",
lc.channelState.ChanID)
var (
commitSpend *chainntnfs.SpendDetail
ok bool
)
select {
// If the daemon is shutting down, then this notification channel will // If the daemon is shutting down, then this notification channel will
// be closed, so check the second read-value to avoid a false positive. // be closed, so check the second read-value to avoid a false positive.
commitSpend, ok := <-channelCloseNtfn.Spend case commitSpend, ok = <-channelCloseNtfn.Spend:
if !ok { if !ok {
return return
} }
// Otherwise, we've be signalled to bail out early by the
// caller/maintainer of this channel.
case <-lc.quit:
return
}
// If we've already initiated a local cooperative or unilateral close // If we've already initiated a local cooperative or unilateral close
// locally, then we have nothing more to do. // locally, then we have nothing more to do.
lc.RLock() lc.RLock()
@ -769,6 +788,16 @@ func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEven
} }
} }
// Stop gracefully shuts down any active goroutines spawned by the
// LightningChannel during regular duties.
func (lc *LightningChannel) Stop() {
if !atomic.CompareAndSwapInt32(&lc.shutdown, 0, 1) {
return
}
close(lc.quit)
}
// restoreStateLogs runs through the current locked-in HTLCs from the point of // restoreStateLogs runs through the current locked-in HTLCs from the point of
// view of the channel and insert corresponding log entries (both local and // view of the channel and insert corresponding log entries (both local and
// remote) for each HTLC read from disk. This method is required sync the // remote) for each HTLC read from disk. This method is required sync the