Skip to content

Commit c5e1fa9

Browse files
Coral-coderclaudecodex
authored andcommitted
cron: arm a one-shot timer for the next job instead of polling at 1 Hz
The cron service registered a per-second regular_timer callback that locked the job-list mutex and compared the head job's execute time against the clock 86400 times a day, almost always concluding there was nothing to do. Cron already knows exactly when the next job is due, so arm a one-shot new_timer for that instant instead and re-arm on every list mutation (schedule, schedule_after, unschedule, clear) and firing. Job execution semantics are unchanged: callbacks still run on the timer task at the same second they used to, offset_seconds resolution is preserved, and cron_service_handle_clock_change() still runs due jobs and now re-arms for the new head. The armed interval is capped at one hour so any tick-clock drift across long sleeps stays bounded. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: GPT-5 <noreply@openai.com> Signed-off-by: Ara Michelle <coral-coder@proton.me> (cherry picked from commit 90a67da) Signed-off-by: Joshua Jun <lets@throw.rocks>
1 parent d14cba9 commit c5e1fa9

2 files changed

Lines changed: 76 additions & 10 deletions

File tree

src/fw/services/cron/service.c

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,49 @@
44
#include "pbl/services/cron.h"
55
#include <pebbleos/cron.h>
66

7+
#include "drivers/rtc.h"
78
#include "pbl/os/mutex.h"
8-
#include "system/passert.h"
9-
#include "pbl/services/regular_timer.h"
9+
#include "pbl/services/new_timer/new_timer.h"
1010
#include "system/logging.h"
11+
#include "system/passert.h"
1112
#include "pbl/util/math.h"
1213

1314
PBL_LOG_MODULE_DEFINE(service_cron, CONFIG_SERVICE_CRON_LOG_LEVEL);
1415

1516
//! Don't let users modify the list while callbacks are occurring.
1617
static PebbleMutex *s_list_mutex = NULL;
1718

18-
static void prv_timer_callback(void* data);
19-
static RegularTimerInfo s_regular = {
20-
.cb = prv_timer_callback,
21-
};
22-
2319
// List of jobs sorted from soonest to farthest.
2420
static ListNode *s_scheduled_jobs;
2521

22+
static void prv_timer_callback(void* data);
23+
24+
//! One-shot timer armed for the next job's execute time. Re-armed after every
25+
//! list mutation and every firing; stopped when no jobs are scheduled. Capped
26+
//! so that long sleeps re-check at least hourly, which keeps any tick-clock
27+
//! drift across sleep bounded.
28+
#define CRON_MAX_ARM_INTERVAL_S (60 * 60)
29+
static TimerID s_wakeup_timer = TIMER_INVALID_ID;
30+
31+
//! Arm (or stop) the wakeup timer for the head job. s_list_mutex must be held.
32+
static void prv_arm_wakeup(void) {
33+
if (s_scheduled_jobs == NULL) {
34+
new_timer_stop(s_wakeup_timer);
35+
return;
36+
}
37+
time_t now;
38+
uint16_t milliseconds;
39+
rtc_get_time_ms(&now, &milliseconds);
40+
const time_t execute_time = ((CronJob *)s_scheduled_jobs)->cached_execute_time;
41+
int32_t delta_s = (execute_time > now) ? (int32_t)(execute_time - now) : 0;
42+
delta_s = MIN(delta_s, CRON_MAX_ARM_INTERVAL_S);
43+
uint32_t timeout_ms = (uint32_t)delta_s * 1000U;
44+
if (timeout_ms > milliseconds) {
45+
timeout_ms -= milliseconds;
46+
}
47+
new_timer_start(s_wakeup_timer, timeout_ms, prv_timer_callback, NULL, 0 /*flags*/);
48+
}
49+
2650
// -------------------------------------------------------------------------------------------
2751
static bool prv_is_scheduled(CronJob *job) {
2852
// Assumes mutex lock is already taken
@@ -49,6 +73,7 @@ static void prv_timer_callback(void* data) {
4973
job->cb(job, job->cb_data);
5074
mutex_lock(s_list_mutex);
5175
}
76+
prv_arm_wakeup();
5277
mutex_unlock(s_list_mutex);
5378
}
5479

@@ -89,7 +114,9 @@ void cron_service_init(void) {
89114
s_list_mutex = mutex_create();
90115
s_scheduled_jobs = NULL;
91116

92-
regular_timer_add_seconds_callback(&s_regular);
117+
if (s_wakeup_timer == TIMER_INVALID_ID) {
118+
s_wakeup_timer = new_timer_create();
119+
}
93120
}
94121

