@@ -85,6 +85,40 @@ func waitBroadcasters(t *testing.T, wg *sync.WaitGroup) {
8585 }
8686}
8787
88+ // TestPeerCloseIsIdempotent verifies that close can be called repeatedly and
89+ // from concurrent goroutines without panicking on a double close of term. The
90+ // peer set owns term via Unregister, but making close idempotent removes the
91+ // risk of a "close of closed channel" panic from future callers.
92+ func TestPeerCloseIsIdempotent (t * testing.T ) {
93+ app , net := p2p .MsgPipe ()
94+ defer app .Close ()
95+ var id enode.ID
96+ if _ , err := rand .Read (id [:]); err != nil {
97+ t .Fatalf ("failed to generate random peer id: %v" , err )
98+ }
99+ p := newPeer (xdc100 , p2p .NewPeer (id , "close" , nil ), net , func (common.Hash ) * types.Transaction { return nil })
100+
101+ var wg sync.WaitGroup
102+ for i := 0 ; i < 8 ; i ++ {
103+ wg .Add (1 )
104+ go func () {
105+ defer wg .Done ()
106+ p .close ()
107+ }()
108+ }
109+ p .close ()
110+ wg .Wait ()
111+
112+ // All concurrent calls returned without panic, so idempotency holds. As a
113+ // final sanity check, the first call must have closed term; the default
114+ // case fails fast if close ever stops closing it.
115+ select {
116+ case <- p .term :
117+ default :
118+ t .Fatal ("close did not terminate the peer" )
119+ }
120+ }
121+
88122// countingMsgWriter wraps a p2p.MsgReadWriter and counts every write attempt,
89123// allowing tests to assert that a peer stops sending after a network error.
90124type countingMsgWriter struct {
@@ -118,8 +152,7 @@ func newBroadcastTestPeer(t *testing.T, name string) (*peer, *types.Transaction,
118152 }
119153 counter := & countingMsgWriter {MsgReadWriter : net }
120154 p := newPeer (xdc100 , p2p .NewPeer (id , name , nil ), counter , func (common.Hash ) * types.Transaction { return tx })
121- var closeOnce sync.Once
122- terminate := func () { closeOnce .Do (p .close ) }
155+ terminate := p .close
123156 t .Cleanup (terminate )
124157 app .Close ()
125158 return p , tx , counter , terminate
0 commit comments