diff --git a/internal/quic/stream.go b/internal/quic/stream.go index 12117dbd3..1033cbb40 100644 --- a/internal/quic/stream.go +++ b/internal/quic/stream.go @@ -66,7 +66,9 @@ func newStream(c *Conn, id streamID) *Stream { inresetcode: -1, // -1 indicates no RESET_STREAM received ingate: newLockedGate(), outgate: newLockedGate(), - outdone: make(chan struct{}), + } + if !s.IsReadOnly() { + s.outdone = make(chan struct{}) } return s } @@ -237,6 +239,9 @@ func (s *Stream) Close() error { // CloseContext discards the buffer and returns the context error. func (s *Stream) CloseContext(ctx context.Context) error { s.CloseRead() + if s.IsReadOnly() { + return nil + } s.CloseWrite() // TODO: Return code from peer's RESET_STREAM frame? return s.conn.waitOnDone(ctx, s.outdone) diff --git a/internal/quic/stream_test.go b/internal/quic/stream_test.go index 7b8ba2c54..79377c6a4 100644 --- a/internal/quic/stream_test.go +++ b/internal/quic/stream_test.go @@ -972,6 +972,17 @@ func TestStreamCloseWaitsForAcks(t *testing.T) { } } +func TestStreamCloseReadOnly(t *testing.T) { + tc, s := newTestConnAndRemoteStream(t, serverSide, uniStream, permissiveTransportParameters) + if err := s.CloseContext(canceledContext()); err != nil { + t.Errorf("s.CloseContext() = %v, want nil", err) + } + tc.wantFrame("closed stream sends STOP_SENDING", + packetType1RTT, debugFrameStopSending{ + id: s.id, + }) +} + func TestStreamCloseUnblocked(t *testing.T) { for _, test := range []struct { name string