Skip to content

net.http: fix TLS server close_idle fd-reuse race and remove dead set_read_timeout - #27542

Merged
JalonSolov merged 4 commits into
vlang:masterfrom
quaesitor-scientiam:tls-server-27433-followups
Jun 24, 2026
Merged

net.http: fix TLS server close_idle fd-reuse race and remove dead set_read_timeout#27542
JalonSolov merged 4 commits into
vlang:masterfrom
quaesitor-scientiam:tls-server-27433-followups

Conversation

@quaesitor-scientiam

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #27429 (TLS server shutdown fix), addressing items 2 and 4 from #27433.

  • server_tls_idle.vclose_idle fd-reuse race (item 4): the old code dropped t.mu before calling net.shutdown, leaving a window where a worker could race through unmark_idle → conn.shutdown() → net.close(fd), the OS could reuse that fd for a new socket, and the subsequent net.shutdown would interrupt an unrelated connection. Fix: move t.mu.unlock() to after the shutdown loop. Accepted TLS connections are blocking sockets (accept_with_timeouts restores blocking mode before returning), so net.shutdown returns immediately — no stall from holding the lock.

  • server_tls_notd_use_openssl.v — dead set_read_timeout call (item 2): the per-connection conn.set_read_timeout(s.read_timeout) call after accept had no effect. Accepted connections are bound to the listener's ssl config (l.conf) via mbedtls_ssl_setup; the per-conn conf that set_read_timeout writes to is never attached to the live ssl session. The server read_timeout is correctly applied when the SSLListener is created with read_timeout: s.read_timeout in its config. Remove the dead call.

Items 1 and 3 from #27433 were already fixed upstream or remain open architectural work.

Test plan

  • Build: ./v build vlib/net/http/
  • ./v test vlib/net/http/ passes
  • Confirm close_idle holds t.mu across the shutdown loop
  • Confirm no set_read_timeout call in listen_and_serve_tls

🧙 Built with WOZCODE

…_read_timeout

close_idle (server_tls_idle.v): move t.mu.unlock() to after the
net.shutdown loop instead of before it. The old code dropped the lock
before calling net.shutdown, leaving a window where a worker could
race through unmark_idle → conn.shutdown() → net.close(fd), the OS
could reuse that fd for a freshly accepted socket, and the subsequent
net.shutdown would then interrupt an unrelated connection. Accepted TLS
connections are blocking sockets (accept_with_timeouts restores blocking
mode before returning), so net.shutdown returns immediately — holding
the lock across the loop does not introduce a liveness hazard.

listen_and_serve_tls (server_tls_notd_use_openssl.v): remove the
dead conn.set_read_timeout(s.read_timeout) call after accept. Accepted
connections are bound to the listener's ssl config (l.conf) via
mbedtls_ssl_setup; the per-conn conf that set_read_timeout writes to
is never bound to conn.ssl and has no effect on the live session. The
server read_timeout is already applied correctly when the SSLListener
is created with read_timeout: s.read_timeout in its config.

Fixes items 2 (dead call) and 4 (fd-reuse race) from vlang#27433.

Co-Authored-By: WOZCODE <contact@withwoz.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.

@medvednikov

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 73d6230650

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread vlib/net/http/server_tls_idle.v Outdated
The Codex review of PR vlang#27542 flagged that the lock-widening made
close_idle''s net.close(handle) and the worker''s deferred conn.shutdown()
a guaranteed double-close on Windows.

Removing net.close was the wrong fix: net.shutdown(SD_BOTH) alone does
NOT unblock a concurrent C.select() on Windows (only closesocket() does),
so workers blocked in wait_for_read would hang for up to 41 s
(mbedtls_server_read_timeout_ms) before noticing shutdown.

Keep net.close in the Windows path; the double-close is safe because:
- listen_and_serve_tls calls ch.close() before close_idle()
- the accept loop has already stopped at that point
- active handlers have called unmark_idle before w.handler.handle() runs,
  so close_idle only touches truly idle handles
- the worker''s second closesocket returns WSAENOTSOCK, swallowed by or {}

Also switch to defer { t.mu.unlock() } for consistency with mark_idle
and unmark_idle.

Co-Authored-By: WOZCODE <contact@withwoz.com>
@JalonSolov

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae40710fe8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread vlib/net/http/server_tls_idle.v
close_idle force-closes idle handles on Windows with net.close (closesocket) to
wake a worker blocked in select(), which net.shutdown(SD_BOTH) does not do there.
But the worker's handle_conn defer then ran conn.shutdown() unconditionally,
closing the same fd a second time. Between the two closes the SOCKET value can be
reused by ANY socket the process opens -- handle reuse is process-wide on
Windows, not limited to this server's accept loop -- so the worker's second close
could land on an unrelated socket. The previous "double-close is safe" comment
only reasoned about this server's accept loop and was wrong.

Transfer ownership: close_idle records the handles it force-closed (under the
lock, Windows only), and the worker consults was_force_closed and skips its own
conn.shutdown so the fd is closed exactly once. The tracker lock stays held
across the shutdown loop: on non-Windows the worker is still the sole closer, and
the held lock keeps its unmark_idle (hence its net.close) ordered after our
net.shutdown, so the fd cannot be closed and recycled before we shut it down.
Found by Codex on vlang#27542.

Co-Authored-By: WOZCODE <contact@withwoz.com>
@JalonSolov

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 31405a6150

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread vlib/net/http/server_tls_notd_use_openssl.v Outdated
The previous fix skipped conn.shutdown() entirely for an idle TLS connection
that close_idle force-closed on Windows, to avoid closing the socket a second
time. But SSLConn.shutdown() does two jobs: it frees the mbedtls TLS resources
(SSL context, config, certs, RNG state, ALPN allocation) AND closes the socket.
Skipping it leaked all of those for every force-closed idle connection at
server shutdown.

Split the two concerns instead of bypassing cleanup: when close_idle already
owns the socket close (was_force_closed), relinquish socket ownership
(conn.owns_socket = false) and still call conn.shutdown(). shutdown() then frees
the TLS resources but skips net.close, so the fd is still closed exactly once
(by close_idle) and nothing leaks. On non-Windows was_force_closed is always
false and the worker remains the sole closer, unchanged. Found by Codex on
vlang#27542.

Co-Authored-By: WOZCODE <contact@withwoz.com>
@JalonSolov

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants