aboutsummaryrefslogtreecommitdiff
path: root/src/net/http
diff options
context:
space:
mode:
authorJakob Ackermann <das7pad@outlook.com>2025-01-07 22:33:05 +0000
committerGopher Robot <gobot@golang.org>2025-03-03 07:45:07 -0800
commitbda9f85e5cb57d64e9ff8ef39f0d5222c5ceeea0 (patch)
tree79acf33ba4347dd4ece84bdab9d2152f099d188c /src/net/http
parentb199d9766a8957c686ece568483586f08fc9a8b4 (diff)
downloadgo-bda9f85e5cb57d64e9ff8ef39f0d5222c5ceeea0.tar.xz
net/http: allocate CloseNotifier channel lazily
The CloseNotifier interface is deprecated. We can defer allocating the backing channel until the first use of CloseNotifier. goos: linux goarch: amd64 pkg: net/http cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz                    │   before    │               after                │                    │   sec/op    │   sec/op     vs base               │ Server-8             160.8µ ± 2%   160.1µ ± 1%       ~ (p=0.353 n=10) CloseNotifier/h1-8   222.1µ ± 4%   226.4µ ± 7%       ~ (p=0.143 n=10) geomean              189.0µ        190.4µ       +0.75%                    │    before    │                after                │                    │     B/op     │     B/op      vs base               │ Server-8             2.292Ki ± 0%   2.199Ki ± 0%  -4.07% (p=0.000 n=10) CloseNotifier/h1-8   3.224Ki ± 0%   3.241Ki ± 0%  +0.51% (p=0.000 n=10) geomean              2.718Ki        2.669Ki       -1.80%                    │   before   │                after                │                    │ allocs/op  │ allocs/op   vs base                 │ Server-8             21.00 ± 0%   20.00 ± 0%  -4.76% (p=0.000 n=10) CloseNotifier/h1-8   50.00 ± 0%   50.00 ± 0%       ~ (p=1.000 n=10) ¹ geomean              32.40        31.62       -2.41% ¹ all samples are equal Change-Id: I3f35d56b8356fb660589b7708a023e4480f32067 GitHub-Last-Rev: c75696b9b8498ae03a4ad9527b9b7c8337415456 GitHub-Pull-Request: golang/go#71163 Reviewed-on: https://go-review.googlesource.com/c/go/+/640598 Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Michael Knyszek <mknyszek@google.com> Auto-Submit: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Diffstat (limited to 'src/net/http')
-rw-r--r--src/net/http/server.go37
1 files changed, 28 insertions, 9 deletions
diff --git a/src/net/http/server.go b/src/net/http/server.go
index 439efa0c75..b452f643bd 100644
--- a/src/net/http/server.go
+++ b/src/net/http/server.go
@@ -261,7 +261,7 @@ type conn struct {
// rwc is the underlying network connection.
// This is never wrapped by other types and is the value given out
- // to CloseNotifier callers. It is usually of type *net.TCPConn or
+ // to [Hijacker] callers. It is usually of type *net.TCPConn or
// *tls.Conn.
rwc net.Conn
@@ -486,11 +486,12 @@ type response struct {
clenBuf [10]byte
statusBuf [3]byte
+ // lazyCloseNotifyMu protects closeNotifyCh and closeNotifyTriggered.
+ lazyCloseNotifyMu sync.Mutex
// closeNotifyCh is the channel returned by CloseNotify.
- // TODO(bradfitz): this is currently (for Go 1.8) always
- // non-nil. Make this lazily-created again as it used to be?
- closeNotifyCh chan bool
- didCloseNotify atomic.Bool // atomic (only false->true winner should send)
+ closeNotifyCh chan bool
+ // closeNotifyTriggered tracks prior closeNotify calls.
+ closeNotifyTriggered bool
}
func (c *response) SetReadDeadline(deadline time.Time) error {
@@ -761,9 +762,8 @@ func (cr *connReader) handleReadError(_ error) {
// may be called from multiple goroutines.
func (cr *connReader) closeNotify() {
- res := cr.conn.curReq.Load()
- if res != nil && !res.didCloseNotify.Swap(true) {
- res.closeNotifyCh <- true
+ if res := cr.conn.curReq.Load(); res != nil {
+ res.closeNotify()
}
}
@@ -1078,7 +1078,6 @@ func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
reqBody: req.Body,
handlerHeader: make(Header),
contentLength: -1,
- closeNotifyCh: make(chan bool, 1),
// We populate these ahead of time so we're not
// reading from req.Header after their Handler starts
@@ -2228,12 +2227,32 @@ func (w *response) Hijack() (rwc net.Conn, buf *bufio.ReadWriter, err error) {
}
func (w *response) CloseNotify() <-chan bool {
+ w.lazyCloseNotifyMu.Lock()
+ defer w.lazyCloseNotifyMu.Unlock()
if w.handlerDone.Load() {
panic("net/http: CloseNotify called after ServeHTTP finished")
}
+ if w.closeNotifyCh == nil {
+ w.closeNotifyCh = make(chan bool, 1)
+ if w.closeNotifyTriggered {
+ w.closeNotifyCh <- true // action prior closeNotify call
+ }
+ }
return w.closeNotifyCh
}
+func (w *response) closeNotify() {
+ w.lazyCloseNotifyMu.Lock()
+ defer w.lazyCloseNotifyMu.Unlock()
+ if w.closeNotifyTriggered {
+ return // already triggered
+ }
+ w.closeNotifyTriggered = true
+ if w.closeNotifyCh != nil {
+ w.closeNotifyCh <- true
+ }
+}
+
func registerOnHitEOF(rc io.ReadCloser, fn func()) {
switch v := rc.(type) {
case *expectContinueReader: