Skip to content

Commit 6d25b61

Browse files
authored
Merge pull request eclipse-iceoryx#428 from elfenpiff/iox2-425-cyclic-timer
[eclipse-iceoryx#425] Periodic timer
2 parents 4f2156c + 28cc01a commit 6d25b61

File tree

4 files changed

+347
-1
lines changed

4 files changed

+347
-1
lines changed

doc/release-notes/iceoryx2-unreleased.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
conflicts when merging.
1212
-->
1313

14-
* Example text [#1](https://github.com/eclipse-iceoryx/iceoryx2/issues/1)
14+
* Add `PeriodicTimer` into POSIX building blocks [#425](https://github.com/eclipse-iceoryx/iceoryx2/issues/425)
1515

1616
### Bugfixes
1717

iceoryx2-bb/posix/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ pub mod system_configuration;
7171
#[doc(hidden)]
7272
pub mod testing;
7373
pub mod thread;
74+
pub mod timer;
7475
pub mod unique_system_id;
7576
pub mod unix_datagram_socket;
7677
pub mod user;

iceoryx2-bb/posix/src/timer.rs

+238
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
// Copyright (c) 2024 Contributors to the Eclipse Foundation
2+
//
3+
// See the NOTICE file(s) distributed with this work for additional
4+
// information regarding copyright ownership.
5+
//
6+
// This program and the accompanying materials are made available under the
7+
// terms of the Apache Software License 2.0 which is available at
8+
// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9+
// which is available at https://opensource.org/licenses/MIT.
10+
//
11+
// SPDX-License-Identifier: Apache-2.0 OR MIT
12+
13+
//! # Example
14+
//!
15+
//! ```no_run
16+
//! use iceoryx2_bb_posix::timer::*;
17+
//! use core::time::Duration;
18+
//!
19+
//! let timer = TimerBuilder::new().create().unwrap();
20+
//!
21+
//! // the timer waits on the following time points
22+
//! // 4 5 8 9 10 12 15 16 18
23+
//!
24+
//! let guard_1 = timer.cyclic(Duration::from_secs(4));
25+
//! let guard_2 = timer.cyclic(Duration::from_secs(5));
26+
//! let guard_3 = timer.cyclic(Duration::from_secs(9));
27+
//!
28+
//! std::thread::sleep(timer.duration_until_next_timeout().unwrap());
29+
//!
30+
//! // contains all the timers where the timeout was hit
31+
//! let mut missed_timeouts = vec![];
32+
//! timer
33+
//! .missed_timeouts(|timer_index| missed_timeouts.push(timer_index));
34+
//! ```
35+
36+
use std::{cell::RefCell, sync::atomic::Ordering, time::Duration};
37+
38+
use iceoryx2_bb_log::fail;
39+
use iceoryx2_pal_concurrency_sync::iox_atomic::IoxAtomicU64;
40+
41+
use crate::{
42+
clock::ClockType,
43+
clock::{Time, TimeError},
44+
};
45+
46+
/// Represents an index to identify an added timer with [`Timer::cyclic()`].
47+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48+
pub struct TimerIndex(u64);
49+
50+
/// Represents the RAII guard of [`Timer`] and is returned by [`Timer::cyclic()`].
51+
/// As soon as it goes out of scope it removes the attached cyclic timeout from [`Timer`].
52+
pub struct TimerGuard<'timer> {
53+
timer: &'timer Timer,
54+
index: u64,
55+
}
56+
57+
impl<'timer> TimerGuard<'timer> {
58+
/// Returns the underlying [`TimerIndex`] of the attachment.
59+
pub fn index(&self) -> TimerIndex {
60+
TimerIndex(self.index)
61+
}
62+
63+
/// Resets the attached timer and wait again the full time.
64+
pub fn reset(&self) -> Result<(), TimeError> {
65+
self.timer.reset(self.index)
66+
}
67+
}
68+
69+
impl<'timer> Drop for TimerGuard<'timer> {
70+
fn drop(&mut self) {
71+
self.timer.remove(self.index);
72+
}
73+
}
74+
75+
/// Builder to create a [`Timer`].
76+
pub struct TimerBuilder {
77+
clock_type: ClockType,
78+
}
79+
80+
impl Default for TimerBuilder {
81+
fn default() -> Self {
82+
Self::new()
83+
}
84+
}
85+
86+
impl TimerBuilder {
87+
/// Creates a new builder.
88+
pub fn new() -> Self {
89+
Self {
90+
clock_type: ClockType::default(),
91+
}
92+
}
93+
94+
/// Defines the [`ClockType`] that is used for time measurements. By default it is
95+
/// [`ClockType::default()`].
96+
pub fn clock_type(mut self, value: ClockType) -> Self {
97+
self.clock_type = value;
98+
self
99+
}
100+
101+
/// Creates a new [`Timer`]
102+
pub fn create(self) -> Result<Timer, TimeError> {
103+
let start_time = fail!(from "Timer::new()", when Time::now_with_clock(self.clock_type),
104+
"Failed to create Timer since the current time could not be acquired.");
105+
let start_time = start_time.as_duration().as_nanos();
106+
107+
Ok(Timer {
108+
attachments: RefCell::new(vec![]),
109+
id_count: IoxAtomicU64::new(0),
110+
clock_type: self.clock_type,
111+
previous_iteration: RefCell::new(start_time),
112+
})
113+
}
114+
}
115+
116+
#[derive(Debug)]
117+
struct Attachment {
118+
index: u64,
119+
period: u128,
120+
start_time: u128,
121+
}
122+
123+
impl Attachment {
124+
fn new(index: u64, period: u128, clock_type: ClockType) -> Result<Self, TimeError> {
125+
let start_time = fail!(from "Attachment::new()", when Time::now_with_clock(clock_type),
126+
"Failed to create Timer attachment since the current time could not be acquired.");
127+
let start_time = start_time.as_duration().as_nanos();
128+
129+
Ok(Self {
130+
index,
131+
period,
132+
start_time,
133+
})
134+
}
135+
136+
fn reset(&mut self, clock_type: ClockType) -> Result<(), TimeError> {
137+
let start_time = fail!(from "Attachment::new()", when Time::now_with_clock(clock_type),
138+
"Failed to reset Timer attachment since the current time could not be acquired.");
139+
self.start_time = start_time.as_duration().as_nanos();
140+
Ok(())
141+
}
142+
}
143+
144+
/// The [`Timer`] allows the user to attach multiple periodic timers with
145+
/// [`Timer::cyclic()`], to wait on them by acquiring the waiting time to the next timer
146+
/// with [`Timer::duration_until_next_timeout()`] and to acquire all missed timers via
147+
/// [`Timer::missed_timeouts()`].
148+
#[derive(Debug)]
149+
pub struct Timer {
150+
attachments: RefCell<Vec<Attachment>>,
151+
id_count: IoxAtomicU64,
152+
previous_iteration: RefCell<u128>,
153+
154+
clock_type: ClockType,
155+
}
156+
157+
impl Timer {
158+
/// Adds a cyclic timeout to the [`Timer`] and returns an [`TimerGuard`] to
159+
/// identify the attachment uniquely.
160+
/// [`Timer::duration_until_next_timeout()`] will schedule the timings in a way that the attached
161+
/// timeout is considered cyclicly.
162+
pub fn cyclic(&self, timeout: Duration) -> Result<TimerGuard, TimeError> {
163+
let current_idx = self.id_count.load(Ordering::Relaxed);
164+
self.attachments.borrow_mut().push(Attachment::new(
165+
current_idx,
166+
timeout.as_nanos(),
167+
self.clock_type,
168+
)?);
169+
self.id_count.fetch_add(1, Ordering::Relaxed);
170+
171+
Ok(TimerGuard {
172+
timer: self,
173+
index: current_idx,
174+
})
175+
}
176+
177+
fn remove(&self, index: u64) {
178+
let mut index_to_remove = None;
179+
for (n, attachment) in self.attachments.borrow().iter().enumerate() {
180+
if attachment.index == index {
181+
index_to_remove = Some(n);
182+
break;
183+
}
184+
}
185+
186+
if let Some(n) = index_to_remove {
187+
self.attachments.borrow_mut().remove(n);
188+
}
189+
}
190+
191+
fn reset(&self, index: u64) -> Result<(), TimeError> {
192+
for attachment in &mut *self.attachments.borrow_mut() {
193+
if attachment.index == index {
194+
attachment.reset(self.clock_type)?;
195+
break;
196+
}
197+
}
198+
199+
Ok(())
200+
}
201+
202+
/// Returns the waiting duration until the next added timeout is reached.
203+
pub fn duration_until_next_timeout(&self) -> Result<Duration, TimeError> {
204+
let now = fail!(from self, when Time::now_with_clock(self.clock_type),
205+
"Unable to return next duration since the current time could not be acquired.");
206+
let now = now.as_duration().as_nanos();
207+
*self.previous_iteration.borrow_mut() = now;
208+
209+
let mut min_time = u128::MAX;
210+
for attachment in &*self.attachments.borrow() {
211+
min_time =
212+
min_time.min(attachment.period - (now - attachment.start_time) % attachment.period);
213+
}
214+
215+
Ok(Duration::from_nanos(min_time as _))
216+
}
217+
218+
/// Iterates over all missed timeouts and calls the provided callback for each of them
219+
/// and provide the [`TimerIndex`] to identify them.
220+
pub fn missed_timeouts<F: FnMut(TimerIndex)>(&self, mut call: F) -> Result<(), TimeError> {
221+
let now = fail!(from self, when Time::now_with_clock(self.clock_type),
222+
"Unable to return next duration since the current time could not be acquired.");
223+
224+
let now = now.as_duration().as_nanos();
225+
let last = *self.previous_iteration.borrow();
226+
227+
for attachment in &*self.attachments.borrow() {
228+
let duration_until_last = last.max(attachment.start_time) - attachment.start_time;
229+
let duration_until_now = now - attachment.start_time;
230+
if (duration_until_last / attachment.period) < (duration_until_now / attachment.period)
231+
{
232+
call(TimerIndex(attachment.index));
233+
}
234+
}
235+
236+
Ok(())
237+
}
238+
}
+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Copyright (c) 2024 Contributors to the Eclipse Foundation
2+
//
3+
// See the NOTICE file(s) distributed with this work for additional
4+
// information regarding copyright ownership.
5+
//
6+
// This program and the accompanying materials are made available under the
7+
// terms of the Apache Software License 2.0 which is available at
8+
// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
9+
// which is available at https://opensource.org/licenses/MIT.
10+
//
11+
// SPDX-License-Identifier: Apache-2.0 OR MIT
12+
13+
mod timer {
14+
use iceoryx2_bb_posix::timer::*;
15+
use iceoryx2_bb_testing::assert_that;
16+
use std::time::Duration;
17+
18+
#[test]
19+
fn next_iteration_works_smallest_timeout_added_first() {
20+
let sut = TimerBuilder::new().create().unwrap();
21+
22+
let _guard_1 = sut.cyclic(Duration::from_secs(5)).unwrap();
23+
let _guard_2 = sut.cyclic(Duration::from_secs(10)).unwrap();
24+
let _guard_2 = sut.cyclic(Duration::from_secs(100)).unwrap();
25+
26+
assert_that!(sut.duration_until_next_timeout().unwrap(), le Duration::from_secs(5));
27+
assert_that!(sut.duration_until_next_timeout().unwrap(), ge Duration::from_secs(1));
28+
}
29+
30+
#[test]
31+
fn next_iteration_works_smallest_timeout_added_last() {
32+
let sut = TimerBuilder::new().create().unwrap();
33+
34+
let _guard_1 = sut.cyclic(Duration::from_secs(100)).unwrap();
35+
let _guard_2 = sut.cyclic(Duration::from_secs(10)).unwrap();
36+
let _guard_3 = sut.cyclic(Duration::from_secs(5)).unwrap();
37+
38+
assert_that!(sut.duration_until_next_timeout().unwrap(), le Duration::from_secs(5));
39+
assert_that!(sut.duration_until_next_timeout().unwrap(), ge Duration::from_secs(1));
40+
}
41+
42+
#[test]
43+
fn removing_timeout_works() {
44+
let sut = TimerBuilder::new().create().unwrap();
45+
46+
let _guard_1 = sut.cyclic(Duration::from_secs(1000)).unwrap();
47+
let _guard_2 = sut.cyclic(Duration::from_secs(100)).unwrap();
48+
let _guard_3 = sut.cyclic(Duration::from_secs(1)).unwrap();
49+
50+
drop(_guard_3);
51+
52+
assert_that!(sut.duration_until_next_timeout().unwrap(), ge Duration::from_secs(10));
53+
assert_that!(sut.duration_until_next_timeout().unwrap(), le Duration::from_secs(100));
54+
}
55+
56+
#[test]
57+
fn no_missed_timeout_works() {
58+
let sut = TimerBuilder::new().create().unwrap();
59+
60+
let _guard_1 = sut.cyclic(Duration::from_secs(10)).unwrap();
61+
let _guard_2 = sut.cyclic(Duration::from_secs(100)).unwrap();
62+
let _guard_3 = sut.cyclic(Duration::from_secs(1000)).unwrap();
63+
64+
let mut missed_timers = vec![];
65+
sut.missed_timeouts(|idx| missed_timers.push(idx)).unwrap();
66+
67+
assert_that!(missed_timers, len 0);
68+
}
69+
70+
#[test]
71+
fn one_missed_timeouts_works() {
72+
let sut = TimerBuilder::new().create().unwrap();
73+
74+
let _guard_1 = sut.cyclic(Duration::from_nanos(1)).unwrap();
75+
let _guard_2 = sut.cyclic(Duration::from_secs(100)).unwrap();
76+
let _guard_3 = sut.cyclic(Duration::from_secs(1000)).unwrap();
77+
78+
std::thread::sleep(Duration::from_millis(10));
79+
80+
let mut missed_timeouts = vec![];
81+
sut.missed_timeouts(|idx| missed_timeouts.push(idx))
82+
.unwrap();
83+
84+
assert_that!(missed_timeouts, len 1);
85+
assert_that!(missed_timeouts, contains _guard_1.index());
86+
}
87+
88+
#[test]
89+
fn many_missed_timeouts_works() {
90+
let sut = TimerBuilder::new().create().unwrap();
91+
92+
let guard_1 = sut.cyclic(Duration::from_nanos(1)).unwrap();
93+
let guard_2 = sut.cyclic(Duration::from_nanos(10)).unwrap();
94+
let guard_3 = sut.cyclic(Duration::from_nanos(20)).unwrap();
95+
96+
std::thread::sleep(Duration::from_millis(10));
97+
98+
let mut missed_timeouts = vec![];
99+
sut.missed_timeouts(|idx| missed_timeouts.push(idx))
100+
.unwrap();
101+
102+
assert_that!(missed_timeouts, len 3);
103+
assert_that!(missed_timeouts, contains guard_1.index());
104+
assert_that!(missed_timeouts, contains guard_2.index());
105+
assert_that!(missed_timeouts, contains guard_3.index());
106+
}
107+
}

0 commit comments

Comments
 (0)