@@ -224,49 +224,87 @@ class OAuth2Session final : public AuthSession,
224224 auto next_backoff =
225225 std::min (std::chrono::duration_cast<std::chrono::milliseconds>(backoff * 2 ),
226226 kMaxBackoff );
227+ auto generation = BeginScheduledTask ();
227228 std::weak_ptr<OAuth2Session> weak_self = shared_from_this ();
228229 auto weak_for_rejection = weak_self;
229230 auto retry_task = config_.scheduler ->Schedule (
230231 backoff,
231- ExecutorTask ([weak_self = std::move (weak_self), next_attempt = attempt + 1 ,
232- next_backoff, refresh_started_at] {
232+ ExecutorTask ([weak_self = std::move (weak_self), generation ,
233+ next_attempt = attempt + 1 , next_backoff, refresh_started_at] {
233234 if (auto self = weak_self.lock ()) {
235+ self->ScheduledTaskStarted (generation);
234236 self->DoRefreshAttempt (next_attempt, next_backoff, refresh_started_at);
235237 }
236238 }),
237239 ::iceberg::internal::ScheduledTaskRejection (
238- [weak_self = std::move(weak_for_rejection)](Status) {
239- if (auto self = weak_self.lock ()) self->ScheduleRefresh ();
240+ [weak_self = std::move(weak_for_rejection), generation](Status) {
241+ if (auto self = weak_self.lock ()) {
242+ self->ScheduledTaskRejected (generation, 0 );
243+ }
240244 }));
241- StoreScheduledTask (std::move (retry_task));
245+ StoreScheduledTask (std::move (retry_task), generation );
242246 }
243247 }
244248
245- void ScheduleRefresh () {
249+ void ScheduleRefresh () { ScheduleRefreshAfter (std::nullopt , 0 ); }
250+
251+ void ScheduleRefreshAfter (std::optional<std::chrono::milliseconds> override_delay,
252+ int dispatch_attempt) {
246253 if (!config_.keep_refreshed || config_.scheduler == nullptr || closed_.load ()) return ;
247- auto delay = CalculateRefreshDelay ();
254+ auto delay = override_delay. value_or ( CalculateRefreshDelay () );
248255 if (delay < std::chrono::milliseconds::zero ()) return ;
249256
257+ auto generation = BeginScheduledTask ();
250258 std::weak_ptr<OAuth2Session> weak_self = shared_from_this ();
251259 auto weak_for_rejection = weak_self;
252260 auto task = config_.scheduler ->Schedule (
253- delay, ExecutorTask ([weak_self = std::move (weak_self)] {
254- if (auto self = weak_self.lock ()) self->DoRefresh ();
261+ delay, ExecutorTask ([weak_self = std::move (weak_self), generation] {
262+ if (auto self = weak_self.lock ()) {
263+ self->ScheduledTaskStarted (generation);
264+ self->DoRefresh ();
265+ }
255266 }),
256267 ::iceberg::internal::ScheduledTaskRejection (
257- [weak_self = std::move(weak_for_rejection)](Status) {
258- if (auto self = weak_self.lock ()) self->ScheduleRefresh ();
268+ [weak_self = std::move(weak_for_rejection), generation,
269+ dispatch_attempt](Status) {
270+ if (auto self = weak_self.lock ()) {
271+ self->ScheduledTaskRejected (generation, dispatch_attempt);
272+ }
259273 }));
260- StoreScheduledTask (std::move (task));
274+ StoreScheduledTask (std::move (task), generation );
261275 }
262276
263- void StoreScheduledTask (::iceberg::internal::ScheduledTask task ) {
277+ uint64_t BeginScheduledTask ( ) {
264278 std::lock_guard lock (scheduled_mutex_);
265- if (closed_.load ()) {
279+ scheduled_task_.Cancel ();
280+ return ++schedule_generation_;
281+ }
282+
283+ void ScheduledTaskStarted (uint64_t generation) {
284+ std::lock_guard lock (scheduled_mutex_);
285+ if (generation == schedule_generation_) {
286+ ++schedule_generation_;
287+ scheduled_task_.Cancel ();
288+ }
289+ }
290+
291+ void ScheduledTaskRejected (uint64_t generation, int dispatch_attempt) {
292+ ScheduledTaskStarted (generation);
293+ static constexpr int kMaxDispatchRetries = 5 ;
294+ static constexpr auto kInitialDispatchBackoff = std::chrono::milliseconds (200 );
295+ if (dispatch_attempt >= kMaxDispatchRetries || closed_.load ()) return ;
296+ auto multiplier = 1 << dispatch_attempt;
297+ auto delay =
298+ std::min (kInitialDispatchBackoff * multiplier, std::chrono::milliseconds (10'000 ));
299+ ScheduleRefreshAfter (delay, dispatch_attempt + 1 );
300+ }
301+
302+ void StoreScheduledTask (::iceberg::internal::ScheduledTask task, uint64_t generation) {
303+ std::lock_guard lock (scheduled_mutex_);
304+ if (closed_.load () || generation != schedule_generation_) {
266305 task.Cancel ();
267306 return ;
268307 }
269- scheduled_task_.Cancel ();
270308 scheduled_task_ = std::move (task);
271309 }
272310
@@ -294,6 +332,7 @@ class OAuth2Session final : public AuthSession,
294332 std::shared_ptr<HttpClient> client_;
295333 ::iceberg::internal::ScheduledTask scheduled_task_;
296334 std::mutex scheduled_mutex_;
335+ uint64_t schedule_generation_ = 0 ;
297336 std::atomic<bool > closed_{false };
298337 std::mutex refresh_mutex_;
299338 std::condition_variable refresh_cv_;
0 commit comments