From 124f0d249159d59093f1ed63e00a96b402040077 Mon Sep 17 00:00:00 2001 From: Sjors Gielen Date: Sun, 15 Dec 2024 01:11:04 +0100 Subject: [PATCH 1/2] net/http: add context cancellation reason for server handlers When we cancel a HTTP server handler context, set an appropriate cancel cause. This makes investigation of context cancellation errors easier. Fixes #64465 --- src/net/http/server.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/net/http/server.go b/src/net/http/server.go index 1e8e1437d2683..5a5aecbbd980a 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -59,6 +59,17 @@ var ( // anything in the net/http package. Callers should not // compare errors against this variable. ErrWriteAfterFlush = errors.New("unused") + + // TODO: expose once proposal is accepted + // errConnectionClosed is used as a context Cause for contexts + // cancelled because the client closed their connection while + // a request was being handled. + errConnectionClosed = errors.New("connection closed") + + // TODO: expose once proposal is accepted + // errConnectionHandled is used as a context Cause for contexts + // cancelled because the request handler returned. + errConnectionHandled = errors.New("connection handled") ) // A Handler responds to an HTTP request. @@ -257,7 +268,7 @@ type conn struct { server *Server // cancelCtx cancels the connection-level context. - cancelCtx context.CancelFunc + cancelCtx context.CancelCauseFunc // rwc is the underlying network connection. // This is never wrapped by other types and is the value given out @@ -754,8 +765,11 @@ func (cr *connReader) hitReadLimit() bool { return cr.remain <= 0 } // down its context. // // It may be called from multiple goroutines. -func (cr *connReader) handleReadError(_ error) { - cr.conn.cancelCtx() +func (cr *connReader) handleReadError(err error) { + if errors.Is(err, io.EOF) { + err = errConnectionClosed + } + cr.conn.cancelCtx(err) cr.closeNotify() } @@ -2005,9 +2019,9 @@ func (c *conn) serve(ctx context.Context) { // HTTP/1.x from here on. - ctx, cancelCtx := context.WithCancel(ctx) + ctx, cancelCtx := context.WithCancelCause(ctx) c.cancelCtx = cancelCtx - defer cancelCtx() + defer cancelCtx(errConnectionHandled) c.r = &connReader{conn: c} c.bufr = newBufioReader(c.r) @@ -4021,7 +4035,7 @@ func (w checkConnErrorWriter) Write(p []byte) (n int, err error) { n, err = w.c.rwc.Write(p) if err != nil && w.c.werr == nil { w.c.werr = err - w.c.cancelCtx() + w.c.cancelCtx(err) } return } From 29c5a17f228e76c3a1f8affb429823319c4da687 Mon Sep 17 00:00:00 2001 From: Sjors Gielen Date: Sun, 15 Dec 2024 14:43:39 +0100 Subject: [PATCH 2/2] Update error names and descriptions based on feedback & insights. --- src/net/http/server.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/net/http/server.go b/src/net/http/server.go index 5a5aecbbd980a..aa1c7e949797e 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -64,12 +64,12 @@ var ( // errConnectionClosed is used as a context Cause for contexts // cancelled because the client closed their connection while // a request was being handled. - errConnectionClosed = errors.New("connection closed") + errConnectionClosed = errors.New("connection closed by client") // TODO: expose once proposal is accepted - // errConnectionHandled is used as a context Cause for contexts - // cancelled because the request handler returned. - errConnectionHandled = errors.New("connection handled") + // errRequestHandlerReturned is used as a context Cause for contexts + // cancelled because the request handler already returned. + errRequestHandlerReturned = errors.New("request handler returned") ) // A Handler responds to an HTTP request. @@ -2021,7 +2021,7 @@ func (c *conn) serve(ctx context.Context) { ctx, cancelCtx := context.WithCancelCause(ctx) c.cancelCtx = cancelCtx - defer cancelCtx(errConnectionHandled) + defer cancelCtx(errRequestHandlerReturned) c.r = &connReader{conn: c} c.bufr = newBufioReader(c.r)