Skip to content

fix(nvhttp): non-blocking TLS shutdown in SunshineHTTPS destructor - #5617

Open
awerty-noob wants to merge 1 commit into
LizardByte:masterfrom
awerty-noob:fix/nvhttp-tls-shutdown
Open

fix(nvhttp): non-blocking TLS shutdown in SunshineHTTPS destructor#5617
awerty-noob wants to merge 1 commit into
LizardByte:masterfrom
awerty-noob:fix/nvhttp-tls-shutdown

Conversation

@awerty-noob

Copy link
Copy Markdown

Summary

Fixes the HTTPS server (port 47984) permanently hanging after a Moonlight/Host client connects, as reported in #5289: "Sunshine connections fail after using certain Moonlight clients".

Root cause

nvhttp::SunshineHTTPS wraps SimpleWeb::HTTPS (an asio::ssl::stream<tcp::socket>) and added a destructor that performs a synchronous TLS shutdown:

virtual ~SunshineHTTPS() {
  SimpleWeb::error_code ec;
  shutdown(ec);   // blocking!
}

The SunshineHTTPS connection object is destroyed on the single io thread of the HTTPS server (Simple-Web-Server runs with thread_pool_size = 1, i.e. one thread serves all connections on 47984). When the connection is destroyed (e.g. right after a response is written with close_connection_after_response), ssl::stream::shutdown():

  1. writes our close_notify, then
  2. waits for the peer's close_notify — via a blocking read_some()socket_ops::poll_read(fd, -1) because the socket is blocking.

Clients that keep the connection open after receiving a response — the Moonlight HTTP connection pool (Android app, Moonlight-Switch, Moonlight-N3DS, …) — never send a close_notify. The io thread then blocks in poll() forever, so the server stops accepting all new connections: the listen backlog grows, every new HTTPS request times out (Moonlight: "Host reachable but app list authentication failed, HTTP request timed out"), while port 47989 / RTSP / streaming keep working and the process looks healthy.

Confirmed from a core dump of the hung thread:

io_context::run()
  → reactive_socket_send_op::do_complete            (response just sent)
    → Response::~Response → ~Session → Connection released
      → ~SunshineHTTPS() → stream::shutdown()
        → ssl::detail::io<shutdown_op>              (io.hpp:47)
          → basic_stream_socket::read_some          (blocking, socket is blocking)
            → socket_ops::sync_recv1 → poll_read(s, -1)   ← hangs forever
              → poll(fd, POLLIN, INFINITE)

Fix

Mark the underlying socket non-blocking before shutdown() in the destructor. The SSL_shutdown() close_notify is still written best-effort, but instead of blocking forever waiting for the peer reply, the shutdown attempt returns immediately with would_block and the socket is closed. The io thread is never blocked, so the server keeps accepting connections.

Verification

  • Before: a client that sends GET /serverinfo then holds the connection open (no FIN, no close_notify — exactly the pooled-connection pattern) wedges the server; a probe 25 s later times out.
  • After the fix: the same scenario keeps the server fully responsive (repeated probes return 200 OK).
  • Normal client teardown, pairing, app list, launch and streaming flows are unaffected; the close_notify is still sent for clients that terminate gracefully.

Note

The SunshineHTTPS wrapper was introduced in f048510e ("fix(nvhttp): wrap TLS socket to ensure graceful closure", #3077) which is newer than v0.23.1 — matching the issue's report that this affects all versions newer than v0.23.1.

The ~SunshineHTTPS() destructor performs a synchronous ssl::stream::shutdown()
on the single io thread of the HTTPS server (port 47984). After the local
close_notify has been written, SSL_shutdown() waits for the peer's close_notify
using a blocking read_some()->poll(fd, POLLIN, -1) on the underlying socket.

Clients that keep the connection open after receiving a response - e.g. the
Moonlight HTTP connection pool (Android app, Moonlight-Switch, Moonlight-N3DS)
- never send a close_notify, so the io thread blocks forever and the entire
HTTPS server stops accepting connections. Symptom: 'Host reachable, but app
list authentication fails, HTTP request timed out' plus a growing listen
backlog on port 47984, while 47989/RTSP keep working.

Set the socket non-blocking before shutdown so the attempt returns immediately
with would_block instead of hanging; the close_notify is still sent
best-effort and the socket is closed right after.
@sonarqubecloud

sonarqubecloud Bot commented Sep 5, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 New issue
1 New Bugs (required ≤ 0)
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@ReenigneArcher ReenigneArcher added the ai PR has signs of heavy ai usage (either indicated by user or assumed) label Sep 5, 2026
@ReenigneArcher

Copy link
Copy Markdown
Member

Thank you for the PR submission, but it looks like you used AI to create this PR.

Please read and follow our Contributing guidelines and specifically our AI Usage policy.

Additionally, please update the PR to use the correct template. You can find it at https://github.com/LizardByte/.github/blob/master/.github/pull_request_template.md?plain=1

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

Labels

ai PR has signs of heavy ai usage (either indicated by user or assumed)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants