Merge pull request #8385 from Roasbeef/ping-async-dc

peer: make PingManager disconnect call async
This commit is contained in:
Elle 2024-01-23 09:24:11 +02:00 committed by GitHub
commit 51de320d69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 83 additions and 76 deletions

View File

@ -29,6 +29,11 @@
FilterKnownChanIDs](https://github.com/lightningnetwork/lnd/pull/8400) by FilterKnownChanIDs](https://github.com/lightningnetwork/lnd/pull/8400) by
ensuring the `cacheMu` mutex is acquired before the main database lock. ensuring the `cacheMu` mutex is acquired before the main database lock.
* [Prevent](https://github.com/lightningnetwork/lnd/pull/8385) ping failures
from [deadlocking](https://github.com/lightningnetwork/lnd/issues/8379)
the peer connection.
# New Features # New Features
## Functional Enhancements ## Functional Enhancements
## RPC Additions ## RPC Additions
@ -50,5 +55,8 @@
## Tooling and Documentation ## Tooling and Documentation
# Contributors (Alphabetical Order) # Contributors (Alphabetical Order)
* Elle Mouton * Elle Mouton
* Keagan McClelland
* Olaoluwa Osuntokun
* ziggie1984 * ziggie1984

View File

@ -577,7 +577,7 @@ func NewBrontide(cfg Config) *Brontide {
eStr := "pong response failure for %s: %v " + eStr := "pong response failure for %s: %v " +
"-- disconnecting" "-- disconnecting"
p.log.Warnf(eStr, p, err) p.log.Warnf(eStr, p, err)
p.Disconnect(fmt.Errorf(eStr, p, err)) go p.Disconnect(fmt.Errorf(eStr, p, err))
}, },
}) })

View File

@ -13,7 +13,6 @@ import (
// PingManagerConfig is a structure containing various parameters that govern // PingManagerConfig is a structure containing various parameters that govern
// how the PingManager behaves. // how the PingManager behaves.
type PingManagerConfig struct { type PingManagerConfig struct {
// NewPingPayload is a closure that returns the payload to be packaged // NewPingPayload is a closure that returns the payload to be packaged
// in the Ping message. // in the Ping message.
NewPingPayload func() []byte NewPingPayload func() []byte
@ -99,16 +98,20 @@ func NewPingManager(cfg *PingManagerConfig) *PingManager {
func (m *PingManager) Start() error { func (m *PingManager) Start() error {
var err error var err error
m.started.Do(func() { m.started.Do(func() {
err = m.start() m.pingTicker = time.NewTicker(m.cfg.IntervalDuration)
m.pingTimeout = time.NewTimer(0)
m.wg.Add(1)
go m.pingHandler()
}) })
return err return err
} }
func (m *PingManager) start() error { // pingHandler is the main goroutine responsible for enforcing the ping/pong
m.pingTicker = time.NewTicker(m.cfg.IntervalDuration) // protocol.
func (m *PingManager) pingHandler() {
m.pingTimeout = time.NewTimer(0) defer m.wg.Done()
defer m.pingTimeout.Stop() defer m.pingTimeout.Stop()
// Ensure that the pingTimeout channel is empty. // Ensure that the pingTimeout channel is empty.
@ -116,17 +119,13 @@ func (m *PingManager) start() error {
<-m.pingTimeout.C <-m.pingTimeout.C
} }
m.wg.Add(1)
go func() {
defer m.wg.Done()
for { for {
select { select {
case <-m.pingTicker.C: case <-m.pingTicker.C:
// If this occurs it means that the new ping // If this occurs it means that the new ping cycle has
// cycle has begun while there is still an // begun while there is still an outstanding ping
// outstanding ping awaiting a pong response. // awaiting a pong response. This should never occur,
// This should never occur, but if it does, it // but if it does, it implies a timeout.
// implies a timeout.
if m.outstandingPongSize >= 0 { if m.outstandingPongSize >= 0 {
e := errors.New("impossible: new ping" + e := errors.New("impossible: new ping" +
"in unclean state", "in unclean state",
@ -157,6 +156,7 @@ func (m *PingManager) start() error {
e := errors.New("timeout while waiting for " + e := errors.New("timeout while waiting for " +
"pong response", "pong response",
) )
m.cfg.OnPongFailure(e) m.cfg.OnPongFailure(e)
return return
@ -164,19 +164,20 @@ func (m *PingManager) start() error {
case pong := <-m.pongChan: case pong := <-m.pongChan:
pongSize := int32(len(pong.PongBytes)) pongSize := int32(len(pong.PongBytes))
// Save off values we are about to override // Save off values we are about to override when we
// when we call resetPingState. // call resetPingState.
expected := m.outstandingPongSize expected := m.outstandingPongSize
lastPing := m.pingLastSend lastPing := m.pingLastSend
m.resetPingState() m.resetPingState()
// If the pong we receive doesn't match the // If the pong we receive doesn't match the ping we
// ping we sent out, then we fail out. // sent out, then we fail out.
if pongSize != expected { if pongSize != expected {
e := errors.New("pong response does " + e := errors.New("pong response does " +
"not match expected size", "not match expected size",
) )
m.cfg.OnPongFailure(e) m.cfg.OnPongFailure(e)
return return
@ -188,13 +189,11 @@ func (m *PingManager) start() error {
rtt := time.Since(*lastPing) rtt := time.Since(*lastPing)
m.pingTime.Store(&rtt) m.pingTime.Store(&rtt)
} }
case <-m.quit: case <-m.quit:
return return
} }
} }
}()
return nil
} }
// Stop interrupts the goroutines that the PingManager owns. Can only be called // Stop interrupts the goroutines that the PingManager owns. Can only be called