Skip to content

Commit 137d578

Browse files
authored
feat: bring back driver in fastimer-driver (#19)
Signed-off-by: tison <[email protected]>
1 parent 51ae96b commit 137d578

File tree

6 files changed

+370
-2
lines changed

6 files changed

+370
-2
lines changed

Cargo.lock

+38
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414

1515
[workspace]
16-
members = ["fastimer", "fastimer-tokio", "xtask"]
16+
members = ["fastimer", "fastimer-driver", "fastimer-tokio", "xtask"]
1717
resolver = "2"
1818

1919
[workspace.package]

fastimer-driver/Cargo.toml

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright 2024 FastLabs Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
[package]
16+
name = "fastimer-driver"
17+
18+
description = "This crate implements a timer driver that can work with any async runtime scheduler."
19+
20+
edition.workspace = true
21+
homepage.workspace = true
22+
license.workspace = true
23+
readme.workspace = true
24+
repository.workspace = true
25+
rust-version.workspace = true
26+
version.workspace = true
27+
28+
[package.metadata.docs.rs]
29+
all-features = true
30+
rustdoc-args = ["--cfg", "docsrs"]
31+
32+
[dependencies]
33+
atomic-waker = { version = "1.1.2" }
34+
crossbeam-queue = { version = "0.3.12" }
35+
fastimer = { workspace = true }
36+
parking = { version = "2.2.1" }
37+
38+
[dev-dependencies]
39+
tokio = { workspace = true, features = ["full"] }
40+
41+
[lints]
42+
workspace = true

fastimer-driver/src/lib.rs

+233
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
// Copyright 2024 FastLabs Developers
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
16+
#![deny(missing_docs)]
17+
18+
//! Runtime-agnostic time driver for creating delay futures.
19+
20+
use std::cmp;
21+
use std::collections::BinaryHeap;
22+
use std::future::Future;
23+
use std::pin::Pin;
24+
use std::sync::atomic;
25+
use std::sync::atomic::AtomicBool;
26+
use std::sync::Arc;
27+
use std::task::Context;
28+
use std::task::Poll;
29+
use std::time::Duration;
30+
use std::time::Instant;
31+
32+
use atomic_waker::AtomicWaker;
33+
use crossbeam_queue::SegQueue;
34+
use fastimer::make_instant_from_now;
35+
use fastimer::MakeDelay;
36+
use parking::Parker;
37+
use parking::Unparker;
38+
39+
#[cfg(test)]
40+
mod tests;
41+
42+
#[derive(Debug)]
43+
struct TimeEntry {
44+
when: Instant,
45+
waker: Arc<AtomicWaker>,
46+
}
47+
48+
impl PartialEq for TimeEntry {
49+
fn eq(&self, other: &Self) -> bool {
50+
self.when == other.when
51+
}
52+
}
53+
54+
impl Eq for TimeEntry {}
55+
56+
impl PartialOrd for TimeEntry {
57+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
58+
Some(self.when.cmp(&other.when))
59+
}
60+
}
61+
62+
impl Ord for TimeEntry {
63+
fn cmp(&self, other: &Self) -> cmp::Ordering {
64+
self.when.cmp(&other.when)
65+
}
66+
}
67+
68+
/// Future returned by [`delay`] and [`delay_until`].
69+
///
70+
/// [`delay`]: TimeContext::delay
71+
/// [`delay_until`]: TimeContext::delay_until
72+
#[must_use = "futures do nothing unless you `.await` or poll them"]
73+
#[derive(Debug)]
74+
pub struct Delay {
75+
when: Instant,
76+
waker: Arc<AtomicWaker>,
77+
}
78+
79+
impl Future for Delay {
80+
type Output = ();
81+
82+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
83+
if Instant::now() >= self.when {
84+
self.waker.take();
85+
Poll::Ready(())
86+
} else {
87+
self.waker.register(cx.waker());
88+
Poll::Pending
89+
}
90+
}
91+
}
92+
93+
impl Drop for Delay {
94+
fn drop(&mut self) {
95+
self.waker.take();
96+
}
97+
}
98+
99+
/// Returns a new time driver, its time context and the shutdown handle.
100+
pub fn driver() -> (TimeDriver, TimeContext, TimeDriverShutdown) {
101+
let (parker, unparker) = parking::pair();
102+
let timers = BinaryHeap::new();
103+
let inbounds = Arc::new(SegQueue::new());
104+
let shutdown = Arc::new(AtomicBool::new(false));
105+
106+
let driver = TimeDriver {
107+
parker,
108+
unparker,
109+
timers,
110+
inbounds,
111+
shutdown,
112+
};
113+
114+
let context = TimeContext {
115+
unparker: driver.unparker.clone(),
116+
inbounds: driver.inbounds.clone(),
117+
};
118+
119+
let shutdown = TimeDriverShutdown {
120+
unparker: driver.unparker.clone(),
121+
shutdown: driver.shutdown.clone(),
122+
};
123+
124+
(driver, context, shutdown)
125+
}
126+
127+
/// A time context for creating [`Delay`]s.
128+
#[derive(Debug, Clone)]
129+
pub struct TimeContext {
130+
unparker: Unparker,
131+
inbounds: Arc<SegQueue<TimeEntry>>,
132+
}
133+
134+
impl TimeContext {
135+
/// Returns a future that completes after the specified duration.
136+
pub fn delay(&self, dur: Duration) -> Delay {
137+
self.delay_until(make_instant_from_now(dur))
138+
}
139+
140+
/// Returns a future that completes at the specified instant.
141+
pub fn delay_until(&self, when: Instant) -> Delay {
142+
let waker = Arc::new(AtomicWaker::new());
143+
let delay = Delay {
144+
when,
145+
waker: waker.clone(),
146+
};
147+
self.inbounds.push(TimeEntry { when, waker });
148+
self.unparker.unpark();
149+
delay
150+
}
151+
}
152+
153+
/// A handle to shut down the time driver.
154+
#[derive(Debug, Clone)]
155+
pub struct TimeDriverShutdown {
156+
unparker: Unparker,
157+
shutdown: Arc<AtomicBool>,
158+
}
159+
160+
impl TimeDriverShutdown {
161+
/// Shuts down the time driver.
162+
pub fn shutdown(&self) {
163+
self.shutdown.store(true, atomic::Ordering::Release);
164+
self.unparker.unpark();
165+
}
166+
}
167+
168+
/// A time driver that drives registered timers.
169+
#[derive(Debug)]
170+
pub struct TimeDriver {
171+
parker: Parker,
172+
unparker: Unparker,
173+
timers: BinaryHeap<TimeEntry>,
174+
inbounds: Arc<SegQueue<TimeEntry>>,
175+
shutdown: Arc<AtomicBool>,
176+
}
177+
178+
impl TimeDriver {
179+
/// Drives the timers and returns `true` if the driver has been shut down.
180+
pub fn turn(&mut self) -> bool {
181+
if self.shutdown.load(atomic::Ordering::Acquire) {
182+
return true;
183+
}
184+
185+
match self.timers.peek() {
186+
None => self.parker.park(),
187+
Some(entry) => {
188+
let delta = entry.when.saturating_duration_since(Instant::now());
189+
if delta > Duration::ZERO {
190+
self.parker.park_timeout(delta);
191+
}
192+
}
193+
}
194+
195+
while let Some(entry) = self.inbounds.pop() {
196+
self.timers.push(entry);
197+
}
198+
199+
while let Some(entry) = self.timers.peek() {
200+
if entry.when <= Instant::now() {
201+
entry.waker.wake();
202+
let _ = self.timers.pop();
203+
} else {
204+
break;
205+
}
206+
}
207+
208+
self.shutdown.load(atomic::Ordering::Acquire)
209+
}
210+
}
211+
212+
/// A delay implementation that uses the given time context.
213+
#[derive(Debug, Clone)]
214+
pub struct MakeFastimerDelay(TimeContext);
215+
216+
impl MakeFastimerDelay {
217+
/// Create a new [`MakeFastimerDelay`] with the given [`TimeContext`].
218+
pub fn new(context: TimeContext) -> Self {
219+
MakeFastimerDelay(context)
220+
}
221+
}
222+
223+
impl MakeDelay for MakeFastimerDelay {
224+
type Delay = Delay;
225+
226+
fn delay_util(&self, at: Instant) -> Self::Delay {
227+
self.0.delay_until(at)
228+
}
229+
230+
fn delay(&self, duration: Duration) -> Self::Delay {
231+
self.0.delay(duration)
232+
}
233+
}

0 commit comments

Comments
 (0)