Class: SplitIoClient::Engine::ReconnectPolicy

Inherits:
Object
  • Object
show all
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

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

#acquireObject

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.

Returns:

  • (Boolean)


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

#connectedObject

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_errorsObject

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

#intervalObject



73
74
75
# File 'lib/splitclient-rb/engine/reconnect_policy.rb', line 73

def interval
  @back_off.interval
end

#record_disconnectObject

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

#releaseObject



56
57
58
# File 'lib/splitclient-rb/engine/reconnect_policy.rb', line 56

def release
  @reconnecting.make_false
end