Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(s2n-quic-core): add Cached clock implementation #2133

Merged
merged 1 commit into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions quic/s2n-quic-core/src/time/clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,39 @@ impl Clock for NoopClock {
unsafe { Timestamp::from_duration(Duration::from_micros(1)) }
}
}

impl Clock for Timestamp {
#[inline]
fn get_time(&self) -> Timestamp {
*self
}
}

/// A clock that caches the time query for the inner clock
pub struct Cached<'a, C: Clock> {
clock: &'a C,
cached_value: core::cell::Cell<Option<Timestamp>>,
}

impl<'a, C: Clock> Cached<'a, C> {
#[inline]
pub fn new(clock: &'a C) -> Self {
Self {
clock,
cached_value: Default::default(),
}
}
}

impl<'a, C: Clock> Clock for Cached<'a, C> {
#[inline]
fn get_time(&self) -> Timestamp {
if let Some(time) = self.cached_value.get() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this cache doesn't need a TTL?

Copy link
Contributor Author

@camshaft camshaft Feb 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this borrows an inner clock for 'a, the lifetime of the borrow is the TTL, if that makes sense. Its intended use is something like this:

{
    let clock = clock.cached();

    for packet in ack_packets {
        packet.ack_time = clock.get_time();
    }
}

{
    let clock = clock.cached();

    for packet in lost_packets {
        packet.lost_time = clock.get_time();
    }
}

In this example, we'll only query the cache at most twice, regardless of how many packets are in the two iterators.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've got a few ad-hoc examples of doing this in the codebase than I'm going to clean up after this is merged:

let mut now: Option<Timestamp> = None;
queue.for_each(|mut header, payload| {
let timestamp = match now {
Some(time) => time,
None => {
now = Some(clock.get_time());
now.expect("value should be set")
}
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah nice. Would it be possible to a add a debug_assertion or something to prevent misuse? Something like if the difference between the cache and now is > x milliseconds, panic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the problem is what would the x be without triggering false positives... :/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok to merge this though for now, though if its mainly used in pretty tight loops I think we could start with something like 1 or 2 ms

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's way too low, especially in debug mode. I was thinking 1s+. But yeah we can figure something else later. I'm not super worried about it being misused since it borrows a clock rather than owning it so it'd really only live as long as a stack frame.

return time;
}

let now = self.clock.get_time();
self.cached_value.set(Some(now));
now
}
}
13 changes: 13 additions & 0 deletions quic/s2n-quic-core/src/time/timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,19 @@ impl<F: FnMut(&Timer) -> Result> Query for ForEach<F> {
}
}

#[cfg(feature = "tracing")]
pub struct Debugger;

#[cfg(feature = "tracing")]
impl Query for Debugger {
#[inline]
#[track_caller]
fn on_timer(&mut self, timer: &Timer) -> Result {
tracing::trace!(location = %core::panic::Location::caller(), timer = ?timer);
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading