Skip to content

Commit 51de320

Browse files
authored
Merge pull request #8385 from Roasbeef/ping-async-dc
peer: make PingManager disconnect call async
2 parents 41c167d + 359f271 commit 51de320

File tree

3 files changed

+85
-78
lines changed

3 files changed

+85
-78
lines changed

docs/release-notes/release-notes-0.17.4.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
FilterKnownChanIDs](https://github.com/lightningnetwork/lnd/pull/8400) by
3030
ensuring the `cacheMu` mutex is acquired before the main database lock.
3131

32+
* [Prevent](https://github.com/lightningnetwork/lnd/pull/8385) ping failures
33+
from [deadlocking](https://github.com/lightningnetwork/lnd/issues/8379)
34+
the peer connection.
35+
36+
3237
# New Features
3338
## Functional Enhancements
3439
## RPC Additions
@@ -50,5 +55,8 @@
5055
## Tooling and Documentation
5156

5257
# Contributors (Alphabetical Order)
58+
5359
* Elle Mouton
60+
* Keagan McClelland
61+
* Olaoluwa Osuntokun
5462
* ziggie1984

peer/brontide.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ func NewBrontide(cfg Config) *Brontide {
577577
eStr := "pong response failure for %s: %v " +
578578
"-- disconnecting"
579579
p.log.Warnf(eStr, p, err)
580-
p.Disconnect(fmt.Errorf(eStr, p, err))
580+
go p.Disconnect(fmt.Errorf(eStr, p, err))
581581
},
582582
})
583583

peer/ping_manager.go

Lines changed: 76 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
// PingManagerConfig is a structure containing various parameters that govern
1414
// how the PingManager behaves.
1515
type PingManagerConfig struct {
16-
1716
// NewPingPayload is a closure that returns the payload to be packaged
1817
// in the Ping message.
1918
NewPingPayload func() []byte
@@ -99,102 +98,102 @@ func NewPingManager(cfg *PingManagerConfig) *PingManager {
9998
func (m *PingManager) Start() error {
10099
var err error
101100
m.started.Do(func() {
102-
err = m.start()
101+
m.pingTicker = time.NewTicker(m.cfg.IntervalDuration)
102+
m.pingTimeout = time.NewTimer(0)
103+
104+
m.wg.Add(1)
105+
go m.pingHandler()
103106
})
104107

105108
return err
106109
}
107110

108-
func (m *PingManager) start() error {
109-
m.pingTicker = time.NewTicker(m.cfg.IntervalDuration)
110-
111-
m.pingTimeout = time.NewTimer(0)
111+
// pingHandler is the main goroutine responsible for enforcing the ping/pong
112+
// protocol.
113+
func (m *PingManager) pingHandler() {
114+
defer m.wg.Done()
112115
defer m.pingTimeout.Stop()
113116

114117
// Ensure that the pingTimeout channel is empty.
115118
if !m.pingTimeout.Stop() {
116119
<-m.pingTimeout.C
117120
}
118121

119-
m.wg.Add(1)
120-
go func() {
121-
defer m.wg.Done()
122-
for {
123-
select {
124-
case <-m.pingTicker.C:
125-
// If this occurs it means that the new ping
126-
// cycle has begun while there is still an
127-
// outstanding ping awaiting a pong response.
128-
// This should never occur, but if it does, it
129-
// implies a timeout.
130-
if m.outstandingPongSize >= 0 {
131-
e := errors.New("impossible: new ping" +
132-
"in unclean state",
133-
)
134-
m.cfg.OnPongFailure(e)
135-
136-
return
137-
}
138-
139-
pongSize := m.cfg.NewPongSize()
140-
ping := &lnwire.Ping{
141-
NumPongBytes: pongSize,
142-
PaddingBytes: m.cfg.NewPingPayload(),
143-
}
144-
145-
// Set up our bookkeeping for the new Ping.
146-
if err := m.setPingState(pongSize); err != nil {
147-
m.cfg.OnPongFailure(err)
148-
149-
return
150-
}
151-
152-
m.cfg.SendPing(ping)
153-
154-
case <-m.pingTimeout.C:
155-
m.resetPingState()
156-
157-
e := errors.New("timeout while waiting for " +
158-
"pong response",
122+
for {
123+
select {
124+
case <-m.pingTicker.C:
125+
// If this occurs it means that the new ping cycle has
126+
// begun while there is still an outstanding ping
127+
// awaiting a pong response. This should never occur,
128+
// but if it does, it implies a timeout.
129+
if m.outstandingPongSize >= 0 {
130+
e := errors.New("impossible: new ping" +
131+
"in unclean state",
159132
)
160133
m.cfg.OnPongFailure(e)
161134

162135
return
136+
}
137+
138+
pongSize := m.cfg.NewPongSize()
139+
ping := &lnwire.Ping{
140+
NumPongBytes: pongSize,
141+
PaddingBytes: m.cfg.NewPingPayload(),
142+
}
143+
144+
// Set up our bookkeeping for the new Ping.
145+
if err := m.setPingState(pongSize); err != nil {
146+
m.cfg.OnPongFailure(err)
163147

164-
case pong := <-m.pongChan:
165-
pongSize := int32(len(pong.PongBytes))
166-
167-
// Save off values we are about to override
168-
// when we call resetPingState.
169-
expected := m.outstandingPongSize
170-
lastPing := m.pingLastSend
171-
172-
m.resetPingState()
173-
174-
// If the pong we receive doesn't match the
175-
// ping we sent out, then we fail out.
176-
if pongSize != expected {
177-
e := errors.New("pong response does " +
178-
"not match expected size",
179-
)
180-
m.cfg.OnPongFailure(e)
181-
182-
return
183-
}
184-
185-
// Compute RTT of ping and save that for future
186-
// querying.
187-
if lastPing != nil {
188-
rtt := time.Since(*lastPing)
189-
m.pingTime.Store(&rtt)
190-
}
191-
case <-m.quit:
192148
return
193149
}
194-
}
195-
}()
196150

197-
return nil
151+
m.cfg.SendPing(ping)
152+
153+
case <-m.pingTimeout.C:
154+
m.resetPingState()
155+
156+
e := errors.New("timeout while waiting for " +
157+
"pong response",
158+
)
159+
160+
m.cfg.OnPongFailure(e)
161+
162+
return
163+
164+
case pong := <-m.pongChan:
165+
pongSize := int32(len(pong.PongBytes))
166+
167+
// Save off values we are about to override when we
168+
// call resetPingState.
169+
expected := m.outstandingPongSize
170+
lastPing := m.pingLastSend
171+
172+
m.resetPingState()
173+
174+
// If the pong we receive doesn't match the ping we
175+
// sent out, then we fail out.
176+
if pongSize != expected {
177+
e := errors.New("pong response does " +
178+
"not match expected size",
179+
)
180+
181+
m.cfg.OnPongFailure(e)
182+
183+
return
184+
}
185+
186+
// Compute RTT of ping and save that for future
187+
// querying.
188+
if lastPing != nil {
189+
rtt := time.Since(*lastPing)
190+
m.pingTime.Store(&rtt)
191+
}
192+
193+
case <-m.quit:
194+
return
195+
}
196+
}
198197
}
199198

200199
// Stop interrupts the goroutines that the PingManager owns. Can only be called

0 commit comments

Comments
 (0)