Skip to content

Commit d4b22c8

Browse files
committed
Optimized next_fire_us to uint32 and fixed bug with simultaneous tasks ticking
1 parent 978b282 commit d4b22c8

3 files changed

Lines changed: 187 additions & 23 deletions

File tree

Inc/HALAL/Services/Time/Scheduler.hpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ struct Scheduler {
3939

4040
static void start();
4141
static void update();
42-
static inline uint64_t get_global_tick();
42+
static inline uint64_t get_global_tick() {
43+
return global_tick_us_;
44+
}
4345

4446
static inline uint8_t register_task(uint32_t period_us, callback_t func) {
4547
if(period_us == 0) [[unlikely]] period_us = 1;
@@ -72,7 +74,7 @@ struct Scheduler {
7274
private:
7375
#endif
7476
struct Task {
75-
uint64_t next_fire_us{0};
77+
uint32_t next_fire_us{0};
7678
callback_t callback{};
7779
uint32_t period_us{0};
7880
bool repeating{false};
@@ -92,7 +94,7 @@ struct Scheduler {
9294
static uint32_t ready_bitmap_;
9395
static uint32_t free_bitmap_;
9496
static uint64_t global_tick_us_;
95-
static uint64_t current_interval_us_;
97+
static uint32_t current_interval_us_;
9698

9799
static inline uint8_t allocate_slot();
98100
static inline void release_slot(uint8_t id);

Src/HALAL/Services/Time/Scheduler.cpp

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
#define Scheduler_global_timer ((TIM_TypeDef*)SCHEDULER_TIMER_BASE)
2626
namespace {
2727
constexpr uint64_t kMaxIntervalUs =
28-
static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) + 1ULL;
28+
static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())/2 + 1ULL;
2929
}
3030

3131
std::array<Scheduler::Task, Scheduler::kMaxTasks> Scheduler::tasks_{};
@@ -35,7 +35,7 @@ uint32_t Scheduler::active_task_count_{0};
3535
uint32_t Scheduler::ready_bitmap_{0};
3636
uint32_t Scheduler::free_bitmap_{0xFFFF'FFFF};
3737
uint64_t Scheduler::global_tick_us_{0};
38-
uint64_t Scheduler::current_interval_us_{0};
38+
uint32_t Scheduler::current_interval_us_{0};
3939

4040
inline uint8_t Scheduler::get_at(uint8_t idx) {
4141
int word_idx = idx > 7;
@@ -160,8 +160,6 @@ void Scheduler::update() {
160160
}
161161
}
162162

163-
inline uint64_t Scheduler::get_global_tick() { return global_tick_us_; }
164-
165163
// void Scheduler::global_timer_callback() { on_timer_update(); }
166164

167165
inline uint8_t Scheduler::allocate_slot() {
@@ -186,7 +184,7 @@ void Scheduler::insert_sorted(uint8_t id) {
186184
while (left < right) {
187185
std::size_t mid = left + ((right - left) / 2);
188186
const Task& mid_task = tasks_[Scheduler::get_at(mid)];
189-
if (mid_task.next_fire_us <= task.next_fire_us) {
187+
if ((int32_t)(task.next_fire_us - mid_task.next_fire_us) >= 0) {
190188
left = mid + 1;
191189
} else {
192190
right = mid;
@@ -265,14 +263,14 @@ void Scheduler::schedule_next_interval() {
265263
Scheduler::global_timer_enable();
266264
uint8_t next_id = Scheduler::front_id(); // sorted_task_ids_[0]
267265
Task& next_task = tasks_[next_id];
268-
uint64_t delta = ((next_task.next_fire_us + 1ULL) > global_tick_us_)
269-
? (next_task.next_fire_us - global_tick_us_) : 1ULL;
270-
271-
if (delta > kMaxIntervalUs) [[unlikely]] {
272-
current_interval_us_ = kMaxIntervalUs;
266+
int32_t diff = (int32_t)(next_task.next_fire_us - static_cast<uint32_t>(global_tick_us_));
267+
uint32_t delta;
268+
if (diff <= 0) [[unlikely]]{
269+
delta = 1; // Task is due or overdue
273270
} else {
274-
current_interval_us_ = delta;
271+
delta = static_cast<uint32_t>(diff);
275272
}
273+
current_interval_us_ = delta;
276274

277275
configure_timer_for_interval(current_interval_us_);
278276
}
@@ -288,21 +286,28 @@ inline void Scheduler::configure_timer_for_interval(uint64_t microseconds) {
288286
void Scheduler::on_timer_update() {
289287
global_tick_us_ += current_interval_us_;
290288

291-
uint8_t candidate_id = Scheduler::front_id();
292-
Task& task = tasks_[candidate_id];
293-
pop_front();
294-
ready_bitmap_ |= (1u << candidate_id); // mark task as ready
289+
while (active_task_count_ > 0) { //Pop all due tasks, several might be due in the same tick
290+
uint8_t candidate_id = Scheduler::front_id();
291+
Task& task = tasks_[candidate_id];
292+
int32_t diff = (int32_t)(task.next_fire_us - static_cast<uint32_t>(global_tick_us_));
293+
if (diff > 0) [[likely]]{
294+
break; // Task is in the future, stop processing
295+
}
296+
pop_front();
297+
ready_bitmap_ |= (1u << candidate_id); // mark task as ready
295298

296-
if (task.repeating) [[likely]] {
297-
task.next_fire_us = global_tick_us_ + task.period_us;
298-
insert_sorted(candidate_id);
299+
if (task.repeating) [[likely]] {
300+
task.next_fire_us = static_cast<uint32_t>(global_tick_us_ + task.period_us);
301+
insert_sorted(candidate_id);
302+
}
299303
}
300304

301305
schedule_next_interval();
302306
}
303307

304308
uint8_t Scheduler::register_task(uint32_t period_us, callback_t func, bool repeating) {
305309
if (func == nullptr) [[unlikely]] return static_cast<uint8_t>(Scheduler::INVALID_ID);
310+
if(period_us >= kMaxIntervalUs) [[unlikely]] return static_cast<uint8_t>(Scheduler::INVALID_ID);
306311

307312
uint8_t slot = allocate_slot();
308313
if(slot == Scheduler::INVALID_ID) return slot;
@@ -311,8 +316,7 @@ uint8_t Scheduler::register_task(uint32_t period_us, callback_t func, bool repea
311316
task.callback = func;
312317
task.period_us = period_us;
313318
task.repeating = repeating;
314-
task.next_fire_us = global_tick_us_ + period_us;
315-
319+
task.next_fire_us = static_cast<uint32_t>(global_tick_us_ + period_us);
316320
insert_sorted(slot);
317321
schedule_next_interval();
318322
return slot;

Tests/Time/scheduler_test.cpp

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,161 @@ TEST_F(SchedulerTests, TaskDe_ReRegistration) {
162162
EXPECT_EQ(operational_execs, 2);
163163
EXPECT_EQ(fault_execs, 2);
164164
}
165+
166+
struct SelfValidatingTask {
167+
static uint64_t last_fire_tick;
168+
static uint32_t expected_period;
169+
static bool error_detected;
170+
static int execution_count;
171+
172+
static void reset(uint64_t start_tick, uint32_t period) {
173+
last_fire_tick = start_tick;
174+
expected_period = period;
175+
error_detected = false;
176+
execution_count = 0;
177+
}
178+
179+
static void callback() {
180+
uint64_t current_tick = Scheduler::get_global_tick();
181+
182+
uint32_t actual_interval = static_cast<uint32_t>(current_tick - last_fire_tick);
183+
184+
if (actual_interval != expected_period) {
185+
error_detected = true;
186+
printf("Timing Error Exp: %u, Got: %u at Tick: %lu\n", expected_period, actual_interval, current_tick);
187+
}
188+
printf("Task correct Exp: %u, Got: %u at Tick: %lu\n", expected_period, actual_interval, current_tick);
189+
190+
last_fire_tick = current_tick;
191+
execution_count++;
192+
}
193+
};
194+
195+
uint64_t SelfValidatingTask::last_fire_tick = 0;
196+
uint32_t SelfValidatingTask::expected_period = 0;
197+
bool SelfValidatingTask::error_detected = false;
198+
int SelfValidatingTask::execution_count = 0;
199+
200+
TEST_F(SchedulerTests, ExplicitOverflowBoundaryCrossing) {
201+
// Start close to the 32-bit limit
202+
// 0xFFFFFFF0 is 16 ticks away from 32-bit overflow
203+
uint64_t start_time = 0xFFFFFFF0ULL;
204+
Scheduler::global_tick_us_ = start_time;
205+
206+
// We expect the task to fire every 10 ticks
207+
SelfValidatingTask::reset(start_time, 10);
208+
209+
Scheduler::register_task(10, &SelfValidatingTask::callback);
210+
Scheduler::start();
211+
TIM2_BASE->PSC = 2;
212+
213+
// Start: FFFFFFF0
214+
// Fire 1: FFFFFFF0 + 10 = FFFFFFFA (OK)
215+
// Fire 2: FFFFFFFA + 10 = 00000004 (OVERFLOW CROSSING)
216+
// Fire 3: 00000004 + 10 = 0000000E (OK)
217+
// Fire 4: 0000000E + 10 = 00000018 (OK)
218+
constexpr int NUM_TICKS = 50;
219+
220+
for(int i = 0; i < NUM_TICKS; i++){
221+
for(int j = 0; j <= TIM2_BASE->PSC; j++) {
222+
TIM2_BASE->inc_cnt_and_check(1);
223+
}
224+
Scheduler::update();
225+
226+
if(SelfValidatingTask::error_detected) break;
227+
}
228+
229+
EXPECT_FALSE(SelfValidatingTask::error_detected);
230+
231+
EXPECT_EQ(SelfValidatingTask::execution_count, 5);
232+
233+
EXPECT_EQ(static_cast<uint32_t>(Scheduler::global_tick_us_), 0x00000022);
234+
}
235+
236+
struct DualTaskValidator {
237+
struct Context {
238+
uint64_t last_fire_tick;
239+
uint32_t expected_period;
240+
int exec_count;
241+
bool error;
242+
};
243+
244+
static Context ctx_A; // Task that runs BEFORE wrap
245+
static Context ctx_B; // Task that runs AFTER wrap
246+
247+
static void reset(uint64_t start_tick) {
248+
ctx_A = {start_tick, 0, 0, false};
249+
ctx_B = {start_tick, 0, 0, false};
250+
}
251+
252+
// Callback for the "Pre-Wrap" Task
253+
static void callback_A() {
254+
validate(ctx_A);
255+
}
256+
257+
// Callback for the "Post-Wrap" Task
258+
static void callback_B() {
259+
validate(ctx_B);
260+
}
261+
262+
static void validate(Context& ctx) {
263+
uint64_t current_tick = Scheduler::get_global_tick();
264+
uint32_t interval = static_cast<uint32_t>(current_tick - ctx.last_fire_tick);
265+
266+
if (interval != ctx.expected_period) {
267+
ctx.error = true;
268+
printf("Error Expected %u, got %u\n", ctx.expected_period, interval);
269+
}
270+
ctx.last_fire_tick = current_tick;
271+
ctx.exec_count++;
272+
}
273+
};
274+
275+
DualTaskValidator::Context DualTaskValidator::ctx_A;
276+
DualTaskValidator::Context DualTaskValidator::ctx_B;
277+
278+
279+
TEST_F(SchedulerTests, TwoTasksStraddlingOverflow) {
280+
//Start close to overflow
281+
// Current Time: 0xFFFFFFF0 (16 ticks before wrap)
282+
uint64_t start_time = 0xFFFFFFF0ULL;
283+
Scheduler::global_tick_us_ = start_time;
284+
DualTaskValidator::reset(start_time);
285+
286+
// Task A: Period 5. Target = FFFFFFF0 + 5 = FFFFFFF5 (Before Wrap)
287+
// Task B: Period 20. Target = FFFFFFF0 + 20 = 00000004 (After Wrap)
288+
DualTaskValidator::ctx_A.expected_period = 5;
289+
DualTaskValidator::ctx_B.expected_period = 20;
290+
291+
Scheduler::register_task(20, &DualTaskValidator::callback_B); // Registers "Later" task first
292+
Scheduler::register_task(5, &DualTaskValidator::callback_A); // Registers "Earlier" task second
293+
294+
Scheduler::start();
295+
TIM2_BASE->PSC = 2;
296+
297+
// T=0 (Global FFFFFFF0)
298+
// T=5 (Global FFFFFFF5): Task A should fire.
299+
// T=10 (Global FFFFFFFA): Task A should fire again.
300+
// T=15 (Global FFFFFFFF): Task A should fire again.
301+
// T=16 (Global 00000000): --- OVERFLOW ---
302+
// T=20 (Global 00000004): Task A fires (4th time). Task B fires (1st time).
303+
constexpr int NUM_TICKS = 30;
304+
305+
for(int i = 0; i < NUM_TICKS; i++){
306+
for(int j = 0; j <= TIM2_BASE->PSC; j++) {
307+
TIM2_BASE->inc_cnt_and_check(1);
308+
}
309+
Scheduler::update();
310+
}
311+
312+
// Task A should have fired every 5 ticks. 30 / 5 = 6 times total
313+
EXPECT_FALSE(DualTaskValidator::ctx_A.error) << "Task A (Short Period) timing failed";
314+
EXPECT_EQ(DualTaskValidator::ctx_A.exec_count, 6);
315+
316+
// Task B should have fired once at T=20 (which is 0x00000004)
317+
EXPECT_FALSE(DualTaskValidator::ctx_B.error) << "Task B (Long Period) timing failed";
318+
EXPECT_EQ(DualTaskValidator::ctx_B.exec_count, 1);
319+
320+
// 0xFFFFFFF0 + 30 = 0x10000000E -> cast to 32-bit -> 0x0000000E
321+
EXPECT_EQ(static_cast<uint32_t>(Scheduler::global_tick_us_), 0x0000000E);
322+
}

0 commit comments

Comments
 (0)