Repository navigation
feat(server): pre-call connection admission bounds (issue #162) - #209
Conversation
…handshake hard bounds Issue #162: bound the framework-owned accepted/live connection set before RPC call admission. Every accepted connection holds a connection slot from accept to terminal cleanup; the TLS + Protocol v2 + authentication phase additionally holds a handshake slot released at Ready or on failure. Over-limit connections are closed immediately without entering the handshake/session lifecycle or spawning a framework task. - SharpLinkConnectionAdmissionOptions (server-specific): MaxConcurrentConnections default 1024; MaxConcurrentHandshakes default 0 (follows the connection bound). - ServerConnectionAdmission gate: interlocked counters, one lease per connection with idempotent exactly-once releases; single release owner is the terminal cleanup plus the Ready transition for the handshake slot. - Telemetry: sharplink.connections.admitted / .handshakes.active (updown) and sharplink.connections.rejected with reason; throttled 1105 warning and 5004 startup capacity logs. - 21 deterministic tests: options validation, gate unit tests, scripted-listener lifecycle/race coverage (reject, TLS throw, protocol reject, auth reject, Ready release, handshake bound, stop races, 2k churn, duplicate id, timeout), rejection telemetry, real-TCP connection and TLS handshake bounds.
…es in evidence probe - ServerConnectionAdmission.ServerConnectionAdmissionLease -> Lease. - Evidence probe observes sharplink.connections.admitted/rejected (present on the fixed build, zero on the base build) and disposes client transports on rejected connects so head runs do not leak sockets into the fd measurements.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 26de539bbd
ℹ️ 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".
… cleanup, isolate reject-dispose failures, probe throttle and gauges - Hold both admission slots until terminal cleanup completes: a slow transport disposal can no longer hand the slot to a new connection while the previous socket/task is still live (P1). Releases run in a nested finally so a failing cleanup still releases both slots. - A rejected connection whose DisposeAsync throws is logged and skipped instead of faulting the whole accept loop (P2). - Evidence probe: SemaphoreSlim throttle replaces the racy pending counter in both connection launchers; AdmittedConnectionsObserved sampled at peak while connections are live; scenario-filter argument; document note explains the default-bound behavior of head runs. - 3 new tests: slot held until terminal cleanup completes, dispose failure releases slots, rejected dispose failure does not fault the server.
|
@codex review |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
feat(server): pre-call connection admission bounds (issue #162)
Summary
Bounds the framework-owned connection resources that exist before RPC call admission:
every server now has a hard limit on simultaneously live accepted connections and an
independent hard limit on connections still inside TLS / Protocol v2 / authentication
handshake. Over-limit connections are rejected (closed) immediately — they never enter the
handshake/session lifecycle and never spawn a framework task.
Evidence (Phase 0, baremetal 8 pinned cores @ fd236a5)
With no bound, the framework-owned live set grows exactly with arrival rate:
sharplink.connections.activegauge (Ready-only, 0 observed in every stall scenario).
(~1.5 ms CPU per handshake, wall ≈ 117 ms) — an unbound burst can saturate cores.
shapes (an idle Ready connection is orders of magnitude cheaper than a TLS handshake),
so both bounds are exposed; handshakes are additionally always capped by the
connection bound.
Design
AcceptAsync; released exactly once at terminal cleanup. A rejected connection isdisposed inline and the loop continues — no task, no lifecycle entry.
AuthenticateAsync+ Protocol v2 handshake +application authenticator; released exactly once at the Ready transition (or by the
terminal cleanup for every pre-Ready failure: TLS throw/timeout, protocol reject,
auth reject/cancel, Stop, duplicate/replaced connection id).
releases; the single release owner is the terminal cleanup plus the Ready transition
for the handshake slot. No manual
Interlocked.Decrementscattered across catch blocks.carry no user state worth queueing; waiting would turn "handshaking" into "parked and
still consuming a socket + task", exactly the unbounded growth this issue removes.
RPC dispatch never touches the gate.
Accept races cannot leak a slot (task terminal cleanup always runs).
API (server-specific, mirrors UseAdmissionControl)
MaxConcurrentHandshakes0 (default) means no independent handshake bound;a positive value must not exceed
MaxConcurrentConnections.MaxConcurrentCallsPerConnection/PerServer(post-handshake RPCdispatch) — documented on the options class.
Diagnostics
sharplink.connections.admitted/sharplink.connections.handshakes.active(UpDown)sharplink.connections.rejectedwithsharplink.admission.reason=connection_limit|handshake_limitTests
21 deterministic tests in
ConnectionAdmissionTests:options validation, gate unit tests (readmit after release, idempotent release),
scripted-listener lifecycle coverage (immediate reject + dispose-exactly-once, TLS
throw, protocol reject + peer error frame, auth reject, Ready handshake-slot release,
handshake bound, Stop races, 2000-connection churn to zero, duplicate connection id,
handshake timeout), rejection telemetry, real-TCP connection bound and TLS handshake
bound behavior. Full unit suite 982/982.
Validation gates
no retained framework task (head evidence matrix on baremetal, default limits).
quickCI + integration/chaos suites on this PR.