Skip to content

Commit 16adb1e

Browse files
authored
fix calculation of next timer call (ros2#291)
* fix calculation of next timer call * fix logic for period zero
1 parent e78e400 commit 16adb1e

File tree

1 file changed

+33
-4
lines changed

1 file changed

+33
-4
lines changed

rcl/src/rcl/timer.c

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ typedef struct rcl_timer_impl_t
3636
atomic_uint_least64_t period;
3737
// This is a time in nanoseconds since an unspecified time.
3838
atomic_uint_least64_t last_call_time;
39+
// This is a time in nanoseconds since an unspecified time.
40+
atomic_uint_least64_t next_call_time;
3941
// A flag which indicates if the timer is canceled.
4042
atomic_bool canceled;
4143
// The user supplied allocator.
@@ -75,6 +77,7 @@ rcl_timer_init(
7577
atomic_init(&impl.callback, (uintptr_t)callback);
7678
atomic_init(&impl.period, period);
7779
atomic_init(&impl.last_call_time, now);
80+
atomic_init(&impl.next_call_time, now + period);
7881
atomic_init(&impl.canceled, false);
7982
impl.allocator = allocator;
8083
timer->impl = (rcl_timer_impl_t *)allocator.allocate(sizeof(rcl_timer_impl_t), allocator.state);
@@ -127,11 +130,37 @@ rcl_timer_call(rcl_timer_t * timer)
127130
if (now_ret != RCL_RET_OK) {
128131
return now_ret; // rcl error state should already be set.
129132
}
133+
if (now < 0) {
134+
RCL_SET_ERROR_MSG("clock now returned negative time point value", *allocator);
135+
return RCL_RET_ERROR;
136+
}
137+
uint64_t unow = (uint64_t)now;
130138
rcl_time_point_value_t previous_ns =
131-
rcl_atomic_exchange_uint64_t(&timer->impl->last_call_time, now);
139+
rcl_atomic_exchange_uint64_t(&timer->impl->last_call_time, unow);
132140
rcl_timer_callback_t typed_callback =
133141
(rcl_timer_callback_t)rcl_atomic_load_uintptr_t(&timer->impl->callback);
134142

143+
uint64_t next_call_time = rcl_atomic_load_uint64_t(&timer->impl->next_call_time);
144+
uint64_t period = rcl_atomic_load_uint64_t(&timer->impl->period);
145+
// always move the next call time by exactly period forward
146+
// don't use now as the base to avoid extending each cycle by the time
147+
// between the timer being ready and the callback being triggered
148+
next_call_time += period;
149+
// in case the timer has missed at least once cycle
150+
if (next_call_time < unow) {
151+
if (0 == period) {
152+
// a timer with a period of zero is considered always ready
153+
next_call_time = unow;
154+
} else {
155+
// move the next call time forward by as many periods as necessary
156+
uint64_t now_ahead = unow - next_call_time;
157+
// rounding up without overflow
158+
uint64_t periods_ahead = 1 + (now_ahead - 1) / period;
159+
next_call_time += periods_ahead * period;
160+
}
161+
}
162+
rcl_atomic_store(&timer->impl->next_call_time, next_call_time);
163+
135164
if (typed_callback != NULL) {
136165
int64_t since_last_call = now - previous_ns;
137166
typed_callback(timer, since_last_call);
@@ -171,9 +200,8 @@ rcl_timer_get_time_until_next_call(const rcl_timer_t * timer, int64_t * time_unt
171200
if (ret != RCL_RET_OK) {
172201
return ret; // rcl error state should already be set.
173202
}
174-
int64_t period = rcl_atomic_load_uint64_t(&timer->impl->period);
175203
*time_until_next_call =
176-
(rcl_atomic_load_uint64_t(&timer->impl->last_call_time) + period) - now;
204+
rcl_atomic_load_uint64_t(&timer->impl->next_call_time) - now;
177205
return RCL_RET_OK;
178206
}
179207

@@ -282,7 +310,8 @@ rcl_timer_reset(rcl_timer_t * timer)
282310
if (now_ret != RCL_RET_OK) {
283311
return now_ret; // rcl error state should already be set.
284312
}
285-
rcl_atomic_store(&timer->impl->last_call_time, now);
313+
int64_t period = rcl_atomic_load_uint64_t(&timer->impl->period);
314+
rcl_atomic_store(&timer->impl->next_call_time, now + period);
286315
rcl_atomic_store(&timer->impl->canceled, false);
287316
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Timer successfully reset")
288317
return RCL_RET_OK;

0 commit comments

Comments
 (0)