Skip to content

Add reusable Tokio TLS client connector - #550

Open
nox wants to merge 1 commit into
masterfrom
nox/tokio-client-connector
Open

nox wants to merge 1 commit into
masterfrom
nox/tokio-client-connector

Conversation

@nox

@nox nox commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

This pull request adds a reusable client connector to tokio-boring. The connector accepts an already-established asynchronous stream, applies the configured connection and SSL callbacks, and performs the TLS handshake. Callers provide the peer name and session-cache port through Target, while AsTarget lets their callbacks continue to receive a richer application-specific type.

Connector<T, P = ()> parameterizes its cache by the partition type. The ordinary connect method is available on Connector<T, ()> and isolates sessions by target. connect_partitioned accepts a P value and resumes sessions only when the peer name, port, and partition value all match, allowing applications to include identity-affecting context such as a selected client certificate when sharing one connector across differently configured clients. SessionKey<P> stores the partition directly, preserving normal Eq and Hash semantics.

Connector clones share a bounded session cache, and TLS 1.3 tickets are removed before use so concurrent handshakes cannot select the same ticket. The complete generic key is stored in a lazily allocated, process-wide ex-data index for SessionKey<P>, following Boring’s existing typed cached-index pattern without leaking one index per connector.

It also updates hyper-boring to use this connector instead of maintaining its own session cache and handshake setup. The existing URI-based callback API, HTTP scheme handling, stream wrapper, Hyper connection metadata, and historical capacity-zero behavior remain unchanged.

@clanky-the-clanker clanky-the-clanker 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.

Superseded by the review of 60a4538a.

This review, of 5e655f50

💬 Approved with comments — One WARNING: every new Connector registers a new global SSL ex-data index that is never freed, which slowly leaks and grows per-Ssl cost when connectors are created repeatedly. One INFO about a change in what capacity 0 means.

Findings: 1 warning · 1 info (2 are inline comments on the diff)

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.

🔧 Fix with your agent

Paste the following into your coding agent to investigate and fix these issues.

Fix the following code review issues found in pull request #550.

Before making changes, review each issue and present a brief summary table:
- For each finding, state whether you agree, disagree, or need clarification
- If you disagree (e.g., the fix would require disproportionate changes
  for minimal ROI, or the finding is factually incorrect), explain why
- If you need to clarify something before deciding, ask those questions
- Then share your plan for which issues to tackle and in what order

## WARNING (1)

### [Code Quality] Every `Connector` registers a new process-global SSL ex-data index that is never freed. The old code used one static index.
- **File:** tokio-boring/src/client.rs:180

## INFO (1)

### [Code Quality] In hyper-boring, `session_cache_capacity(0)` used to keep one session per key. It now disables caching.
- **File:** tokio-boring/src/client.rs:104

📋 Code Quality: ⚠️ 1 warning · 1 info

The connector refactor is clean. Each Connector registers a new global ex-data index, and capacity-0 behaviour changes silently for hyper-boring.

Recommendation: 📝 Minor Comments

  • [WARNING] Every Connector registers a new process-global SSL ex-data index that is never freed. The old code used one static index. (tokio-boring/src/client.rs:180)
  • [INFO] In hyper-boring, session_cache_capacity(0) used to keep one session per key. It now disables caching. (tokio-boring/src/client.rs:104)
View full review
  • New global ex-data index for every connector (tokio-boring/src/client.rs:180): with_session_cache_capacity calls Ssl::new_ex_index::<SessionKey>() each time it runs. The old hyper-boring code kept one index in a static. BoringSSL cannot unregister ex-data indices, and every Ssl reserves a slot for each registered index. Code that builds connectors repeatedly (HttpsConnector::new/HttpsLayer::new per client or per task) grows that global table without bound. Each later Ssl allocation then costs a little more.
  • Capacity 0 (tokio-boring/src/client.rs:104): with per_key_session_capacity == 0, the old hyper-boring cache still kept one session per key. The new cache turns caching off. The new behaviour is more sensible and is documented on Connector, but HttpsLayerSettings users get the change without notice.

✅ All other checks passed

🔒 Security: ✅ LGTM

Session isolation by peer name and port is unchanged, TLS 1.3 tickets are still single-use, and IPv6 bracket stripping still happens before hostname verification.


📖 AGENTS.md: ✅ LGTM

No issues found.


⚡ Performance: ✅ LGTM

No performance issues beyond the ex-data index growth covered in the code quality section.


📝 Documentation: ✅ LGTM

