Expose a SleepFuture::reset

⚓ Rust    📅 2025-09-26    👤 surdeus    👁️ 12      

surdeus

Warning

This post was published 34 days ago. The information described in this article may have changed.

Info

This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: Expose a SleepFuture::reset

Code: Draft: sleepfuture-reset: Create new `SleepFuture` trait with `reset` method (!3287) · Merge requests · The Tor Project / Core / Arti · GitLab


Hi

I am contributing to the Arti project with the goal to become more fluent in Rust. So I tried the following issue: rtcompat: Expose a SleepFuture::reset (#2176) · Issues · The Tor Project / Core / Arti · GitLab.

In e.g maybenot_padding.rs:L903 you see we currently do:

// TODO circpad: Avoid rebuilding sleep future needlessly.  May require new APIs in
// tor-rtcompat.
self.sleep_future = runtime.sleep(t.saturating_duration_since(now)); // We create a new runtime each time we want to reset the expiration time.

The author of the issue provided to add a reset method in our tor-rtcompat API to expose e.g Tokio's Sleep in tokio::time - Rust reset so we can change the interval of the future without the need of creating a new one.

FYI, tor-rtcompat is a crate in Arti to be support compatibility between different runtimes like tokio, async-std or smol.

I made the following draft MR which already implements this for tokio, but I'd like some feedback and double-check my understanding: Draft: sleepfuture-reset: Create new `SleepFuture` trait with `reset` method (!3287) · Merge requests · The Tor Project / Core / Arti · GitLab.

Commit f1416858 - Create new SleepFuture trait with reset method

Previously SleepFuture was just a type type SleepFuture: Future<Output = ()> + Send + 'static; without any implementation. So I made a trait defining reset() method.

pub trait SleepFuture: Future<Output = ()> + Send + 'static {
    /// Reset the sleep future to expire at `instant`.
    ///
    /// With this method we do not have to create a new sleep future.
    fn reset(self: std::pin::Pin<&mut Self>, instant: Instant);
} 

I added a few supertrait bounds to make sure that each type implementing SleepFuture also implements these:

  • Future<Output = ()>: Make sure any type implementing is also a Future.
  • Send: The implementing type must be able to be send safely across threads.
  • 'static: The implementing type may not have properties with a limited lifetime or borrowing data. This to make sure all the data from the implementing type can fully life over several threads etc.

I also made SleepProvider have the type type SleepFuture: SleepFuture; so each implementation can decide what implementation of SleepFuture to use (Tokio, async-std, smol,...)

Is this correct?

Commit 219bd7d8 - Update Tokio implementation with a new TokioSleep type to implement SleepFuture

Here I make the actual Tokio implementation of the new SleepFuture trait.

First I make a new type TokioSleep:

// Wrapper around a tokio sleep future.
// We use this wrapper so that we can implement SleepFuture on it.
pub struct TokioSleep(std::pin::Pin<Box<tokio_crate::time::Sleep>>);

It's actually just a wrapper around Tokio's sleep implementation which has the actual reset-method: Sleep in tokio::time - Rust

TokioSleep is not future, but our trait requires this due to the supertrait bounds. So I make it future:

// Make TokioSleep a Future by delegating to the inner future.
impl Future for TokioSleep {
    type Output = ();

    fn poll(
        self: std::pin::Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Self::Output> {
        self.get_mut().0.as_mut().poll(cx)
    }
}

This reset method just calls reset on the inner future (being Tokio):

// Implement SleepFuture by delegating to the inner future.
impl SleepFuture for TokioSleep {
    fn reset(self: std::pin::Pin<&mut Self>, instant: std::time::Instant) {
        self.get_mut()
            .0
            .as_mut()
            .reset(tokio_crate::time::Instant::from_std(instant));
    }
}

The method signature is uses type std::time::Instant for the instant parameter to be compatible with the trait definition. In the actual implementation I convert this to a tokio::time::Instant using from_std.

Pin is necessary because the future may contain internal references, and we want to prohibit moving this in data to avoid references to old/invalid locations in memory.

I updated the sleep method in SleepProvider to wrap the returned type in our new TokioSleep so the returned type can use reset.

Is this correct?

Commit fdad727f - Make DynSleepFuture forward the reset call to the inner SleepFuture

DynSleepFuture is a type which makes it possible to support different implementations of SleepFuture (Tokio, async-std, smol,...) at compile time. But when I would use DynSleepFuture in my code it would not implement SleepFuture resulting in my usages not being able to find the new reset method.

That's why I explicitly implemented it for DynSleepFuture.

// Make DynSleepFuture a SleepFuture explicitly.
impl SleepFuture for DynSleepFuture {
    fn reset(mut self: Pin<&mut Pin<Box<(dyn SleepFuture + 'static)>>>, instant: std::time::Instant) {
        let inner: Pin<&mut dyn SleepFuture> = self.as_mut();
        inner.reset(instant);
    }
}

This is actually just glue code to make DynSleepFuture forward the reset call to the actual SleepFuture from e.g Tokio.

Is this correct?

Commit ac3e33a7 - Update tor-rtmock to implement stubs for SleepFuture

tor-rtmock is used for testing. I implemented basic stubs for SleepFuture so the examples can keep executing.


Some feedback and learnings would be highly appreciated! Thanks in advance!

1 post - 1 participant

Read full topic

🏷️ Rust_feed