wgtmac commented on code in PR #1033:
URL: https://github.com/apache/iceberg-cpp/pull/1033#discussion_r4350325670


##########
src/iceberg/catalog/rest/auth/auth_session_internal.h:
##########
@@ -182,73 +200,89 @@ class OAuth2Session final : public AuthSession,
       const OAuthTokenResponse& token_response,
       std::optional<std::chrono::steady_clock::time_point> 
token_request_started_at) {
     UpdateTokenState(token_response, token_request_started_at);
-    if (config_.keep_refreshed &&
-        expires_at_ != std::chrono::steady_clock::time_point{}) {
-      ScheduleRefresh();
+    if (!scheduler_) {
+      return;
+    }
+    // Refresh an already-expired initial token immediately.
+    if (auto delay = CalculateRefreshDelay()) {
+      ScheduleRefresh(*delay);
     }
   }
 
-  void DoRefresh() {
-    DoRefreshAttempt(0, std::chrono::milliseconds(200), 
std::chrono::steady_clock::now());
-  }
-
-  void DoRefreshAttempt(int attempt, std::chrono::milliseconds backoff,
-                        std::chrono::steady_clock::time_point 
refresh_started_at) {
-    static constexpr int kMaxRetries = 5;
-    static constexpr auto kMaxBackoff = std::chrono::milliseconds(10'000);
+  // Complete one refresh, including HTTP retries, on the executor's worker.
+  std::optional<std::chrono::milliseconds> Refresh() {
     RefreshAttemptGuard guard(*this);
-    if (closed_.load()) return;
+    if (!guard.IsActive()) {
+      return std::nullopt;
+    }
+
+    auto result = RetryRunner<retry::StopRetryOn<>>({.num_retries = 
5}).Run([this] {
+      return RefreshToken();
+    });
+    if (!result.has_value()) {
+      ICEBERG_LOG_WARN("Failed to refresh OAuth2 token: {}", 
result.error().message);
+      return std::nullopt;
+    }
+    return *result;
+  }
 
-    auto empty_session = AuthSession::MakeDefault({});
-    // TODO(lishuxu): Honor token-exchange-enabled and refresh via token 
exchange,
-    // matching Java.
-    auto result = OAuth2Util::FetchToken(*client_, *empty_session, 
refresh_properties_);
-    if (result.has_value()) {
-      auto& response = result.value();
+  Result<std::optional<std::chrono::milliseconds>> RefreshToken() {
+    {
+      std::lock_guard lock(refresh_mutex_);
+      if (closed_) {
+        return std::nullopt;
+      }
+    }
+    try {
+      auto request_started_at = std::chrono::steady_clock::now();
+      auto empty_session = AuthSession::MakeDefault({});
+      // TODO(lishuxu): Honor token-exchange-enabled when refreshing tokens.
+      ICEBERG_ASSIGN_OR_RAISE(
+          auto response,
+          OAuth2Util::FetchToken(*client_, *empty_session, 
refresh_properties_));
       {
         std::unique_lock lock(mutex_);
-        UpdateTokenState(response, refresh_started_at);
+        UpdateTokenState(response, request_started_at);
       }
-      ScheduleRefresh();
-      return;
-    }
-
-    if (attempt + 1 < kMaxRetries && !closed_.load()) {
-      auto next_backoff =
-          
std::min(std::chrono::duration_cast<std::chrono::milliseconds>(backoff * 2),
-                   kMaxBackoff);
-      std::weak_ptr<OAuth2Session> weak_self = shared_from_this();
-      auto retry_id = TokenRefreshScheduler::Instance().Schedule(
-          backoff, [weak_self = std::move(weak_self), next_attempt = attempt + 
1,
-                    next_backoff, refresh_started_at] {
-            if (auto self = weak_self.lock()) {
-              self->DoRefreshAttempt(next_attempt, next_backoff, 
refresh_started_at);
-            }
-          });
-      scheduled_task_id_.store(retry_id);
+      auto delay = CalculateRefreshDelay();
+      if (delay.has_value() && *delay <= std::chrono::milliseconds::zero()) {
+        return TokenExpired("Refreshed OAuth2 token is already expired");
+      }
+      return delay;
+    } catch (const std::exception& e) {
+      return UnknownError("OAuth2 token refresh threw: {}", e.what());
+    } catch (...) {
+      return UnknownError("OAuth2 token refresh threw a non-standard 
exception");
     }
   }
 
-  void ScheduleRefresh() {
-    if (!config_.keep_refreshed || closed_.load()) return;
-    auto delay = CalculateRefreshDelay();
-    if (delay < std::chrono::milliseconds::zero()) return;
-
-    std::weak_ptr<OAuth2Session> weak_self = shared_from_this();
-    auto new_id = TokenRefreshScheduler::Instance().Schedule(
-        delay, [weak_self = std::move(weak_self)] {
-          if (auto self = weak_self.lock()) self->DoRefresh();
-        });
-    scheduled_task_id_.store(new_id);
+  void ScheduleRefresh(std::chrono::milliseconds delay) {
+    ExecutorTask refresh([weak_self = weak_from_this()] {
+      if (auto self = weak_self.lock()) {
+        if (auto next_delay = self->Refresh()) {
+          self->ScheduleRefresh(*next_delay);
+        }
+      }
+    });
+    ::iceberg::internal::TaskSubmissionCallback on_submission([](Status 
status) {

Review Comment:
   Thanks for catching this. You are right: with a caller-provided bounded 
executor, logging a submission failure would permanently stop the refresh 
chain. I restored bounded submission retries independently from HTTP retries: 
the initial submission plus five retries with 200 ms exponential backoff 
(200/400/800/1600/3200 ms). The HTTP retry budget remains in RetryRunner and is 
only consumed after a task is accepted. After the submission budget is 
exhausted, we log and stop; session shutdown still cancels pending retry tasks. 
Implemented in 0317a526c.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to