Class: SplitIoClient::Engine::ReconnectPolicy
- Inherits:
-
Object
- Object
- SplitIoClient::Engine::ReconnectPolicy
- Defined in:
- lib/splitclient-rb/engine/reconnect_policy.rb
Overview
Governs how aggressively streaming reconnects are attempted.
Extracted for FME-18143, where a customer's SDK reconnected up to ~64 times per second and accumulated ~10,000 threads. Two properties matter here:
* A connection only earns a back off reset if it stayed up long enough to count
as a genuine success. Resetting on every PUSH_CONNECTED meant a connection
that lived for milliseconds still produced a zero second retry.
* Only one reconnect may be in flight at a time, and errors describing an
already-replaced connection must not each start another one.
Constant Summary collapse
- MIN_UPTIME_FOR_BACKOFF_RESET =
How long a connection must survive before it counts as stable.
30
Instance Method Summary collapse
-
#acquire ⇒ Object
Returns true only for the caller that acquired the right to reconnect; a concurrent caller gets false.
-
#actionable?(sse_connected, reconnect) ⇒ Boolean
Whether a disconnect should be acted on at all.
-
#connected ⇒ Object
Monotonic clock: a backwards wall-clock jump (NTP correction, container migration) must not make uptime go negative and permanently block the back off reset.
-
#discard_stale_errors ⇒ Object
Drops queued retryable errors, which refer to the connection just replaced.
-
#initialize(config, status_queue, back_off = nil) ⇒ ReconnectPolicy
constructor
A new instance of ReconnectPolicy.
- #interval ⇒ Object
-
#record_disconnect ⇒ Object
Resets the back off only if the connection that just dropped was stable, so a flapping connection keeps backing off instead of retrying immediately.
- #release ⇒ Object
Constructor Details
#initialize(config, status_queue, back_off = nil) ⇒ ReconnectPolicy
Returns a new instance of ReconnectPolicy.
19 20 21 22 23 24 25 |
# File 'lib/splitclient-rb/engine/reconnect_policy.rb', line 19 def initialize(config, status_queue, back_off = nil) @config = config @status_queue = status_queue @back_off = back_off || Engine::BackOff.new(1, 3) @connected_at = Concurrent::AtomicReference.new(nil) @reconnecting = Concurrent::AtomicBoolean.new(false) end |
Instance Method Details
#acquire ⇒ Object
Returns true only for the caller that acquired the right to reconnect; a concurrent caller gets false. AtomicBoolean#make_true is the compare-and-set: it returns true only for the transition from false to true.
52 53 54 |
# File 'lib/splitclient-rb/engine/reconnect_policy.rb', line 52 def acquire @reconnecting.make_true end |
#actionable?(sse_connected, reconnect) ⇒ Boolean
Whether a disconnect should be acted on at all. Returns false for a disconnect of an already-disconnected stream, and for a reconnect when one is already in flight (typically driven by the token refresh thread) -- running two concurrently is what multiplied connection attempts.
38 39 40 41 42 43 44 45 46 47 |
# File 'lib/splitclient-rb/engine/reconnect_policy.rb', line 38 def actionable?(sse_connected, reconnect) unless sse_connected || reconnect log_debug('Streaming already disconnected.') return false end return true unless reconnect && !acquire log_debug('Streaming reconnect already in progress, ignoring.') false end |
#connected ⇒ Object
Monotonic clock: a backwards wall-clock jump (NTP correction, container migration) must not make uptime go negative and permanently block the back off reset.
30 31 32 |
# File 'lib/splitclient-rb/engine/reconnect_policy.rb', line 30 def connected @connected_at.set(monotonic_now) end |
#discard_stale_errors ⇒ Object
Drops queued retryable errors, which refer to the connection just replaced. Acting on each of them would start one more reconnect apiece. Other statuses are preserved in order.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'lib/splitclient-rb/engine/reconnect_policy.rb', line 80 def discard_stale_errors kept = [] discarded = 0 until @status_queue.empty? status = @status_queue.pop(true) if status == Constants::PUSH_RETRYABLE_ERROR discarded += 1 else kept << status end end kept.each { |kept_status| @status_queue.push(kept_status) } log_debug("Discarded #{discarded} stale streaming errors.") if discarded.positive? discarded rescue ThreadError discarded end |
#interval ⇒ Object
73 74 75 |
# File 'lib/splitclient-rb/engine/reconnect_policy.rb', line 73 def interval @back_off.interval end |
#record_disconnect ⇒ Object
Resets the back off only if the connection that just dropped was stable, so a flapping connection keeps backing off instead of retrying immediately.
62 63 64 65 66 67 68 69 70 71 |
# File 'lib/splitclient-rb/engine/reconnect_policy.rb', line 62 def record_disconnect connected_at = @connected_at.get_and_set(nil) return if connected_at.nil? uptime = monotonic_now - connected_at return if uptime < MIN_UPTIME_FOR_BACKOFF_RESET log_debug("Streaming connection was up for #{uptime.round(1)}s, resetting back off.") @back_off.reset end |
#release ⇒ Object
56 57 58 |
# File 'lib/splitclient-rb/engine/reconnect_policy.rb', line 56 def release @reconnecting.make_false end |