fix(sector-radar): 支持当前上市股票资金流补拉
This commit is contained in:
@@ -33,11 +33,12 @@ class TushareSourceError(RuntimeError):
|
||||
|
||||
|
||||
class RequestCoordinator:
|
||||
"""Coordinate retries and shared rate-limit cooling for one provider client.
|
||||
"""Coordinate retries, rate-limit cooling, and optional request start spacing.
|
||||
|
||||
Normal requests are not serialized. Only a classified provider limit creates
|
||||
a shared cooldown. Injectable time functions keep long cooldowns deterministic
|
||||
in tests without coupling the coordinator to any business bounded context.
|
||||
Provider calls execute outside the coordinator lock and may overlap. When a
|
||||
positive request interval is configured, only their start times are serialized.
|
||||
Injectable time functions keep waits deterministic in tests without coupling
|
||||
the coordinator to any business bounded context.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -45,6 +46,7 @@ class RequestCoordinator:
|
||||
*,
|
||||
max_retries: int = 3,
|
||||
backoff_seconds: float = 1.0,
|
||||
request_interval_seconds: float = 0.0,
|
||||
cooldown_seconds: Sequence[float] = DEFAULT_RATE_LIMIT_COOLDOWNS,
|
||||
random_fn: Callable[[], float] = random.random,
|
||||
clock: Callable[[], float] = time.monotonic,
|
||||
@@ -56,6 +58,7 @@ class RequestCoordinator:
|
||||
raise ValueError("cooldown_seconds must contain non-negative values")
|
||||
self.max_retries = max(0, max_retries)
|
||||
self.backoff_seconds = max(0.0, backoff_seconds)
|
||||
self.request_interval_seconds = max(0.0, request_interval_seconds)
|
||||
self.cooldown_seconds = cooldowns
|
||||
self.random_fn = random_fn
|
||||
self.clock = clock
|
||||
@@ -63,6 +66,7 @@ class RequestCoordinator:
|
||||
self.sleep_fn = sleep_fn or wait_fn
|
||||
self._condition = threading.Condition()
|
||||
self._cooldown_until = 0.0
|
||||
self._next_request_start = 0.0
|
||||
self._rate_limit_count = 0
|
||||
|
||||
@property
|
||||
@@ -77,7 +81,7 @@ class RequestCoordinator:
|
||||
|
||||
last_error: BaseException | None = None
|
||||
for attempt in range(self.max_retries + 1):
|
||||
self._wait_for_cooldown(method_name)
|
||||
self._wait_for_request_start(method_name)
|
||||
try:
|
||||
result = request()
|
||||
except Exception as exc:
|
||||
@@ -124,17 +128,29 @@ class RequestCoordinator:
|
||||
|
||||
return self.call(method_name, operation)
|
||||
|
||||
def _wait_for_cooldown(self, method_name: str) -> None:
|
||||
def _wait_for_request_start(self, method_name: str) -> None:
|
||||
"""Reserve one start slot after both shared wait deadlines have elapsed."""
|
||||
|
||||
while True:
|
||||
with self._condition:
|
||||
delay = self._cooldown_until - self.clock()
|
||||
if delay <= 0:
|
||||
return
|
||||
logger.info(
|
||||
"provider_rate_limit_wait method=%s wait_seconds=%.1f",
|
||||
method_name,
|
||||
delay,
|
||||
)
|
||||
now = self.clock()
|
||||
start_at = max(self._cooldown_until, self._next_request_start)
|
||||
delay = start_at - now
|
||||
if delay <= 0:
|
||||
self._next_request_start = now + self.request_interval_seconds
|
||||
return
|
||||
if start_at == self._cooldown_until:
|
||||
logger.info(
|
||||
"provider_rate_limit_wait method=%s wait_seconds=%.1f",
|
||||
method_name,
|
||||
delay,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"provider_request_interval_wait method=%s wait_seconds=%.3f",
|
||||
method_name,
|
||||
delay,
|
||||
)
|
||||
self.wait_fn(delay)
|
||||
|
||||
def _set_rate_limit_cooldown(self) -> float:
|
||||
|
||||
Reference in New Issue
Block a user