blockchain: always relock chainLock for subscription callbacks

For various b.sendNotifcation() callbacks, if a runtime panic happens,
we don't get any useful debugging information since the error that
happens first is the "unlock of unlocked mutex" error.

This is because we temporarily unlock the chainLock for callbacks and
then relock them.  However, since the relocking code is executed after
the completion of the callback, if an error happens during that
callback, we never relock the chainLock.

Switching to an anonymous function and having the unlock code as a
defer will ensure that the lock always relocks.
This commit is contained in:
Calvin Kim 2024-02-27 14:32:42 +09:00
parent 13152b35e1
commit 3d1150a1a8
2 changed files with 15 additions and 9 deletions

View file

@ -84,9 +84,11 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
// Notify the caller that the new block was accepted into the block
// chain. The caller would typically want to react by relaying the
// inventory to other peers.
b.chainLock.Unlock()
b.sendNotification(NTBlockAccepted, block)
b.chainLock.Lock()
func() {
b.chainLock.Unlock()
defer b.chainLock.Lock()
b.sendNotification(NTBlockAccepted, block)
}()
return isMainChain, nil
}

View file

@ -730,9 +730,11 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block,
// Notify the caller that the block was connected to the main chain.
// The caller would typically want to react with actions such as
// updating wallets.
b.chainLock.Unlock()
b.sendNotification(NTBlockConnected, block)
b.chainLock.Lock()
func() {
b.chainLock.Unlock()
defer b.chainLock.Lock()
b.sendNotification(NTBlockConnected, block)
}()
// Since we may have changed the UTXO cache, we make sure it didn't exceed its
// maximum size. If we're pruned and have flushed already, this will be a no-op.
@ -853,9 +855,11 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view
// Notify the caller that the block was disconnected from the main
// chain. The caller would typically want to react with actions such as
// updating wallets.
b.chainLock.Unlock()
b.sendNotification(NTBlockDisconnected, block)
b.chainLock.Lock()
func() {
b.chainLock.Unlock()
defer b.chainLock.Lock()
b.sendNotification(NTBlockDisconnected, block)
}()
return nil
}