1818
1919# currently excluded from documentation - see docs/README.md
2020
21+ import math
2122import random
2223import time
2324from enum import Enum
2728 DEFAULT_INITIAL_RECONNECT_DELAY ,
2829 DEFAULT_POLL_INTERVAL
2930)
30- from ldclient .impl .util import log , validate_positive_finite
31+ from ldclient .impl .util import log
3132
3233# The delay ceiling of the normal regime for streaming, in seconds.
3334NORMAL_STREAMING_CEILING_DELAY = 30
5354_MAX_BACKOFF_EXPONENT = 30
5455
5556
57+ def _usable_delay (value : float , default : float , name : str , ceiling : float = math .inf ) -> float :
58+ """
59+ Returns the delay to use, clamped to the ceiling. A value that is
60+ not a positive, finite number of seconds is replaced by the default.
61+
62+ :param value: the configured number of seconds
63+ :param default: the value to use when ``value`` is not usable
64+ :param name: the option name, for the warning message
65+ :param ceiling: the longest delay allowed
66+ """
67+
68+ if value > 0 and math .isfinite (value ):
69+ return min (value , ceiling )
70+ log .warning ("%s must be a positive, finite number of seconds; using the default of %ss" % (name , default ))
71+ return default
72+
73+
5674class FailureKind (Enum ):
5775 """How a failure is classified, which decides how long the next wait is."""
5876
@@ -257,13 +275,14 @@ def for_streaming(initial_reconnect_delay: float) -> RetryState:
257275 healthy operation after establishing a successful connection with no
258276 failures during the ``STREAMING_RESET_INTERVAL``.
259277
260- ``Config`` validates the configured delay, so this guard only catches a
261- state built without it.
262-
263- The extended regime never starts below the configured delay.
278+ The configured delay is validated here, because ``Config`` reports it as
279+ given. It is not clamped to either ceiling: a delay that applies after an
280+ unexpected failure may never be shorter than the configured delay, so a
281+ configured delay longer than a ceiling raises the bound rather than being
282+ cut down to it.
264283 """
265- initial_reconnect_delay = validate_positive_finite (
266- initial_reconnect_delay , DEFAULT_INITIAL_RECONNECT_DELAY , 'initial_reconnect_delay' , log
284+ initial_reconnect_delay = _usable_delay (
285+ initial_reconnect_delay , DEFAULT_INITIAL_RECONNECT_DELAY , 'initial_reconnect_delay'
267286 )
268287 return RetryState (
269288 normal_initial_delay = initial_reconnect_delay ,
@@ -285,10 +304,14 @@ def for_polling(poll_interval: float) -> RetryState:
285304 schedule. Polling is healthy on any successful poll, and resets after two
286305 in a row.
287306
288- ``Config`` validates and clamps the poll interval, so this guard only
289- catches a state built without it.
307+ The poll interval is validated here, because ``Config`` reports it as
308+ given. It is not clamped to a ceiling, unlike the streaming delay. The
309+ normal ceiling is the poll interval itself, so a clamp would do nothing,
310+ and the extended regime has to keep the cadence even where that is longer
311+ than an hour: a wait may never be shorter than the interval the caller
312+ configured, so the cadence wins over the extended ceiling.
290313 """
291- poll_interval = validate_positive_finite (poll_interval , DEFAULT_POLL_INTERVAL , 'poll_interval' , log )
314+ poll_interval = _usable_delay (poll_interval , DEFAULT_POLL_INTERVAL , 'poll_interval' )
292315 return RetryState (
293316 normal_initial_delay = poll_interval ,
294317 normal_ceiling_delay = poll_interval ,
0 commit comments