mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-15 17:16:14 -08:00
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.
This commit is contained in:
1 parent
7b506e25a7
commit
d55e63b88e
40 files changed
+1342
-220
No files matched your search
@@ -14,6 +14,7 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt, io,
|
||||
io::IoSlice,
|
||||
net::SocketAddr,
|
||||
pin::Pin,
|
||||
sync::{Arc, LazyLock, Mutex, atomic::Ordering},
|
||||
@@ -296,6 +297,8 @@ pub struct HostTcpStream {
|
||||
closed: bool,
|
||||
}
|
||||
|
||||
const HOST_TCP_READ_CAPACITY: usize = 64 * 1024;
|
||||
|
||||
impl HostTcpStream {
|
||||
fn close(&mut self) -> io::Result<()> {
|
||||
if self.closed {
|
||||
@@ -352,6 +355,40 @@ impl HostTcpStream {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_submit_write(
|
||||
&mut self,
|
||||
context: &mut Context<'_>,
|
||||
buffer: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
if self.closed {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::BrokenPipe,
|
||||
"host TCP stream is closed",
|
||||
)));
|
||||
}
|
||||
if buffer.is_empty() {
|
||||
return Poll::Ready(Ok(0));
|
||||
}
|
||||
|
||||
match self.poll_write_completion(context) {
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
|
||||
Poll::Ready(Ok(())) => {}
|
||||
}
|
||||
|
||||
let operation = self.runtime.next_operation();
|
||||
if let Err(error) = self.io.submit_write(self.handle, operation, buffer) {
|
||||
return Poll::Ready(Err(error));
|
||||
}
|
||||
self.write_operation = Some(PendingHostOperation::new(
|
||||
self.runtime.clone(),
|
||||
self.io.clone(),
|
||||
operation,
|
||||
|io, operation| io.cancel_operation(operation),
|
||||
));
|
||||
Poll::Ready(Ok(buffer.len()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for HostTcpStream {
|
||||
@@ -383,10 +420,11 @@ impl AsyncRead for HostTcpStream {
|
||||
loop {
|
||||
if self.read_operation.is_none() {
|
||||
let operation = self.runtime.next_operation();
|
||||
if let Err(error) = self
|
||||
.io
|
||||
.submit_read(self.handle, operation, buffer.remaining())
|
||||
{
|
||||
if let Err(error) = self.io.submit_read(
|
||||
self.handle,
|
||||
operation,
|
||||
buffer.remaining().max(HOST_TCP_READ_CAPACITY),
|
||||
) {
|
||||
return Poll::Ready(Err(error));
|
||||
}
|
||||
self.read_operation = Some(PendingHostOperation::new(
|
||||
@@ -431,33 +469,31 @@ impl AsyncWrite for HostTcpStream {
|
||||
context: &mut Context<'_>,
|
||||
buffer: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
if self.closed {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::BrokenPipe,
|
||||
"host TCP stream is closed",
|
||||
)));
|
||||
self.poll_submit_write(context, buffer)
|
||||
}
|
||||
|
||||
fn poll_write_vectored(
|
||||
mut self: Pin<&mut Self>,
|
||||
context: &mut Context<'_>,
|
||||
buffers: &[IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
if buffers.len() == 1 {
|
||||
return self.poll_submit_write(context, &buffers[0]);
|
||||
}
|
||||
if buffer.is_empty() {
|
||||
return Poll::Ready(Ok(0));
|
||||
let length = buffers.iter().map(|buffer| buffer.len()).sum();
|
||||
if length == 0 {
|
||||
return self.poll_submit_write(context, &[]);
|
||||
}
|
||||
|
||||
match self.poll_write_completion(context) {
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
|
||||
Poll::Ready(Ok(())) => {}
|
||||
let mut combined = Vec::with_capacity(length);
|
||||
for buffer in buffers {
|
||||
combined.extend_from_slice(buffer);
|
||||
}
|
||||
self.poll_submit_write(context, &combined)
|
||||
}
|
||||
|
||||
let operation = self.runtime.next_operation();
|
||||
if let Err(error) = self.io.submit_write(self.handle, operation, buffer) {
|
||||
return Poll::Ready(Err(error));
|
||||
}
|
||||
self.write_operation = Some(PendingHostOperation::new(
|
||||
self.runtime.clone(),
|
||||
self.io.clone(),
|
||||
operation,
|
||||
|io, operation| io.cancel_operation(operation),
|
||||
));
|
||||
Poll::Ready(Ok(buffer.len()))
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn poll_flush(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||
@@ -513,7 +549,10 @@ mod tests {
|
||||
}
|
||||
|
||||
enum TestOperation {
|
||||
Read(Option<io::Result<Vec<u8>>>),
|
||||
Read {
|
||||
capacity: usize,
|
||||
result: Option<io::Result<Vec<u8>>>,
|
||||
},
|
||||
Write {
|
||||
source: Vec<u8>,
|
||||
result: Option<io::Result<()>>,
|
||||
@@ -536,7 +575,7 @@ mod tests {
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find_map(|(id, operation)| match (read, operation) {
|
||||
(true, TestOperation::Read(_)) | (false, TestOperation::Write { .. }) => {
|
||||
(true, TestOperation::Read { .. }) | (false, TestOperation::Write { .. }) => {
|
||||
Some(*id)
|
||||
}
|
||||
_ => None,
|
||||
@@ -552,9 +591,17 @@ mod tests {
|
||||
source.clone()
|
||||
}
|
||||
|
||||
fn read_capacity(&self, operation: HostOperationId) -> usize {
|
||||
let operations = self.operations.lock().unwrap();
|
||||
let TestOperation::Read { capacity, .. } = operations.get(&operation).unwrap() else {
|
||||
panic!("operation is not a read");
|
||||
};
|
||||
*capacity
|
||||
}
|
||||
|
||||
fn complete_read(&self, operation: HostOperationId, data: Vec<u8>) {
|
||||
let mut operations = self.operations.lock().unwrap();
|
||||
let TestOperation::Read(result) = operations.get_mut(&operation).unwrap() else {
|
||||
let TestOperation::Read { result, .. } = operations.get_mut(&operation).unwrap() else {
|
||||
panic!("operation is not a read");
|
||||
};
|
||||
*result = Some(Ok(data));
|
||||
@@ -597,12 +644,15 @@ mod tests {
|
||||
&self,
|
||||
_handle: HostSocketHandle,
|
||||
operation: HostOperationId,
|
||||
_capacity: usize,
|
||||
capacity: usize,
|
||||
) -> io::Result<()> {
|
||||
self.operations
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(operation, TestOperation::Read(None));
|
||||
self.operations.lock().unwrap().insert(
|
||||
operation,
|
||||
TestOperation::Read {
|
||||
capacity,
|
||||
result: None,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -617,7 +667,7 @@ mod tests {
|
||||
return Poll::Pending;
|
||||
}
|
||||
let mut operations = self.operations.lock().unwrap();
|
||||
let Some(TestOperation::Read(result)) = operations.get_mut(&operation) else {
|
||||
let Some(TestOperation::Read { result, .. }) = operations.get_mut(&operation) else {
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"read operation is missing",
|
||||
@@ -714,6 +764,42 @@ mod tests {
|
||||
assert!(io.closed.lock().unwrap().contains(&HostSocketHandle(7)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn small_reads_submit_one_bounded_read_ahead_operation() {
|
||||
let io = Arc::new(TestHostIo::default());
|
||||
let (runtime, mut stream) = test_stream(io.clone());
|
||||
let mut first = [0_u8; 1];
|
||||
let mut read = Box::pin(stream.read(&mut first));
|
||||
assert!(futures::poll!(&mut read).is_pending());
|
||||
|
||||
let operation = io.operation(true);
|
||||
assert_eq!(io.read_capacity(operation), HOST_TCP_READ_CAPACITY);
|
||||
io.complete_read(operation, b"abc".to_vec());
|
||||
runtime.notify_completions();
|
||||
assert_eq!(read.await.unwrap(), 1);
|
||||
assert_eq!(&first, b"a");
|
||||
|
||||
let mut remainder = [0_u8; 2];
|
||||
stream.read_exact(&mut remainder).await.unwrap();
|
||||
assert_eq!(&remainder, b"bc");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn vectored_write_submits_one_ordered_host_operation() {
|
||||
let io = Arc::new(TestHostIo::default());
|
||||
let (runtime, mut stream) = test_stream(io.clone());
|
||||
assert!(stream.is_write_vectored());
|
||||
|
||||
let buffers = [IoSlice::new(b"one"), IoSlice::new(b"two")];
|
||||
assert_eq!(stream.write_vectored(&buffers).await.unwrap(), 6);
|
||||
let operation = io.operation(false);
|
||||
assert_eq!(io.write_source(operation), b"onetwo");
|
||||
|
||||
io.complete_write(operation);
|
||||
runtime.notify_completions();
|
||||
stream.shutdown().await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cancelled_read_keeps_owned_completion_remainder() {
|
||||
let io = Arc::new(TestHostIo::default());
|
||||
|
||||
Reference in new issue
Block a user