| 229 | } |
| 230 | |
| 231 | HttpResp CloudProviderBase::ApiRequest(const char* method, const std::string& path, |
| 232 | const std::string& body, |
| 233 | const std::string& contentType) { |
| 234 | // Exponential backoff with jitter: base 1s, factor 2x, up to 5 attempts. |
| 235 | // Retries on rate-limit (429 / 403+rateLimitExceeded) AND timeout (HTTP 0). |
| 236 | static constexpr int kMaxAttempts = 5; |
| 237 | static thread_local std::mt19937 rng{std::random_device{}()}; |
| 238 | |
| 239 | HttpResp lastResp; |
| 240 | for (int attempt = 0; attempt < kMaxAttempts; ++attempt) { |
| 241 | if (attempt > 0) { |
| 242 | int baseMs = 1000 * (1 << (attempt - 1)); // 1s, 2s, 4s, 8s |
| 243 | int jitter = std::uniform_int_distribution<int>(0, baseMs / 2)(rng); |
| 244 | int delayMs = baseMs + jitter; |
| 245 | LOG("%s Backoff %s %s: attempt %d, waiting %dms", |
| 246 | LogTag(), method, path.c_str(), attempt + 1, delayMs); |
| 247 | std::this_thread::sleep_for(std::chrono::milliseconds(delayMs)); |
| 248 | } |
| 249 | auto token = GetAccessToken(); |
| 250 | if (token.empty()) { |
| 251 | LOG("%s ApiRequest: no access token for %s %s", LogTag(), method, path.c_str()); |
| 252 | return {}; |
| 253 | } |
| 254 | ThrottleApiCall(); |
| 255 | std::vector<std::string> hdrs = {"Authorization: Bearer " + token}; |
| 256 | if (!contentType.empty()) |
| 257 | hdrs.push_back("Content-Type: " + contentType); |
| 258 | lastResp = Request(method, ApiHost(), path, body, hdrs); |
| 259 | bool rateLimited = IsRateLimited(lastResp.status, lastResp.body); |
| 260 | bool timedOut = (lastResp.status == 0); |
| 261 | if (!rateLimited && !timedOut) return lastResp; |
| 262 | if (rateLimited) |
| 263 | g_rateLimitHits.fetch_add(1, std::memory_order_relaxed); |
| 264 | LOG("%s %s (%s attempt %d, HTTP %d), retrying", |
| 265 | LogTag(), rateLimited ? "Rate limited" : "Timeout", |
| 266 | method, attempt + 1, lastResp.status); |
| 267 | } |
| 268 | LOG("%s Retries exhausted for %s %s (HTTP %d)", LogTag(), method, path.c_str(), lastResp.status); |
| 269 | return lastResp; |
| 270 | } |
| 271 | |
| 272 | // ── Init / Shutdown / IsAuthenticated ────────────────────────────────────── |
| 273 |
nothing calls this directly
no outgoing calls
no test coverage detected