From 2024514380a812b982df9e33ea3dd7af78e6dcd5 Mon Sep 17 00:00:00 2001 From: kalo <24719519+KaloyanTanev@users.noreply.github.com> Date: Mon, 6 Jul 2026 17:27:32 +0300 Subject: [PATCH 1/2] Do not error on close write, if stream was already canceled --- p2p/sender.go | 21 ++++++++++++++++++--- p2p/sender_internal_test.go | 7 +++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/p2p/sender.go b/p2p/sender.go index a36a69d15e..713aa86298 100644 --- a/p2p/sender.go +++ b/p2p/sender.go @@ -344,7 +344,15 @@ func SendReceive(ctx context.Context, p2pNode host.Host, peerID peer.ID, } if err := s.CloseWrite(); err != nil { - return errors.Wrap(err, "close write", z.Any("protocol", s.Protocol())) + // A canceled-stream error here is benign: the request was already written and + // delivered above, and the peer resetting our send-direction (STOP_SENDING) does + // not affect the receive-direction, so the response is still readable below. + // This happens much more frequently when QUIC is enabled. + if isCanceledStreamErr(err) { + log.Debug(ctx, "Closing write of canceled stream", z.Err(err), z.Any("protocol", s.Protocol())) + } else { + return errors.Wrap(err, "close write", z.Any("protocol", s.Protocol())) + } } if err = reader.ReadMsg(resp); err != nil { @@ -355,7 +363,7 @@ func SendReceive(ctx context.Context, p2pNode host.Host, peerID peer.ID, // This close error doesn't require a retry or warning mechanism since the message was // correctly sent to the destination. However, it is still unknown what/who is causing the // stream to be canceled. This error appears much more frequently when QUIC is enabled. - if strings.Contains(err.Error(), "close called for canceled stream") { + if isCanceledStreamErr(err) { log.Debug(ctx, "Closing canceled stream", z.Err(err), z.Any("protocol", s.Protocol())) return nil } @@ -412,7 +420,7 @@ func Send(ctx context.Context, p2pNode host.Host, protoID protocol.ID, peerID pe // This close error doesn't require a retry or warning mechanism since the message was // correctly sent to the destination. However, it is still unknown what/who is causing the // stream to be canceled. This error appears much more frequently when QUIC is enabled. - if strings.Contains(err.Error(), "close called for canceled stream") { + if isCanceledStreamErr(err) { log.Debug(ctx, "Closing canceled stream", z.Err(err), z.Any("protocol", s.Protocol())) return nil } @@ -423,6 +431,13 @@ func Send(ctx context.Context, p2pNode host.Host, protoID protocol.ID, peerID pe return nil } +// isCanceledStreamErr returns true if the error is the benign QUIC stream error +// returned when closing a stream whose send-direction the peer has already reset +// (via STOP_SENDING). It occurs much more frequently when QUIC is enabled. +func isCanceledStreamErr(err error) bool { + return err != nil && strings.Contains(err.Error(), "close called for canceled stream") +} + // protocolPrefix returns the common prefix of the provided protocol IDs. func protocolPrefix(pIDs ...protocol.ID) protocol.ID { if len(pIDs) == 0 { diff --git a/p2p/sender_internal_test.go b/p2p/sender_internal_test.go index 88911c5f5d..bf60037b4d 100644 --- a/p2p/sender_internal_test.go +++ b/p2p/sender_internal_test.go @@ -160,3 +160,10 @@ func TestNonZeroResponse(t *testing.T) { err := SendReceive(ctx, nil, "", nil, &pbv1.Duty{Slot: 1}, "") require.ErrorContains(t, err, "bug: response proto must be zero value") } + +func TestIsCanceledStreamErr(t *testing.T) { + require.False(t, isCanceledStreamErr(nil)) + require.False(t, isCanceledStreamErr(errors.New("some other error"))) + require.True(t, isCanceledStreamErr(errors.New("close called for canceled stream 4"))) + require.True(t, isCanceledStreamErr(errors.Wrap(errors.New("close called for canceled stream 4"), "close write"))) +} From b486119b5c021a417b77a21c3c636154c34c8d49 Mon Sep 17 00:00:00 2001 From: kalo <24719519+KaloyanTanev@users.noreply.github.com> Date: Mon, 6 Jul 2026 18:43:48 +0300 Subject: [PATCH 2/2] Nolint for revive --- p2p/sender.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/p2p/sender.go b/p2p/sender.go index 713aa86298..3c88456e98 100644 --- a/p2p/sender.go +++ b/p2p/sender.go @@ -348,6 +348,8 @@ func SendReceive(ctx context.Context, p2pNode host.Host, peerID peer.ID, // delivered above, and the peer resetting our send-direction (STOP_SENDING) does // not affect the receive-direction, so the response is still readable below. // This happens much more frequently when QUIC is enabled. + // + //nolint:revive // Prioritise readability. if isCanceledStreamErr(err) { log.Debug(ctx, "Closing write of canceled stream", z.Err(err), z.Any("protocol", s.Protocol())) } else {