95122
// -------------------------------------------------------------------------------------------
@@ -108,6 +135,7 @@ time_t cron_job_schedule(CronJob *job) {
108135
PBL_LOG_DBG("Cron job scheduled for %ld (%+ld)", job->cached_execute_time,
109136
(job->cached_execute_time - now));
110137

138+
prv_arm_wakeup();
111139
mutex_unlock(s_list_mutex);
112140

113141
return job->cached_execute_time;
@@ -135,6 +163,7 @@ time_t cron_job_schedule_after(CronJob *job, CronJob *new_job) {
135163
list_insert_after(&job->list_node, &new_job->list_node);
136164
PBL_LOG_DBG("Cron job scheduled for %ld", job->cached_execute_time);
137165

166+
prv_arm_wakeup();
138167
mutex_unlock(s_list_mutex);
139168

140169
return job->cached_execute_time;
@@ -160,6 +189,7 @@ bool cron_job_unschedule(CronJob *job) {
160189
if (prv_is_scheduled(job)) {
161190
list_remove(&job->list_node, &s_scheduled_jobs, NULL);
162191
removed = true;
192+
prv_arm_wakeup();
163193
}
164194

165195
mutex_unlock(s_list_mutex);
@@ -181,6 +211,7 @@ void cron_clear_all_jobs(void) {
181211
list_remove(&job->list_node, NULL, NULL);
182212
}
183213
s_scheduled_jobs = NULL;
214+
prv_arm_wakeup();
184215

185216
mutex_unlock(s_list_mutex);
186217
}
@@ -191,7 +222,7 @@ void cron_service_deinit(void) {
191222
mutex_destroy(s_list_mutex);
192223
s_list_mutex = NULL;
193224

194-
regular_timer_remove_callback(&s_regular);
225+
new_timer_stop(s_wakeup_timer);
195226
}
196227

197228
uint32_t cron_service_get_job_count(void) {

tests/fw/services/test_cron.c

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,32 @@
44
#include "clar.h"
55

66
#include "pbl/services/cron.h"
7+
#include "pbl/services/new_timer/new_timer.h"
78
#include "pbl/util/size.h"
89

910
#include <pebbleos/cron.h>
1011

1112
#include "stubs_logging.h"
1213
#include "stubs_mutex.h"
1314
#include "stubs_passert.h"
14-
#include "stubs_regular_timer.h"
1515
#include "fake_rtc.h"
1616

17+
static uint32_t s_timer_timeout_ms;
18+
19+
TimerID new_timer_create(void) {
20+
return 1;
21+
}
22+
23+
bool new_timer_start(TimerID timer, uint32_t timeout_ms, NewTimerCallback cb, void *cb_data,
24+
uint32_t flags) {
25+
s_timer_timeout_ms = timeout_ms;
26+
return true;
27+
}
28+
29+
bool new_timer_stop(TimerID timer) {
30+
return true;
31+
}
32+
1733
// Tests
1834
///////////////////////////////////////////////////////////
1935
// Thursday 2015 Nov 12, 00:00:00 GMT
@@ -36,6 +52,7 @@ static const TimezoneInfo s_timezone_gmt = {
3652
};
3753

3854
void test_cron__initialize(void) {
55+
s_timer_timeout_ms = 0;
3956
cron_service_init();
4057
}
4158

@@ -66,6 +83,24 @@ static void prv_clock_change(int32_t time_diff, int32_t gmt_diff, bool dst_trans
6683
cron_service_handle_clock_change(&set_time_info);
6784
}
6885

86+
void test_cron__timer_aligned_to_execute_second(void) {
87+
CronJob job = {
88+
.cb = prv_cron_callback,
89+
.minute = 45,
90+
.hour = CRON_HOUR_ANY,
91+
.mday = CRON_MDAY_ANY,
92+
.month = CRON_MONTH_ANY,
93+
};
94+
prv_set_rtc(s_2015_nov12_123456_gmt, &s_timezone_gmt);
95+
fake_rtc_increment_time_ms(750);
96+
97+
const time_t execute_time = cron_job_schedule(&job);
98+
99+
cl_assert_equal_i(execute_time, 1447332300);
100+
cl_assert_equal_i(s_timer_timeout_ms, 603250);
101+
cl_assert(cron_job_unschedule(&job));
102+
}
103+
69104
void test_cron__time_change_basic(void) {
70105
CronJob test_cron = {
71106
.cb = prv_cron_callback,

0 commit comments

Comments
 (0)