mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-12 23:36:14 -08:00
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.
216 lines
6.2 KiB
Rust
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();
|
|
}
|
|
}
|