Skip to content

Commit 143e897

Browse files
net.http: track h2 server connections for idle shutdown once, not per frame (#27435)
The HTTP/2 server registered each connection's fd in the TLS idle-conn tracker around every frame read (mark_idle before, unmark_idle after), so the shared tracker mutex -- and an O(n) handle-list scan in unmark -- was hit on the hot path for every frame on every connection. Register the connection once for its serve lifetime instead. The reader spends almost all of its time blocked in a frame read, so the per-frame churn bought nothing: close_idle still interrupts the reader on shutdown by shutting the fd down. An h2 request in flight when the server stops is now interrupted rather than allowed to finish, which is acceptable at shutdown and is not relied on by any caller -- the graceful "wait for the active request" guarantee is HTTP/1.1-only and unaffected. Refs #27433. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 75789c6 commit 143e897

1 file changed

Lines changed: 20 additions & 29 deletions

File tree

vlib/net/http/h2_server.v

Lines changed: 20 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,28 @@ fn serve_h2_conn_with_idle_tracker(mut transport H2Transport, mut handler Handle
7373
}
7474

7575
fn (mut c H2ServerConn) serve(mut handler Handler) ! {
76-
c.read_client_preface_idle()!
76+
// Register the connection with the idle tracker once for its whole
77+
// lifetime, instead of around every frame read. The reader thread spends
78+
// nearly all of its time blocked in a frame read, so per-frame mark/unmark
79+
// only added shared-lock contention (and an O(n) list scan) on the hot
80+
// path. On shutdown, close_idle still interrupts the reader by shutting the
81+
// fd down; an h2 request in flight when the server stops is interrupted,
82+
// which is acceptable at shutdown and is not relied on by any caller (the
83+
// graceful "wait for active request" guarantee is HTTP/1.1-only).
84+
tracked := c.should_track_idle_read()
85+
if tracked && !c.idle_conns.mark_idle(c.idle_handle) {
86+
// The server is already shutting down; do not start serving.
87+
return
88+
}
89+
defer {
90+
if tracked {
91+
c.idle_conns.unmark_idle(c.idle_handle)
92+
}
93+
}
94+
c.read_client_preface()!
7795
c.send_initial_settings()!
7896
for !c.closing {
79-
frame := c.read_idle_frame() or {
97+
frame := c.read_frame() or {
8098
// Treat a clean transport close as end of session.
8199
return
82100
}
@@ -88,33 +106,6 @@ fn (mut c H2ServerConn) should_track_idle_read() bool {
88106
return c.idle_handle > 0 && c.idle_conns != unsafe { nil }
89107
}
90108

91-
fn (mut c H2ServerConn) read_client_preface_idle() ! {
92-
if !c.should_track_idle_read() {
93-
c.read_client_preface()!
94-
return
95-
}
96-
if !c.idle_conns.mark_idle(c.idle_handle) {
97-
return error('h2 server: connection is shutting down')
98-
}
99-
defer {
100-
c.idle_conns.unmark_idle(c.idle_handle)
101-
}
102-
c.read_client_preface()!
103-
}
104-
105-
fn (mut c H2ServerConn) read_idle_frame() !H2Frame {
106-
if !c.should_track_idle_read() {
107-
return c.read_frame()!
108-
}
109-
if !c.idle_conns.mark_idle(c.idle_handle) {
110-
return error('h2 server: connection is shutting down')
111-
}
112-
defer {
113-
c.idle_conns.unmark_idle(c.idle_handle)
114-
}
115-
return c.read_frame()!
116-
}
117-
118109
fn (mut c H2ServerConn) read_client_preface() ! {
119110
c.fill_at_least(h2_client_preface.len)!
120111
got := c.rbuf[..h2_client_preface.len].bytestr()

0 commit comments

Comments
 (0)