Skip to content

Commit 4e26134

Browse files
committed
fix(auth): harden scheduled refresh lifecycle
1 parent f00c3b7 commit 4e26134

3 files changed

Lines changed: 142 additions & 56 deletions

File tree

‎src/iceberg/catalog/rest/auth/auth_session_internal.h‎

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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_;

‎src/iceberg/util/scheduled_executor.cc‎

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,29 @@
1717
* under the License.
1818
*/
1919

20-
#include <optional>
2120
#include <utility>
2221

2322
#include "iceberg/util/scheduled_executor_internal.h"
2423

2524
namespace iceberg::internal {
2625

2726
void ScheduledTask::Cancel() {
28-
if (valid()) {
29-
executor_->Cancel(id_);
30-
executor_ = nullptr;
31-
id_ = 0;
32-
}
27+
if (auto state = state_.lock()) state->Cancel(id_);
28+
state_.reset();
29+
id_ = 0;
3330
}
3431

35-
ScheduledExecutor::ScheduledExecutor(Executor& executor)
36-
: executor_(executor), timer_thread_([this] { Run(); }) {}
32+
ScheduledExecutorState::ScheduledExecutorState(Executor& executor)
33+
: executor_(executor) {}
3734

38-
ScheduledExecutor::~ScheduledExecutor() { Shutdown(); }
35+
ScheduledExecutorState::~ScheduledExecutorState() { Shutdown(); }
3936

40-
ScheduledTask ScheduledExecutor::Schedule(
37+
void ScheduledExecutorState::Start() {
38+
auto self = shared_from_this();
39+
timer_thread_ = std::thread([self = std::move(self)] { self->Run(); });
40+
}
41+
42+
ScheduledTask ScheduledExecutorState::Schedule(
4143
std::chrono::steady_clock::time_point deadline, ExecutorTask task,
4244
std::optional<ScheduledTaskRejection> on_rejected) {
4345
std::lock_guard lock(mutex_);
@@ -49,17 +51,10 @@ ScheduledTask ScheduledExecutor::Schedule(
4951
.on_rejected = std::move(on_rejected)});
5052
scheduled_.insert(id);
5153
cv_.notify_one();
52-
return ScheduledTask(this, id);
53-
}
54-
55-
ScheduledTask ScheduledExecutor::Schedule(
56-
std::chrono::milliseconds delay, ExecutorTask task,
57-
std::optional<ScheduledTaskRejection> on_rejected) {
58-
return Schedule(std::chrono::steady_clock::now() + delay, std::move(task),
59-
std::move(on_rejected));
54+
return ScheduledTask(weak_from_this(), id);
6055
}
6156

62-
void ScheduledExecutor::Cancel(uint64_t id) {
57+
void ScheduledExecutorState::Cancel(uint64_t id) {
6358
if (id == 0) return;
6459
{
6560
std::lock_guard lock(mutex_);
@@ -68,7 +63,7 @@ void ScheduledExecutor::Cancel(uint64_t id) {
6863
cv_.notify_one();
6964
}
7065

71-
void ScheduledExecutor::Shutdown() {
66+
void ScheduledExecutorState::Shutdown() {
7267
{
7368
std::lock_guard lock(mutex_);
7469
if (shutdown_) return;
@@ -78,10 +73,15 @@ void ScheduledExecutor::Shutdown() {
7873
scheduled_.clear();
7974
}
8075
cv_.notify_one();
81-
if (timer_thread_.joinable()) timer_thread_.join();
76+
if (!timer_thread_.joinable()) return;
77+
if (timer_thread_.get_id() == std::this_thread::get_id()) {
78+
timer_thread_.detach();
79+
} else {
80+
timer_thread_.join();
81+
}
8282
}
8383

84-
void ScheduledExecutor::Run() {
84+
void ScheduledExecutorState::Run() {
8585
while (true) {
8686
std::optional<Task> task;
8787
{
@@ -108,11 +108,36 @@ void ScheduledExecutor::Run() {
108108
scheduled_.erase(task->id);
109109
if (cancelled_.erase(task->id) != 0) continue;
110110
}
111+
111112
auto status = executor_.Submit(std::move(task->task));
112113
if (!status.has_value() && task->on_rejected.has_value()) {
113114
std::move (*task->on_rejected)(std::move(status));
114115
}
115116
}
116117
}
117118

119+
ScheduledExecutor::ScheduledExecutor(Executor& executor)
120+
: state_(std::make_shared<ScheduledExecutorState>(executor)) {
121+
state_->Start();
122+
}
123+
124+
ScheduledExecutor::~ScheduledExecutor() { Shutdown(); }
125+
126+
ScheduledTask ScheduledExecutor::Schedule(
127+
std::chrono::steady_clock::time_point deadline, ExecutorTask task,
128+
std::optional<ScheduledTaskRejection> on_rejected) {
129+
return state_->Schedule(deadline, std::move(task), std::move(on_rejected));
130+
}
131+
132+
ScheduledTask ScheduledExecutor::Schedule(
133+
std::chrono::milliseconds delay, ExecutorTask task,
134+
std::optional<ScheduledTaskRejection> on_rejected) {
135+
return Schedule(std::chrono::steady_clock::now() + delay, std::move(task),
136+
std::move(on_rejected));
137+
}
138+
139+
void ScheduledExecutor::Shutdown() {
140+
if (state_) state_->Shutdown();
141+
}
142+
118143
} // namespace iceberg::internal

‎src/iceberg/util/scheduled_executor_internal.h‎

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <chrono>
2323
#include <condition_variable>
2424
#include <cstdint>
25+
#include <memory>
2526
#include <mutex>
2627
#include <optional>
2728
#include <queue>
@@ -35,45 +36,42 @@
3536

3637
namespace iceberg::internal {
3738

38-
class ScheduledExecutor;
39+
class ScheduledExecutorState;
3940

4041
using ScheduledTaskRejection = FnOnce<void(Status)>;
4142

4243
class ICEBERG_EXPORT ScheduledTask {
4344
public:
4445
ScheduledTask() = default;
45-
ScheduledTask(ScheduledExecutor* executor, uint64_t id)
46-
: executor_(executor), id_(id) {}
46+
ScheduledTask(std::weak_ptr<ScheduledExecutorState> state, uint64_t id)
47+
: state_(std::move(state)), id_(id) {}
4748

4849
void Cancel();
49-
bool valid() const { return executor_ != nullptr && id_ != 0; }
50+
bool valid() const { return id_ != 0 && !state_.expired(); }
5051

5152
private:
52-
ScheduledExecutor* executor_ = nullptr;
53+
std::weak_ptr<ScheduledExecutorState> state_;
5354
uint64_t id_ = 0;
5455
};
5556

56-
/// A delay queue that dispatches due tasks to an existing Executor.
57-
///
58-
/// The executor is borrowed and must outlive this object and all scheduled tasks.
59-
class ICEBERG_EXPORT ScheduledExecutor {
57+
class ICEBERG_EXPORT ScheduledExecutorState
58+
: public std::enable_shared_from_this<ScheduledExecutorState> {
6059
public:
61-
explicit ScheduledExecutor(Executor& executor);
62-
~ScheduledExecutor();
60+
explicit ScheduledExecutorState(Executor& executor);
61+
~ScheduledExecutorState();
6362

64-
ScheduledExecutor(const ScheduledExecutor&) = delete;
65-
ScheduledExecutor& operator=(const ScheduledExecutor&) = delete;
63+
ScheduledExecutorState(const ScheduledExecutorState&) = delete;
64+
ScheduledExecutorState& operator=(const ScheduledExecutorState&) = delete;
6665

67-
ScheduledTask Schedule(
68-
std::chrono::steady_clock::time_point deadline, ExecutorTask task,
69-
std::optional<ScheduledTaskRejection> on_rejected = std::nullopt);
70-
ScheduledTask Schedule(
71-
std::chrono::milliseconds delay, ExecutorTask task,
72-
std::optional<ScheduledTaskRejection> on_rejected = std::nullopt);
66+
ScheduledTask Schedule(std::chrono::steady_clock::time_point deadline,
67+
ExecutorTask task,
68+
std::optional<ScheduledTaskRejection> on_rejected);
7369
void Cancel(uint64_t id);
7470
void Shutdown();
7571

7672
private:
73+
friend class ScheduledExecutor;
74+
7775
struct Task {
7876
uint64_t id;
7977
std::chrono::steady_clock::time_point deadline;
@@ -87,6 +85,7 @@ class ICEBERG_EXPORT ScheduledExecutor {
8785
}
8886
};
8987

88+
void Start();
9089
void Run();
9190

9291
Executor& executor_;
@@ -100,4 +99,27 @@ class ICEBERG_EXPORT ScheduledExecutor {
10099
std::thread timer_thread_;
101100
};
102101

102+
/// A delay queue that dispatches due tasks to an existing Executor.
103+
///
104+
/// The executor is borrowed and must outlive this object and all scheduled tasks.
105+
class ICEBERG_EXPORT ScheduledExecutor {
106+
public:
107+
explicit ScheduledExecutor(Executor& executor);
108+
~ScheduledExecutor();
109+
110+
ScheduledExecutor(const ScheduledExecutor&) = delete;
111+
ScheduledExecutor& operator=(const ScheduledExecutor&) = delete;
112+
113+
ScheduledTask Schedule(
114+
std::chrono::steady_clock::time_point deadline, ExecutorTask task,
115+
std::optional<ScheduledTaskRejection> on_rejected = std::nullopt);
116+
ScheduledTask Schedule(
117+
std::chrono::milliseconds delay, ExecutorTask task,
118+
std::optional<ScheduledTaskRejection> on_rejected = std::nullopt);
119+
void Shutdown();
120+
121+
private:
122+
std::shared_ptr<ScheduledExecutorState> state_;
123+
};
124+
103125
} // namespace iceberg::internal

0 commit comments

Comments
 (0)