The new public API has rustdoc comments.


Reviewed commit: 5e655f50

Comment thread tokio-boring/src/client.rs Outdated
capacity: usize,
) -> Result<Self, ErrorStack> {
let cache = Arc::new(Mutex::new(SessionCache::with_capacity(capacity)));
let key_index = Ssl::new_ex_index::<SessionKey>()?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ WARNING · Code Quality

Every Connector registers a new process-global SSL ex-data index that is never freed. The old code used one static index.

Ssl::new_ex_index adds an index to BoringSSL's global SSL ex-data class, and that class cannot unregister indices. Before this change, hyper-boring kept one index in a static, so every connector shared it. Now Connector::new, HttpsConnector::new and HttpsLayer::new each add one more. A process that creates connectors repeatedly will grow the global index table without bound, and every Ssl allocated later reserves a slot for each index. Since SessionKey is private, a single lazily initialised static index (for example a LazyLock<Index<Ssl, SessionKey>>, or Ssl::cached_ex_index) is enough.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 60a4538. The session key index is now initialized once with a process-wide LazyLock and shared by every Connector.

}

fn insert(&mut self, key: SessionKey, session: SslSession) {
if self.per_target_capacity == 0 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ℹ️ INFO · Code Quality

In hyper-boring, session_cache_capacity(0) used to keep one session per key. It now disables caching.

With capacity 0, the old hyper-boring SessionCache::insert evicted nothing and still inserted the session, so one session per key stayed cached. This early return turns caching off completely. That is a reasonable meaning for 0, but HttpsLayerSettings users get the change without notice. Consider mentioning it in the changelog.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 60a4538. hyper-boring now maps its legacy capacity of 0 to 1 and has regression coverage, while the new tokio-boring Connector keeps its documented 0 = disabled behavior.

@nox
nox force-pushed the nox/tokio-client-connector branch from 5e655f5 to 60a4538 Compare November 4, 2026 14:31

@clanky-the-clanker clanky-the-clanker 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.

Superseded by the review of 63447672.

This review, of 60a4538a

✅ Approved — Both earlier findings (per-connector ex-data index, capacity-0 semantics in hyper-boring) are fixed, and the changed code has no new issues.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

Sessions are still isolated by peer name and port, TLS 1.3 tickets are still single-use, and hostname verification still sees the host with IPv6 brackets removed.


📖 AGENTS.md: ✅ LGTM

No issues found.


📋 Code Quality: ✅ LGTM

The ex-data index is now a single process-wide LazyLock (tokio-boring/src/client.rs:117-118). hyper-boring maps capacity 0 to 1 to keep its previous behaviour, and a test covers this. Callback order, IPv6 bracket stripping and TLS 1.3 ticket removal match the removed hyper-boring implementation.


⚡ Performance: ✅ LGTM

No issues found.


📝 Documentation: ✅ LGTM

No issues found.


Reviewed commit: 60a4538a

@nox
nox force-pushed the nox/tokio-client-connector branch from 60a4538 to 6344767 Compare November 4, 2026 14:40

@clanky-the-clanker clanky-the-clanker 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.

Superseded by the review of fbc30ad4.

This review, of 63447672

✅ Approved — No issues found. The hyper-boring/src/v1.rs change keeps the existing callback, error and session-cache behaviour, and both earlier comments are still fixed.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

  • Hostname verification still gets the host with IPv6 brackets removed.
  • Sessions are still kept separate per peer name and port.
  • TLS 1.3 tickets are still used only once.

📖 AGENTS.md: ✅ LGTM

No issues found.


📋 Code Quality: ✅ LGTM

  • effective_session_cache_capacity turns a capacity of 0 into 1, and a unit test covers it.
  • HttpTarget::try_new keeps the missing-host error.
  • IPv6 brackets are still stripped in Target::new, as the removed hyper-boring code did.
  • The localhost test now checks that set_callback gets the URI on each connection.

⚡ Performance: ✅ LGTM

  • One process-wide ex-data index is shared by all connectors.
  • Cache insert and lookup stay O(1).
  • Each connection allocates one extra String for the peer name, which is negligible.

📝 Documentation: ✅ LGTM

No issues found.


Reviewed commit: 63447672

@nox
nox force-pushed the nox/tokio-client-connector branch from 6344767 to fbc30ad Compare November 4, 2026 15:04

@clanky-the-clanker clanky-the-clanker 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.

Superseded by the review of 6674fe4d.

This review, of fbc30ad4

✅ Approved — No issues found. Since the last review, the only change pins the CI runner to ubuntu-24.04, and the connector and the hyper-boring migration keep the behaviour approved earlier.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No issues found. Hostname verification, keeping sessions separate per peer, and using each TLS 1.3 ticket only once all work as before.


📖 AGENTS.md: ✅ LGTM

No issues found.


📋 Code Quality: ✅ LGTM

No issues found.

  • CI change: .github/workflows/ci.yml now pins the test-boringssl runner to ubuntu-24.04. It does not affect the code.
  • hyper-boring: it keeps its earlier behaviour:
    • The config callback still runs before a session is chosen.
    • A missing host still gives the same error.
    • IPv6 brackets are still stripped.
    • A cache capacity of 0 still maps to 1.
  • Removed dependencies: nothing in hyper-boring still uses antidote or linked_hash_set.

⚡ Performance: ✅ LGTM

No issues found.

  • Session cache insert and lookup stay O(1).
  • All connectors share one process-wide ex-data index.
  • Each connection adds one small String allocation, which is negligible.

📝 Documentation: ✅ LGTM

No issues found.


Reviewed commit: fbc30ad4

@nox
nox force-pushed the nox/tokio-client-connector branch from fbc30ad to 6674fe4 Compare November 4, 2026 15:06

@clanky-the-clanker clanky-the-clanker 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.

Superseded by the review of e382a152.

This review, of 6674fe4d

✅ Approved — No issues found. Since the last review, only .github/workflows/ci.yml changed: it pins the runner and adds a BoringSSL build flag to avoid a GCC Keccak overflow false positive. The connector and the hyper-boring migration behave as approved before.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No issues found. Hostname verification still works, sessions stay separate per peer, and each TLS 1.3 ticket is still used only once.


📖 AGENTS.md: ✅ LGTM

No issues found.


📋 Code Quality: ✅ LGTM

No issues found.

Since the last review, the only change is in .github/workflows/ci.yml: it pins the CI runner and adds a BoringSSL CMake flag. Neither touches the library code.

The connector and the hyper-boring migration behave as before:

  • The config callback still runs before a session is chosen.
  • A missing host still returns the same error.
  • A cache capacity of 0 still maps to 1 in hyper-boring.
  • IPv6 brackets are still stripped.
  • All connectors share one session key index, created once.

⚡ Performance: ✅ LGTM

No issues found.

  • Session cache insert and lookup are still O(1), and each target's cache stays bounded.
  • All connectors share one process-wide ex-data index.
  • Each connection adds a few small String allocations. That cost is negligible next to a TLS handshake.

📝 Documentation: ✅ LGTM

No issues found in the changed files.


Reviewed commit: 6674fe4d

@nox
nox force-pushed the nox/tokio-client-connector branch from 6674fe4 to e382a15 Compare November 4, 2026 16:41

@clanky-the-clanker clanky-the-clanker 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.

Superseded by the review of 023f7393.

This review, of e382a152

✅ Approved — No issues found. The two earlier discussion points (shared ex-data index, capacity-0 mapping in hyper-boring) are fixed at this commit.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No issues found.

  • Hostname verification still uses the peer name.
  • Sessions are cached per peer name and port, so peers do not share them.
  • Each TLS 1.3 ticket is removed from the cache under the lock before use, so concurrent handshakes cannot pick the same one.

📖 AGENTS.md: ✅ LGTM

No issues found.


📋 Code Quality: ✅ LGTM

Both earlier points are fixed:

  • The session key index is initialised once through a process-wide LazyLock and every connector shares it.
  • effective_session_cache_capacity in hyper-boring/src/v1.rs maps 0 to 1. Connector keeps 0 = disabled, and a test covers that.

The hyper-boring migration keeps the existing behaviour:

  • The config callback still runs before a session is chosen.
  • The default port is still 443, and a missing host returns the same error.
  • IPv6 brackets are still stripped.

⚡ Performance: ✅ LGTM

No issues found.

  • Cache insert and lookup are O(1), and each target's cache stays bounded by its capacity.
  • The ex-data index is process-wide.
  • Each connection allocates a few small strings, which is negligible next to a TLS handshake.

📝 Documentation: ✅ LGTM

No issues found in the changed files.


Reviewed commit: e382a152

@nox
nox force-pushed the nox/tokio-client-connector branch from e382a15 to 023f739 Compare November 4, 2026 19:13

@clanky-the-clanker clanky-the-clanker 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.

Superseded by the review of 44d2a2f1.

This review, of 023f7393

✅ Approved — No issues found at this commit. The new partitioned session cache keeps identities apart, and the earlier discussion points are still fixed.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

No issues found.

  • Session partitions compare type and value, so sessions cannot be reused across identities.
  • Hostname verification still uses the peer name.
  • TLS 1.3 tickets are single-use: each is removed from the cache under the lock before it is returned.

📖 AGENTS.md: ✅ LGTM

No issues found.


📋 Code Quality: ✅ LGTM

No issues found.

  • The session-key index is process-wide.
  • effective_session_cache_capacity keeps hyper-boring's old behaviour for capacity 0.
  • Setup errors are returned as HandshakeError(SetupFailure(..)).
  • The tests cover partition isolation, a cache shared by clones and callback ordering.

⚡ Performance: ✅ LGTM

No issues found.


📝 Documentation: ✅ LGTM

No issues found in the changed files.


Reviewed commit: 023f7393

@nox
nox force-pushed the nox/tokio-client-connector branch from 023f739 to 44d2a2f Compare November 4, 2026 19:16

@clanky-the-clanker clanky-the-clanker 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.

Superseded by the review of e52a36b1.

This review, of 44d2a2f1

✅ Approved — No issues found in the code changed since the last review. Session isolation, single-use tickets and hyper-boring's existing behaviour all hold up.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

  • Session keys: they include the peer name, the port and the partition's type and value. Partitions are compared for equality, not only by hash.
  • Hostname verification: it still uses the peer name.
  • TLS 1.3 tickets: each one is removed from the cache under the lock before it is returned, so it is used only once.

📖 AGENTS.md: ✅ LGTM

No issues found.


📋 Code Quality: ✅ LGTM

  • Behaviour kept: the callback order is unchanged, IPv6 brackets are now stripped in Target::new, and the error for a URI with no host is still returned.
  • Capacity 0: effective_session_cache_capacity keeps hyper-boring's old behaviour.
  • Dependencies: antidote and linked_hash_set are no longer used in hyper-boring, and both are declared in the workspace Cargo.toml, so the new tokio-boring dependencies resolve.
  • Tests: they cover resumption, isolation by partition and by port, a cache shared by clones, callback ordering and zero capacity.

⚡ Performance: ✅ LGTM

  • Per-connection cost: each connection adds one small String and one Arc allocation.
  • Cache lock: it is never held across an .await.
  • Ex-data index: the process-wide LazyLock index avoids registering a new global index for every connector.

📝 Documentation: ✅ LGTM

No issues found in the changed files.


Reviewed commit: 44d2a2f1

@nox
nox force-pushed the nox/tokio-client-connector branch from 44d2a2f to 619e7fb Compare November 4, 2026 19:19
@nox
nox force-pushed the nox/tokio-client-connector branch from 619e7fb to e52a36b Compare November 4, 2026 19:20

@clanky-the-clanker clanky-the-clanker 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.

✅ Approved — No issues found in the code changed since the last review. The ex-data index fix and the capacity-zero fix from the earlier discussion are both in this commit, and session isolation and single-use TLS 1.3 tickets still hold.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🔒 Security: ✅ LGTM

  • Session keys: they include the peer name, the port and the partition value, and partitions are compared with Eq.
  • TLS 1.3 tickets: each one is removed from the cache under the lock before it is returned, so it is used only once.
  • Hostname verification: it still uses the peer name.
  • Public Ssl::cached_ex_index: each index is typed, and SessionKey<P> is private to tokio-boring, so no other code can read or overwrite the session key.

📖 AGENTS.md: ✅ LGTM

No issues found.


📋 Code Quality: ✅ LGTM

  • Shared ex-data index: the process-wide Ssl::cached_ex_index::<SessionKey<P>>() avoids registering a new global index for every connector.
  • Cache lock: it is never held across an .await.
  • Capacity zero: hyper-boring still treats a capacity of 0 the way it always did.
  • Tests: they cover resumption, isolation by partition and by port, a cache shared by clones, callback order and zero capacity.

⚡ Performance: ✅ LGTM

  • Per-connection cost: each connection adds one small String and one Arc allocation.
  • Cache lock: it is held only for short operations and never across an .await.
  • Cache size: the cache is still bounded per key, as before.

📝 Documentation: ✅ LGTM

No issues found in the changed files.


Reviewed commit: e52a36b1

This branch has not been deployed

No deployments
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.

1 participant