|
| 1 | +import random |
1 | 2 | import time |
2 | 3 | import logging |
3 | 4 | from abc import ABC |
@@ -186,6 +187,85 @@ def _init_flag(self, model_name: str) -> str: |
186 | 187 | """ |
187 | 188 | return f"{self._get_redis_key(model_name)}:initialized" |
188 | 189 |
|
| 190 | + # ---------------------------------------------------------------------- |
| 191 | + # Helper methods |
| 192 | + # ---------------------------------------------------------------------- |
| 193 | + def _try_acquire_random_provider( |
| 194 | + self, redis_key: str, providers: List[Dict] |
| 195 | + ) -> Optional[Dict]: |
| 196 | + """ |
| 197 | + Attempt to lock a provider chosen at random. |
| 198 | +
|
| 199 | + The method works in three stages: |
| 200 | +
|
| 201 | + 1. **Shuffle** – a shallow copy of ``providers`` is shuffled so that each |
| 202 | + provider has an equal probability of being tried first. The original |
| 203 | + list is left untouched. |
| 204 | + 2. **Atomic acquisition** – each shuffled provider is passed to the |
| 205 | + ``_acquire_script`` Lua script which atomically sets the corresponding |
| 206 | + Redis hash field to ``'true'`` *only if* it is currently ``'false'`` or |
| 207 | + missing. The first provider for which the script returns ``1`` is |
| 208 | + considered successfully acquired. |
| 209 | + 3. **Fallback** – if none of the providers can be locked (e.g., all are |
| 210 | + currently in use), the method falls back to the *first* provider in the |
| 211 | + original ``providers`` list, marks its ``"__chosen_field"`` for |
| 212 | + consistency, and returns it. This fallback mirrors the behaviour of |
| 213 | + the non‑random acquisition path and ensures the caller always receives |
| 214 | + a provider dictionary (or ``None`` when ``providers`` is empty). |
| 215 | +
|
| 216 | + Parameters |
| 217 | + ---------- |
| 218 | + redis_key : str |
| 219 | + The Redis hash key associated with the model (e.g., ``model:<name>``). |
| 220 | + providers : List[Dict] |
| 221 | + A list of provider configuration dictionaries. Each dictionary must |
| 222 | + contain sufficient information for :meth:`_provider_field` to generate |
| 223 | + a unique field name within the Redis hash. |
| 224 | +
|
| 225 | + Returns |
| 226 | + ------- |
| 227 | + Optional[Dict] |
| 228 | + The selected provider dictionary with an additional ``"__chosen_field"`` |
| 229 | + entry indicating the Redis hash field that was locked. Returns ``None`` |
| 230 | + only when the input ``providers`` list is empty. |
| 231 | +
|
| 232 | + Raises |
| 233 | + ------ |
| 234 | + Exception |
| 235 | + Propagates any unexpected exceptions raised by the Lua script execution; |
| 236 | + callers may catch these to implement retry or logging logic. |
| 237 | +
|
| 238 | + Notes |
| 239 | + ----- |
| 240 | + * The random selection is *non‑deterministic* on each call; however, the |
| 241 | + fallback to the first provider ensures deterministic behaviour when |
| 242 | + all providers are currently busy. |
| 243 | + * The method does **not** block; it returns immediately after trying all |
| 244 | + shuffled providers. |
| 245 | + """ |
| 246 | + shuffled = providers[:] |
| 247 | + random.shuffle(shuffled) |
| 248 | + for provider in shuffled: |
| 249 | + provider_field = self._provider_field(provider) |
| 250 | + try: |
| 251 | + ok = int( |
| 252 | + self._acquire_script(keys=[redis_key], args=[provider_field]) |
| 253 | + ) |
| 254 | + if ok == 1: |
| 255 | + provider["__chosen_field"] = provider_field |
| 256 | + return provider |
| 257 | + except Exception: |
| 258 | + continue |
| 259 | + return None |
| 260 | + |
| 261 | + def _get_active_providers( |
| 262 | + self, model_name: str, providers: List[Dict] |
| 263 | + ) -> List[Dict]: |
| 264 | + active_providers = self._monitor.get_providers( |
| 265 | + model_name=model_name, only_active=True |
| 266 | + ) |
| 267 | + return active_providers |
| 268 | + |
189 | 269 | def _initialize_providers(self, model_name: str, providers: List[Dict]) -> None: |
190 | 270 | """ |
191 | 271 | Ensure that the provider lock fields for *model_name* exist in Redis. |
@@ -282,6 +362,6 @@ def _print_provider_status(self, redis_key: str, providers: List[Dict]) -> None: |
282 | 362 | status = hash_data.get(field, "false") |
283 | 363 | icon = "🔴" if status == "true" else "🟢" |
284 | 364 | # Show a short identifier for the provider (fallback to field) |
285 | | - provider_id = provider.get("id") or provider.get("name") or field |
| 365 | + provider_id = provider.get("id") or provider.get("api_host") or field |
286 | 366 | print(f"{icon} {provider_id:<30} [{field}]") |
287 | 367 | print("-" * 40) |
0 commit comments