Class: Hyperion::TLS::HandshakeRateLimiter
- Inherits:
-
Object
- Object
- Hyperion::TLS::HandshakeRateLimiter
- Defined in:
- lib/hyperion/tls.rb
Overview
2.3-B: TLS handshake CPU throttle. Per-worker token bucket sized at the operator’s ‘tls.handshake_rate_limit` (handshakes/sec). Capacity == rate so a steady-state handshake stream of `rate` handshakes/sec passes cleanly while a burst above the rate is rate-limited; tokens refill at `rate` per second uniformly.
**When this fires.** A flood of new TLS handshakes (e.g., during a deployment when nginx restarts and reconnects everything) can starve regular requests of CPU — RSA/ECDHE handshakes are the most expensive op the server does. The bucket caps that starvation by closing the TCP connection at the listener edge before SSL_accept runs; clients see a clean TCP RST/FIN and retry. Default ‘:unlimited` keeps 2.2.0 behaviour.
**For nginx-fronted topologies** this is mostly defensive: nginx keeps long-lived upstream connections, so handshake rate is normally near-zero. Real value is for direct-exposure operators or staging environments where misconfiguration causes a handshake storm.
Concurrency. A Mutex-guarded refill+take. Hold time is one ‘Process.clock_gettime` + a couple of arithmetic ops — tens of nanoseconds. Contention is bounded by handshake rate (orders of magnitude lower than request rate), so the mutex is never on the hot per-request path.
Instance Attribute Summary collapse
-
#capacity ⇒ Object
readonly
Returns the value of attribute capacity.
-
#rate ⇒ Object
readonly
Returns the value of attribute rate.
Instance Method Summary collapse
-
#acquire_token! ⇒ Object
True when the bucket had a token to spend (handshake proceeds).
-
#initialize(rate) ⇒ HandshakeRateLimiter
constructor
Build a limiter for ‘rate` handshakes/sec/worker, or `:unlimited` to short-circuit every `acquire_token!` to true (no throttle).
-
#stats ⇒ Object
Snapshot for stats / logging.
Constructor Details
#initialize(rate) ⇒ HandshakeRateLimiter
Build a limiter for ‘rate` handshakes/sec/worker, or `:unlimited` to short-circuit every `acquire_token!` to true (no throttle). Anything else raises ArgumentError so config typos surface at boot.
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/hyperion/tls.rb', line 322 def initialize(rate) if rate == :unlimited || rate.nil? @rate = :unlimited @capacity = nil @tokens = nil @last_refill_at = nil @mutex = nil elsif rate.is_a?(Integer) && rate.positive? @rate = rate @capacity = rate.to_f @tokens = @capacity @last_refill_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) @mutex = Mutex.new else raise ArgumentError, "tls.handshake_rate_limit must be a positive integer or :unlimited (got #{rate.inspect})" end @rejected = 0 end |
Instance Attribute Details
#capacity ⇒ Object (readonly)
Returns the value of attribute capacity.
316 317 318 |
# File 'lib/hyperion/tls.rb', line 316 def capacity @capacity end |
#rate ⇒ Object (readonly)
Returns the value of attribute rate.
316 317 318 |
# File 'lib/hyperion/tls.rb', line 316 def rate @rate end |
Instance Method Details
#acquire_token! ⇒ Object
True when the bucket had a token to spend (handshake proceeds). False when the bucket is empty (caller should close the TCP connection without running SSL_accept — saves the CPU cost of the asymmetric crypto under handshake-storm conditions).
346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/hyperion/tls.rb', line 346 def acquire_token! return true if @rate == :unlimited @mutex.synchronize do refill_locked! if @tokens >= 1.0 @tokens -= 1.0 true else @rejected += 1 false end end end |
#stats ⇒ Object
Snapshot for stats / logging. ‘tokens` is the current bucket level (float), `rejected` is the cumulative count of denied handshake attempts since limiter construction.
364 365 366 367 368 369 370 371 |
# File 'lib/hyperion/tls.rb', line 364 def stats return { rate: :unlimited, rejected: 0 } if @rate == :unlimited @mutex.synchronize do refill_locked! { rate: @rate, capacity: @capacity, tokens: @tokens, rejected: @rejected } end end |