Skip to content

Add concurrent TLS handshake benchmark for SslStream - #5314

Open
wfurt wants to merge 1 commit into
dotnet:mainfrom
wfurt:tls-concurrent-handshake-benchmark
Open

wfurt wants to merge 1 commit into
dotnet:mainfrom
wfurt:tls-concurrent-handshake-benchmark

Conversation

@wfurt

@wfurt wfurt commented Sep 19, 2026

Copy link
Copy Markdown
Member

The existing SslStream benchmarks only ever handshake one connection at a time, so there is no coverage of the shape clients actually use against a single host: connection pools, and HTTP/2 and HTTP/3 pool warm-up, fanning out several handshakes at once.

SslStreamConcurrencyTests.ConcurrentHandshake measures per-handshake cost at concurrency levels 1, 8, 64 and 128, with and without session resumption, for TLS 1.2 and TLS 1.3. That is 16 test cases, the maximum TooManyTestCasesValidator allows for a single benchmark.

It lives in its own class because [Params] apply to every benchmark in a class, so adding these to SslStreamTests would have multiplied its entire matrix.

Why loopback sockets rather than in-memory streams

This is the one deliberate departure from the surrounding benchmarks, and it is required rather than incidental. Measuring the steady-state TLS 1.3 resumption rate under identical wave scheduling, varying only the transport:

Concurrency in-memory ConnectedStreams loopback socket
1 100.0% 100.0%
8 100.0% 100.0%
64 96.6% 51.6%
128 96.4% 37.2%

TLS 1.2 stays at 100% on both transports at every level. Over in-memory streams the concurrent case essentially does not occur, so an in-memory version of this benchmark would measure the uncontended path whatever Concurrency was set to.

In-memory is also noticeably slower and noisier here — TLS 1.2 at 8 concurrent connections costs 1596 µs/handshake in memory versus 199 µs over sockets, because StreamBuffer overhead dominates the TLS work.

Results (Linux x64, OpenSSL 3.0.13, RSA-2048)

Resumed handshakes, µs each:

Concurrency TLS 1.2 TLS 1.3
1 2831.5 4118.2
8 234.1 319.5
64 172.2 317.5
128 173.1 326.9

For concurrency ≥ 8 every cell reports under 2% error, and the numbers reproduced across repeated runs.

Notes

  • [GlobalSetup] asserts that resumption actually takes effect before measuring. Without that check the Resume=true cases can silently measure full handshakes and still look like plausible results.
  • The client closes abortively (LingerOption(true, 0)) once it has ingested its session ticket. A graceful close left a peak of ~85,000 sockets in TIME_WAIT, which is 65% of the default tcp_max_tw_buckets and risks exhausting the ephemeral port range; this brings the peak down to ~122. Concurrent file descriptor use is bounded by Concurrency, not by run length.
  • Wall time is about 195 s for all 16 cases on the default job.

Note

This pull request description was generated with AI assistance.

The existing SslStream benchmarks only ever handshake one connection at a
time, so there is no coverage of the shape clients actually use against a
single host: connection pools, and HTTP/2 and HTTP/3 pool warm-up, fanning
out several handshakes at once.

SslStreamConcurrencyTests measures per-handshake cost at concurrency levels
1, 8, 64 and 128, with and without session resumption, for TLS 1.2 and
TLS 1.3.

Unlike the other SslStream benchmarks this one runs over loopback sockets.
Measured over in-memory streams, concurrent TLS 1.3 connections resume
~97% of the time at every concurrency level; over sockets the rate falls to
~40% at 128 concurrent connections. An in-memory version would measure the
uncontended case whatever Concurrency was set to.
Copilot AI lite review requested due to automatic review settings September 19, 2026 05:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The server-side accept/serve background tasks currently risk masking unexpected failures and producing unobserved task faults during cleanup, which can destabilize benchmark runs.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

Adds a new microbenchmark targeting concurrent TLS handshakes with SslStream, intended to better represent real client behavior (connection pools / HTTP/2+ warm-up) and to measure per-handshake cost across multiple concurrency levels and protocol/resumption combinations.

Changes:

  • Introduces SslStreamConcurrencyTests.ConcurrentHandshake measuring 128 handshakes per invocation in waves of configurable concurrency.
  • Adds protocol matrix (TLS 1.2 + optional TLS 1.3) and session-resumption toggle, with a GlobalSetup assertion that resumption is actually occurring.
  • Implements a loopback-socket server accept/serve loop to drive concurrent client handshakes.
File Description
src/​benchmarks/​micro/​libraries/​System.Net.Security/​SslStreamConcurrencyTests.cs New benchmark class for concurrent SslStream handshakes over loopback sockets, parameterized by protocol, resumption, and concurrency.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +135 to +138
catch
{
return;
}
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.

2 participants