Files
EasyTier/easytier-core/src/gateway/dataplane/deadline.rs
T
KKRainbow d55e63b88e perf(wasi): optimize data plane and extend host ABI to v3 (#2455)
Overhaul the WASI guest data plane for throughput and add the host
capabilities it relies on. The externally driven Tokio runtime now
runs its timer pre-turn only when a tracked deadline has expired,
and all WASI-reachable timers (STUN, port mapping, WebClient, UDP
flow cleanup) go through the portable time facade so conditional
timer driving cannot starve them.

Data plane:

- Move read/write deadlines onto TCP and UDP resources with one ABI
  setter per direction, reuse a single expiration timer per
  resource, and drop timeout arguments from the four hot data-plane
  submissions (ABI v3). Checked absolute instants treat
  unrepresentable finite timeouts as unbounded instead of panicking.
- Batch host traffic: vectored TCP frame writes combine queued
  slices into one host operation, and reads request a bounded 64
  KiB while retaining excess bytes in the stream buffer.
- Complete TCP writes inside the guest with cancellation-safe
  writes, reporting the completed prefix before honoring
  cancellation or timeout so hosts never replay bytes.
- Repoll smoltcp egress immediately on zero poll delay, enlarge
  virtual UDP receive queues to 128 KiB payload with 128 metadata
  slots, and bound UDP session receive buffers to 8 KiB plus one
  byte while keeping oversized-datagram detection.

Host integration:

- Add optional algorithm-neutral AEAD seal/open imports with the
  ring backend as fallback, and pin the ring AES-128-GCM wire vector
  so the Go host stays interoperable.
- Forward instance events to hosts through one best-effort,
  synchronous, non-blocking import.
- Add a repository-owned build entry point for the Go host artifact:
  Binaryen 131 at -O4 with cached, SHA-256-verified official
  archives.
2026-07-28 00:29:08 +08:00

216 lines
6.2 KiB
Rust

//! Deadlines for data-plane control operations and persistent I/O resources.
use std::{future::Future, sync::Mutex, time::Duration};
use quanta::Instant;
use tokio::{sync::watch, task::JoinHandle};
use tokio_util::sync::CancellationToken;
use crate::foundation::time;
use super::{DataPlaneError, DataPlaneResult};
#[derive(Clone, Copy, Debug)]
pub(super) struct DataPlaneDeadline(Option<Instant>);
impl DataPlaneDeadline {
pub(super) fn from_timeout(timeout: Duration) -> Self {
Self(Instant::now().checked_add(timeout))
}
pub(super) fn from_optional_timeout(timeout: Option<Duration>) -> Self {
match timeout {
Some(timeout) => Self::from_timeout(timeout),
None => Self(None),
}
}
pub(super) fn remaining(self) -> DataPlaneResult<Option<Duration>> {
let Some(deadline) = self.0 else {
return Ok(None);
};
let now = Instant::now();
if now >= deadline {
return Err(DataPlaneError::deadline_exceeded());
}
Ok(Some(deadline - now))
}
pub(super) async fn run<T, E>(
self,
future: impl Future<Output = Result<T, E>>,
) -> DataPlaneResult<T>
where
E: Into<DataPlaneError>,
{
match self.remaining()? {
Some(remaining) => time::timeout(remaining, future)
.await
.map_err(|_| DataPlaneError::deadline_exceeded())?
.map_err(Into::into),
None => future.await.map_err(Into::into),
}
}
}
pub(super) struct DataPlaneIoDeadline {
generation: watch::Sender<CancellationToken>,
timer: Mutex<Option<JoinHandle<()>>>,
}
impl Default for DataPlaneIoDeadline {
fn default() -> Self {
Self {
generation: watch::channel(CancellationToken::new()).0,
timer: Mutex::new(None),
}
}
}
impl DataPlaneIoDeadline {
pub(super) fn set_timeout(&self, timeout: Option<Duration>) {
let mut timer = self.timer.lock().unwrap_or_else(|error| error.into_inner());
if let Some(timer) = timer.take() {
timer.abort();
}
let expired = CancellationToken::new();
if let Some(timeout) = timeout {
if timeout.is_zero() {
expired.cancel();
} else {
if let Some(deadline) = time::Instant::now().checked_add(timeout) {
let sleep = time::sleep_until(deadline);
let expiration = expired.clone();
*timer = Some(tokio::spawn(async move {
sleep.await;
expiration.cancel();
}));
}
}
}
self.generation.send_replace(expired);
}
pub(super) async fn run<T, E>(
&self,
cancel: CancellationToken,
future: impl Future<Output = Result<T, E>>,
) -> DataPlaneResult<T>
where
E: Into<DataPlaneError>,
{
let mut deadline = self.generation.subscribe();
tokio::pin!(future);
loop {
let expired = deadline.borrow_and_update().clone();
tokio::select! {
biased;
_ = cancel.cancelled() => {
return Err(DataPlaneError::new(
super::DataPlaneErrorKind::Cancelled,
"data-plane operation cancelled",
));
}
changed = deadline.changed() => {
if changed.is_err() {
return Err(DataPlaneError::new(
super::DataPlaneErrorKind::HandleClosed,
"data-plane resource is closed",
));
}
}
_ = expired.cancelled() => {
return Err(DataPlaneError::deadline_exceeded());
}
result = &mut future => return result.map_err(Into::into),
}
}
}
}
impl Drop for DataPlaneIoDeadline {
fn drop(&mut self) {
if let Some(timer) = self
.timer
.get_mut()
.unwrap_or_else(|error| error.into_inner())
.take()
{
timer.abort();
}
}
}
#[cfg(test)]
mod tests {
use std::{future, sync::Arc};
use super::*;
#[tokio::test]
async fn io_deadline_updates_an_active_operation() {
let deadline = Arc::new(DataPlaneIoDeadline::default());
let operation = {
let deadline = deadline.clone();
tokio::spawn(async move {
deadline
.run(
CancellationToken::new(),
future::pending::<Result<(), DataPlaneError>>(),
)
.await
})
};
tokio::task::yield_now().await;
deadline.set_timeout(Some(Duration::ZERO));
assert_eq!(
operation.await.unwrap().unwrap_err().kind(),
super::super::DataPlaneErrorKind::DeadlineExceeded
);
}
#[tokio::test]
async fn io_deadline_expires_future_operations_until_cleared() {
let deadline = DataPlaneIoDeadline::default();
deadline.set_timeout(Some(Duration::ZERO));
assert_eq!(
deadline
.run(
CancellationToken::new(),
future::ready(Ok::<_, DataPlaneError>(())),
)
.await
.unwrap_err()
.kind(),
super::super::DataPlaneErrorKind::DeadlineExceeded
);
deadline.set_timeout(None);
deadline
.run(
CancellationToken::new(),
future::ready(Ok::<_, DataPlaneError>(())),
)
.await
.unwrap();
}
#[tokio::test]
async fn io_deadline_treats_unrepresentable_timeout_as_unbounded() {
let deadline = DataPlaneIoDeadline::default();
deadline.set_timeout(Some(Duration::from_millis(u64::MAX - 1)));
deadline
.run(
CancellationToken::new(),
future::ready(Ok::<_, DataPlaneError>(())),
)
.await
.unwrap();
}
}