quic: don't block when closing read-only streams

Stream.Close blocks until all data sent on a stream has been
acked by the peer. Don't block indefinitely when closing
a read-only stream, waiting for an ack of data we never sent.

For golang/go#58547

Change-Id: I4087666f739d7388e460b613d211c043626f1c87
Reviewed-on: https://go-review.googlesource.com/c/net/+/524038
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/internal/quic/stream.go b/internal/quic/stream.go
index 12117db..1033cbb 100644
--- a/internal/quic/stream.go
+++ b/internal/quic/stream.go
@@ -66,7 +66,9 @@
 		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 @@
 // 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 7b8ba2c..79377c6 100644
--- a/internal/quic/stream_test.go
+++ b/internal/quic/stream_test.go
@@ -972,6 +972,17 @@
 	}
 }
 
+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