net/http/internal/http2: let the server's serve goroutine exit when idle

Implement the TODO from 2014 at the top of server.go: a server
connection with nothing to do used to pin a goroutine blocked in the
serve loop (reached through net/http's conn.serve and ServeConn) in
addition to the readFrames goroutine; now it pins only readFrames.

When the serve loop has nothing runnable (nothing being written,
unflushed, or poppable on the write side, and no shutdown in
progress), the serve goroutine parks: it simply exits. Every sender
into the serve loop's channels first calls beginServeSend, which
counts in-flight sends and revives a parked loop on a new goroutine,
so a frame from the peer, a handler write or body read, a timer
firing, or a graceful shutdown request wakes the connection back up.

Open streams and running handlers don't prevent parking, since the
handler-side paths into the serve loop (response writes, body reads,
stream deadline timers, pushes) all go through beginServeSend too.
This helps SSE/long-poll-style servers: a connection whose handler is
parked mid-long-poll for hours pins only the readFrames and handler
goroutines, and the handler's next Write or Flush revives the loop.
With open streams, the write scheduler can still hold DATA frames
blocked on flow control; that's fine to park on, because only an
incoming WINDOW_UPDATE or SETTINGS frame can unblock them, and
incoming frames revive the loop.

The serve loop's teardown became explicit rather than deferred, and
it takes over the cleanup that ServeConn's callers used to do after
it returned (closing the connection, unregistering it, and running
net/http's StateClosed hook), signaled through the new
ServeConnOpts.OnClose hook. Callers that don't set OnClose keep the
old blocking ServeConn semantics and never park.

Parking happens between the frames of an active request, so the
park/resume cycle runs on hot paths. Spawning the resume goroutine
with a pre-allocated method-value closure keeps it allocation-free:
BenchmarkClientServer/h2 reports the same 68 allocs/op as before,
with ns/op unchanged within noise.

As a temporary safety measure, the GODEBUG=http2serveparking=0
setting disables parking, restoring the old behavior of keeping a
goroutine parked for the lifetime of each connection. The setting is
undocumented (#-prefixed), so it is not listed in doc/godebug.md or
runtime/metrics.

This is the server-side counterpart of CL 810780, which let the
Transport's request-write goroutine exit early for the same reason:
for servers with many mostly-idle connections, a parked goroutine and
its stack per connection add up.

Updates #80735
Updates #81524

Change-Id: I4a9fd4e2ce7a7a4e0f84179e54a9cc97d1a62ad5
Reviewed-on: https://go-review.googlesource.com/c/go/+/831084
Reviewed-by: Nicholas Husin <husin@google.com>
Reviewed-by: Nicholas Husin <nsh@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
9 files changed
tree: dc3ee2114cad4edb9bd4214b17bbb4b837f3d20e
  1. .github/
  2. api/
  3. doc/
  4. lib/
  5. misc/
  6. src/
  7. test/
  8. .gitattributes
  9. .gitignore
  10. codereview.cfg
  11. CONTRIBUTING.md
  12. go.env
  13. LICENSE
  14. PATENTS
  15. README.md
  16. SECURITY.md
README.md

The Go Programming Language

Go is an open source programming language that makes it easy to build simple, reliable, and efficient software.

Gopher image Gopher image by Renee French, licensed under Creative Commons 4.0 Attribution license.

Our canonical Git repository is located at https://go.googlesource.com/go. There is a mirror of the repository at https://github.com/golang/go.

Unless otherwise noted, the Go source files are distributed under the BSD-style license found in the LICENSE file.

Download and Install

Binary Distributions

Official binary distributions are available at https://go.dev/dl/.

After downloading a binary release, visit https://go.dev/doc/install for installation instructions.

Install From Source

If a binary distribution is not available for your combination of operating system and architecture, visit https://go.dev/doc/install/source for source installation instructions.

Contributing

Go is the work of thousands of contributors. We appreciate your help!

To contribute, please read the contribution guidelines at https://go.dev/doc/contribute.

Note that the Go project uses the issue tracker for bug reports and proposals only. See https://go.dev/wiki/Questions for a list of places to ask questions about the Go language.