@@ -3,6 +3,7 @@ package wire
33import (
44 "context"
55 "crypto/tls"
6+ "errors"
67 "fmt"
78 "io"
89 "net"
@@ -106,7 +107,7 @@ func (l *HTTP2Listener) ServeHTTP(w http.ResponseWriter, r *http.Request) {
106107 return
107108 }
108109
109- conn := newHTTP2Conn (l .Addr (), remoteAddr , r .Body , w , rc , l .codec )
110+ conn := newHTTP2Conn (r . Context (), l .Addr (), remoteAddr , r .Body , w , rc , l .codec )
110111 defer conn .Close ()
111112
112113 // Wait until connection is accepted by HTTP2Listener.Accept(ctx)
@@ -164,6 +165,8 @@ func (l *HTTP2Listener) Addr() net.Addr {
164165
165166// http2Conn implements Conn for HTTP/2-based connections.
166167type http2Conn struct {
168+ ctx context.Context
169+
167170 localAddr net.Addr
168171 remoteAddr net.Addr
169172
@@ -187,8 +190,9 @@ type incomingFrame struct {
187190
188191var _ Conn = (* http2Conn )(nil )
189192
190- // newHTTP2Conn creates a new HTTP/2 connection.
193+ // newHTTP2Conn creates a new HTTP/2 connection tied to the lifetime of ctx .
191194func newHTTP2Conn (
195+ ctx context.Context ,
192196 localAddr net.Addr ,
193197 remoteAddr net.Addr ,
194198 reader io.ReadCloser ,
@@ -197,6 +201,7 @@ func newHTTP2Conn(
197201 codec * protobufCodec ,
198202) * http2Conn {
199203 c := & http2Conn {
204+ ctx : ctx ,
200205 localAddr : localAddr ,
201206 remoteAddr : remoteAddr ,
202207 codec : codec ,
@@ -225,25 +230,32 @@ func (c *http2Conn) readLoop(ctx context.Context) {
225230
226231// Send sends a frame over the connection.
227232func (c * http2Conn ) Send (ctx context.Context , frame Frame ) error {
233+ c .writeMu .Lock ()
234+ defer c .writeMu .Unlock ()
235+
228236 select {
237+ case <- c .ctx .Done ():
238+ return ErrConnClosed
229239 case <- ctx .Done ():
230240 return ctx .Err ()
231241 case <- c .closed :
232242 return ErrConnClosed
233243 default :
234244 }
235245
236- c . writeMu . Lock ( )
237- defer c . writeMu . Unlock ()
238-
239- if err := c . codec . EncodeTo ( c . writer , frame ); err != nil {
246+ err := c . codec . EncodeTo ( c . writer , frame )
247+ if err != nil && isStreamClosedError ( err ) {
248+ return ErrConnClosed
249+ } else if err != nil {
240250 return fmt .Errorf ("write frame: %w" , err )
241251 }
242252
243253 // Flush after each frame to ensure immediate delivery
244254 if c .responseController != nil {
245255 err := c .responseController .Flush ()
246- if err != nil {
256+ if err != nil && isStreamClosedError (err ) {
257+ return ErrConnClosed
258+ } else if err != nil {
247259 return fmt .Errorf ("flush response: %w" , err )
248260 }
249261 }
@@ -254,6 +266,8 @@ func (c *http2Conn) Send(ctx context.Context, frame Frame) error {
254266// Recv receives a frame from the connection.
255267func (c * http2Conn ) Recv (ctx context.Context ) (Frame , error ) {
256268 select {
269+ case <- c .ctx .Done ():
270+ return nil , ErrConnClosed
257271 case <- ctx .Done ():
258272 return nil , ctx .Err ()
259273 case <- c .closed :
@@ -265,6 +279,9 @@ func (c *http2Conn) Recv(ctx context.Context) (Frame, error) {
265279
266280// Close closes the connection.
267281func (c * http2Conn ) Close () error {
282+ c .writeMu .Lock ()
283+ defer c .writeMu .Unlock ()
284+
268285 var err error
269286 c .closeOnce .Do (func () {
270287 close (c .closed )
@@ -341,6 +358,7 @@ func (d *HTTP2Dialer) Dial(ctx context.Context, from, to net.Addr) (Conn, error)
341358
342359 // Create connection
343360 conn := newHTTP2Conn (
361+ req .Context (),
344362 from ,
345363 to ,
346364 resp .Body ,
@@ -364,3 +382,19 @@ func (d *HTTP2Dialer) Dial(ctx context.Context, from, to net.Addr) (Conn, error)
364382
365383 return conn , nil
366384}
385+
386+ // isStreamClosedError returns true if err represents an `http2: stream closed`
387+ // error.
388+ func isStreamClosedError (err error ) bool {
389+ // NOTE(rfratto): At the time of writing, the http2 package doesn't have a
390+ // guaranteed way to check if an error is a stream closed error, so we look
391+ // at the error message instead.
392+ for {
393+ next := errors .Unwrap (err )
394+ if next == nil {
395+ break
396+ }
397+ err = next
398+ }
399+ return err .Error () == "http2: stream closed"
400+ }
0 commit comments