Skip to content

Commit 7c1c8e2

Browse files
authored
[R4R] add timeout for stopping p2p server (#643)
* add timeout for stopping p2p server * extend extension wait time * add unit tests * fix lint issue
1 parent ec8d46e commit 7c1c8e2

File tree

6 files changed

+151
-6
lines changed

6 files changed

+151
-6
lines changed

eth/handler_eth_test.go

+70
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,76 @@ func testForkIDSplit(t *testing.T, protocol uint) {
239239
func TestRecvTransactions65(t *testing.T) { testRecvTransactions(t, eth.ETH65) }
240240
func TestRecvTransactions66(t *testing.T) { testRecvTransactions(t, eth.ETH66) }
241241

242+
func TestWaitDiffExtensionTimout(t *testing.T) {
243+
t.Parallel()
244+
245+
// Create a message handler, configure it to accept transactions and watch them
246+
handler := newTestHandler()
247+
defer handler.close()
248+
249+
// Create a source peer to send messages through and a sink handler to receive them
250+
_, p2pSink := p2p.MsgPipe()
251+
defer p2pSink.Close()
252+
253+
protos := []p2p.Protocol{
254+
{
255+
Name: "diff",
256+
Version: 1,
257+
},
258+
}
259+
260+
sink := eth.NewPeer(eth.ETH67, p2p.NewPeerWithProtocols(enode.ID{2}, protos, "", []p2p.Cap{
261+
{
262+
Name: "diff",
263+
Version: 1,
264+
},
265+
}), p2pSink, nil)
266+
defer sink.Close()
267+
268+
err := handler.handler.runEthPeer(sink, func(peer *eth.Peer) error {
269+
return eth.Handle((*ethHandler)(handler.handler), peer)
270+
})
271+
272+
if err == nil || err.Error() != "peer wait timeout" {
273+
t.Fatalf("error should be `peer wait timeout`")
274+
}
275+
}
276+
277+
func TestWaitSnapExtensionTimout(t *testing.T) {
278+
t.Parallel()
279+
280+
// Create a message handler, configure it to accept transactions and watch them
281+
handler := newTestHandler()
282+
defer handler.close()
283+
284+
// Create a source peer to send messages through and a sink handler to receive them
285+
_, p2pSink := p2p.MsgPipe()
286+
defer p2pSink.Close()
287+
288+
protos := []p2p.Protocol{
289+
{
290+
Name: "snap",
291+
Version: 1,
292+
},
293+
}
294+
295+
sink := eth.NewPeer(eth.ETH67, p2p.NewPeerWithProtocols(enode.ID{2}, protos, "", []p2p.Cap{
296+
{
297+
Name: "snap",
298+
Version: 1,
299+
},
300+
}), p2pSink, nil)
301+
defer sink.Close()
302+
303+
err := handler.handler.runEthPeer(sink, func(peer *eth.Peer) error {
304+
return eth.Handle((*ethHandler)(handler.handler), peer)
305+
})
306+
307+
if err == nil || err.Error() != "peer wait timeout" {
308+
t.Fatalf("error should be `peer wait timeout`")
309+
}
310+
}
311+
242312
func testRecvTransactions(t *testing.T, protocol uint) {
243313
t.Parallel()
244314

eth/peerset.go

+32-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"errors"
2121
"math/big"
2222
"sync"
23+
"time"
2324

2425
"github.com/ethereum/go-ethereum/common"
2526
"github.com/ethereum/go-ethereum/eth/downloader"
@@ -38,19 +39,28 @@ var (
3839
// to the peer set, but one with the same id already exists.
3940
errPeerAlreadyRegistered = errors.New("peer already registered")
4041

42+
// errPeerWaitTimeout is returned if a peer waits extension for too long
43+
errPeerWaitTimeout = errors.New("peer wait timeout")
44+
4145
// errPeerNotRegistered is returned if a peer is attempted to be removed from
4246
// a peer set, but no peer with the given id exists.
4347
errPeerNotRegistered = errors.New("peer not registered")
4448

4549
// errSnapWithoutEth is returned if a peer attempts to connect only on the
46-
// snap protocol without advertizing the eth main protocol.
50+
// snap protocol without advertising the eth main protocol.
4751
errSnapWithoutEth = errors.New("peer connected on snap without compatible eth support")
4852

4953
// errDiffWithoutEth is returned if a peer attempts to connect only on the
50-
// diff protocol without advertizing the eth main protocol.
54+
// diff protocol without advertising the eth main protocol.
5155
errDiffWithoutEth = errors.New("peer connected on diff without compatible eth support")
5256
)
5357

58+
const (
59+
// extensionWaitTimeout is the maximum allowed time for the extension wait to
60+
// complete before dropping the connection as malicious.
61+
extensionWaitTimeout = 10 * time.Second
62+
)
63+
5464
// peerSet represents the collection of active peers currently participating in
5565
// the `eth` protocol, with or without the `snap` extension.
5666
type peerSet struct {
@@ -169,7 +179,16 @@ func (ps *peerSet) waitSnapExtension(peer *eth.Peer) (*snap.Peer, error) {
169179
ps.snapWait[id] = wait
170180
ps.lock.Unlock()
171181

172-
return <-wait, nil
182+
select {
183+
case peer := <-wait:
184+
return peer, nil
185+
186+
case <-time.After(extensionWaitTimeout):
187+
ps.lock.Lock()
188+
delete(ps.snapWait, id)
189+
ps.lock.Unlock()
190+
return nil, errPeerWaitTimeout
191+
}
173192
}
174193

175194
// waitDiffExtension blocks until all satellite protocols are connected and tracked
@@ -203,7 +222,16 @@ func (ps *peerSet) waitDiffExtension(peer *eth.Peer) (*diff.Peer, error) {
203222
ps.diffWait[id] = wait
204223
ps.lock.Unlock()
205224

206-
return <-wait, nil
225+
select {
226+
case peer := <-wait:
227+
return peer, nil
228+
229+
case <-time.After(extensionWaitTimeout):
230+
ps.lock.Lock()
231+
delete(ps.diffWait, id)
232+
ps.lock.Unlock()
233+
return nil, errPeerWaitTimeout
234+
}
207235
}
208236

209237
func (ps *peerSet) GetDiffPeer(pid string) downloader.IDiffPeer {

eth/protocols/diff/handshake.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626

2727
const (
2828
// handshakeTimeout is the maximum allowed time for the `diff` handshake to
29-
// complete before dropping the connection.= as malicious.
29+
// complete before dropping the connection as malicious.
3030
handshakeTimeout = 5 * time.Second
3131
)
3232

p2p/peer.go

+10
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ func NewPeer(id enode.ID, name string, caps []Cap) *Peer {
129129
return peer
130130
}
131131

132+
// NewPeerWithProtocols returns a peer for testing purposes.
133+
func NewPeerWithProtocols(id enode.ID, protocols []Protocol, name string, caps []Cap) *Peer {
134+
pipe, _ := net.Pipe()
135+
node := enode.SignNull(new(enr.Record), id)
136+
conn := &conn{fd: pipe, transport: nil, node: node, caps: caps, name: name}
137+
peer := newPeer(log.Root(), conn, protocols)
138+
close(peer.closed) // ensures Disconnect doesn't block
139+
return peer
140+
}
141+
132142
// ID returns the node's public key.
133143
func (p *Peer) ID() enode.ID {
134144
return p.rw.node.ID()

p2p/server.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ const (
6363

6464
// Maximum amount of time allowed for writing a complete message.
6565
frameWriteTimeout = 20 * time.Second
66+
67+
// Maximum time to wait before stop the p2p server
68+
stopTimeout = 5 * time.Second
6669
)
6770

6871
var errServerStopped = errors.New("server stopped")
@@ -403,7 +406,18 @@ func (srv *Server) Stop() {
403406
}
404407
close(srv.quit)
405408
srv.lock.Unlock()
406-
srv.loopWG.Wait()
409+
410+
stopChan := make(chan struct{})
411+
go func() {
412+
srv.loopWG.Wait()
413+
close(stopChan)
414+
}()
415+
416+
select {
417+
case <-stopChan:
418+
case <-time.After(stopTimeout):
419+
srv.log.Warn("stop p2p server timeout, forcing stop")
420+
}
407421
}
408422

409423
// sharedUDPConn implements a shared connection. Write sends messages to the underlying connection while read returns

p2p/server_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,29 @@ func TestServerDial(t *testing.T) {
203203
}
204204
}
205205

206+
func TestServerStopTimeout(t *testing.T) {
207+
srv := &Server{Config: Config{
208+
PrivateKey: newkey(),
209+
MaxPeers: 1,
210+
NoDiscovery: true,
211+
Logger: testlog.Logger(t, log.LvlTrace).New("server", "1"),
212+
}}
213+
srv.Start()
214+
srv.loopWG.Add(1)
215+
216+
stopChan := make(chan struct{})
217+
go func() {
218+
srv.Stop()
219+
close(stopChan)
220+
}()
221+
222+
select {
223+
case <-stopChan:
224+
case <-time.After(10 * time.Second):
225+
t.Error("server should be shutdown in 10 seconds")
226+
}
227+
}
228+
206229
// This test checks that RemovePeer disconnects the peer if it is connected.
207230
func TestServerRemovePeerDisconnect(t *testing.T) {
208231
srv1 := &Server{Config: Config{

0 commit comments

Comments
 (0)