mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-09-04 10:05:42 +00: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:
@@ -15,11 +15,22 @@
|
||||
/// WebAssembly import module a WASI runtime must implement.
|
||||
pub const HOST_IMPORT_MODULE: &str = "easytier_host";
|
||||
|
||||
/// AEAD algorithm identifiers accepted by the optional crypto imports.
|
||||
pub const AEAD_AES_128_GCM: u32 = 1;
|
||||
pub const AEAD_AES_256_GCM: u32 = 2;
|
||||
pub const AEAD_CHACHA20_POLY1305: u32 = 3;
|
||||
|
||||
/// The Host could not authenticate an AEAD record.
|
||||
///
|
||||
/// Unlike other non-zero crypto statuses, this must not fall back to the
|
||||
/// built-in implementation because an in-place open may have changed bytes.
|
||||
pub const HOST_CRYPTO_AUTH_FAILED: i32 = -10;
|
||||
|
||||
/// Version of the JSON document accepted by `easytier_instance_create`.
|
||||
pub const CORE_INSTANCE_CONFIG_VERSION: u32 = 14;
|
||||
|
||||
/// Version of the public data-plane guest export contract.
|
||||
pub const DATA_PLANE_ABI_VERSION: u32 = 2;
|
||||
pub const DATA_PLANE_ABI_VERSION: u32 = 3;
|
||||
|
||||
/// The guest exposes an instance-scoped data-plane operation broker.
|
||||
pub const DATA_PLANE_CAPABILITY: u64 = 1 << 0;
|
||||
@@ -27,6 +38,10 @@ pub const DATA_PLANE_CAPABILITY: u64 = 1 << 0;
|
||||
pub const DATA_PLANE_TCP_CAPABILITY: u64 = 1 << 1;
|
||||
/// The guest data plane supports UDP sockets.
|
||||
pub const DATA_PLANE_UDP_CAPABILITY: u64 = 1 << 2;
|
||||
/// Update the read deadline in `easytier_data_plane_resource_deadline_set`.
|
||||
pub const DATA_PLANE_DEADLINE_READ: u32 = 1 << 0;
|
||||
/// Update the write deadline in `easytier_data_plane_resource_deadline_set`.
|
||||
pub const DATA_PLANE_DEADLINE_WRITE: u32 = 1 << 1;
|
||||
|
||||
/// Guest exports a WASI runtime calls to manage a core instance.
|
||||
///
|
||||
@@ -68,6 +83,7 @@ pub const DATA_PLANE_GUEST_EXPORTS: &[&str] = &[
|
||||
"easytier_data_plane_udp_bind_submit",
|
||||
"easytier_data_plane_udp_receive_submit",
|
||||
"easytier_data_plane_udp_send_submit",
|
||||
"easytier_data_plane_resource_deadline_set",
|
||||
// Completion, result, and resource lifecycle.
|
||||
"easytier_data_plane_completion_drain",
|
||||
"easytier_data_plane_result_size",
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
use crate::{
|
||||
events::{CoreEvent, CoreEventSink},
|
||||
wasi::imports::emit_event,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WasiHostEventSink {
|
||||
handle: u64,
|
||||
}
|
||||
|
||||
impl WasiHostEventSink {
|
||||
pub fn new(handle: u64) -> Self {
|
||||
Self { handle }
|
||||
}
|
||||
}
|
||||
|
||||
impl CoreEventSink for WasiHostEventSink {
|
||||
fn emit(&self, event: CoreEvent) {
|
||||
let kind = event_kind(&event);
|
||||
let message = format!("{event:?}");
|
||||
let _ = unsafe {
|
||||
emit_event(
|
||||
self.handle,
|
||||
kind.as_ptr() as u32,
|
||||
kind.len() as u32,
|
||||
message.as_ptr() as u32,
|
||||
message.len() as u32,
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn event_kind(event: &CoreEvent) -> &'static str {
|
||||
match event {
|
||||
CoreEvent::PeerAdded(_) => "peer_added",
|
||||
CoreEvent::PeerRemoved(_) => "peer_removed",
|
||||
CoreEvent::PeerConnAdded(_) => "peer_connection_added",
|
||||
CoreEvent::PeerConnRemoved(_) => "peer_connection_removed",
|
||||
CoreEvent::CredentialChanged => "credential_changed",
|
||||
CoreEvent::ManualConnecting { .. } => "connecting",
|
||||
CoreEvent::ManualConnectError { .. } => "connect_error",
|
||||
CoreEvent::ListenerPlanFailed { .. } => "listener_plan_failed",
|
||||
CoreEvent::ListenerAdded { .. } => "listener_added",
|
||||
CoreEvent::ListenerRemoved { .. } => "listener_removed",
|
||||
CoreEvent::ListenerAddFailed { .. } => "listener_add_failed",
|
||||
CoreEvent::ListenerAcceptFailed { .. } => "listener_accept_failed",
|
||||
CoreEvent::ListenerSocketAccepted { .. } => "listener_socket_accepted",
|
||||
CoreEvent::ListenerAcceptedSocketHandleFailed { .. } => "listener_socket_handle_failed",
|
||||
CoreEvent::TunnelAccepted { .. } => "tunnel_accepted",
|
||||
CoreEvent::TunnelAdmissionFailed { .. } => "tunnel_admission_failed",
|
||||
CoreEvent::UdpPortMappingEstablished { .. } => "udp_port_mapping_established",
|
||||
CoreEvent::ProxyCidrsUpdated { .. } => "proxy_cidrs_updated",
|
||||
CoreEvent::PublicIpv6LeaseChanged { .. } => "public_ipv6_lease_changed",
|
||||
CoreEvent::PublicIpv6RoutesChanged { .. } => "public_ipv6_routes_changed",
|
||||
CoreEvent::VpnPortalStarted(_) => "vpn_portal_started",
|
||||
CoreEvent::VpnPortalClientConnected { .. } => "vpn_portal_client_connected",
|
||||
CoreEvent::VpnPortalClientDisconnected { .. } => "vpn_portal_client_disconnected",
|
||||
CoreEvent::GatewayPortForwardAdded(_) => "gateway_port_forward_added",
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,6 @@
|
||||
|
||||
pub mod dns;
|
||||
pub mod environment;
|
||||
pub mod event;
|
||||
pub mod packet;
|
||||
pub mod socket;
|
||||
|
||||
@@ -9,6 +9,57 @@ pub(crate) const HOST_WOULD_BLOCK: i32 = -5;
|
||||
|
||||
#[link(wasm_import_module = "easytier_host")]
|
||||
unsafe extern "C" {
|
||||
/// Emits one best-effort instance event after the host copies both strings.
|
||||
///
|
||||
/// The host must not block the guest. A non-zero status drops this event
|
||||
/// without affecting core execution.
|
||||
pub(crate) fn emit_event(
|
||||
handle: u64,
|
||||
kind: u32,
|
||||
kind_len: u32,
|
||||
message: u32,
|
||||
message_len: u32,
|
||||
) -> i32;
|
||||
|
||||
/// Encrypts `text_len` bytes in place and writes the AEAD tag immediately
|
||||
/// after them.
|
||||
///
|
||||
/// The guest reserves the algorithm's tag size in linear memory before
|
||||
/// calling. Every non-zero result except
|
||||
/// [`crate::wasi::abi::HOST_CRYPTO_AUTH_FAILED`] must leave the buffer
|
||||
/// unchanged so the guest can use its built-in implementation.
|
||||
#[cfg(feature = "wasi-crypto-offload")]
|
||||
pub(crate) fn crypto_aead_seal(
|
||||
algorithm: u32,
|
||||
key: u32,
|
||||
key_len: u32,
|
||||
nonce: u32,
|
||||
nonce_len: u32,
|
||||
aad: u32,
|
||||
aad_len: u32,
|
||||
buffer: u32,
|
||||
text_len: u32,
|
||||
) -> i32;
|
||||
|
||||
/// Authenticates and decrypts `text_len` bytes in place using the AEAD tag
|
||||
/// immediately after them.
|
||||
///
|
||||
/// Authentication failure may change the buffer and must return
|
||||
/// [`crate::wasi::abi::HOST_CRYPTO_AUTH_FAILED`]. Every other non-zero
|
||||
/// result must leave the buffer unchanged so the guest can fall back.
|
||||
#[cfg(feature = "wasi-crypto-offload")]
|
||||
pub(crate) fn crypto_aead_open(
|
||||
algorithm: u32,
|
||||
key: u32,
|
||||
key_len: u32,
|
||||
nonce: u32,
|
||||
nonce_len: u32,
|
||||
aad: u32,
|
||||
aad_len: u32,
|
||||
buffer: u32,
|
||||
text_len: u32,
|
||||
) -> i32;
|
||||
|
||||
/// Starts one TCP read into a host-owned pending operation.
|
||||
///
|
||||
/// The host records at most `capacity` bytes for `operation` and must not
|
||||
|
||||
@@ -31,20 +31,18 @@ pub(super) fn new_wasi_core_runtime(
|
||||
process_runtime: std::sync::Arc<crate::process_runtime::CoreProcessRuntime>,
|
||||
environment_snapshot: HostConnectorEnvironmentSnapshot,
|
||||
packet_sink: crate::host::packet::HostPacketSinkHandle,
|
||||
event_sink: u64,
|
||||
) -> anyhow::Result<WasiCoreRuntime> {
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::host::{
|
||||
dns::HostDnsResolver,
|
||||
packet::{HostPacket, HostPacketSink},
|
||||
socket::HostSocketRuntime,
|
||||
};
|
||||
use crate::host::{dns::HostDnsResolver, packet::HostPacketSink, socket::HostSocketRuntime};
|
||||
use crate::{
|
||||
connectivity::connector_host::new_connector_host,
|
||||
instance::{CoreHostAdapters, CoreInstance},
|
||||
wasi::adapter::{
|
||||
dns::WasiHostDnsIo, environment::WasiHostConnectorEnvironmentIo,
|
||||
packet::WasiHostPacketIo, socket::backend::WasiHostSocketBackend,
|
||||
event::WasiHostEventSink, packet::WasiHostPacketIo,
|
||||
socket::backend::WasiHostSocketBackend,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -64,7 +62,8 @@ pub(super) fn new_wasi_core_runtime(
|
||||
Arc::new(WasiHostPacketIo),
|
||||
packet_sink,
|
||||
));
|
||||
let adapters = CoreHostAdapters::new(host, dns, packet_sink, process_runtime);
|
||||
let mut adapters = CoreHostAdapters::new(host, dns, packet_sink, process_runtime);
|
||||
adapters.events = Arc::new(WasiHostEventSink::new(event_sink));
|
||||
let core = CoreInstance::from_toml(config, adapters)?;
|
||||
|
||||
Ok(WasiCoreRuntime {
|
||||
@@ -85,7 +84,7 @@ mod abi {
|
||||
use crate::{
|
||||
config::toml::{ConfigLoader as _, TomlConfig},
|
||||
foundation::time::{clear_domain, enter_domain, next_deadline_millis},
|
||||
host::packet::HostPacketSinkHandle,
|
||||
host::packet::{HostPacket, HostPacketSinkHandle},
|
||||
instance::{
|
||||
CoreInstanceState,
|
||||
manager::{InstanceFactory, ManagedInstance},
|
||||
@@ -164,6 +163,7 @@ mod abi {
|
||||
domain: u64,
|
||||
environment: crate::connectivity::connector_host::HostConnectorEnvironmentSnapshot,
|
||||
packet_sink: HostPacketSinkHandle,
|
||||
event_sink: u64,
|
||||
}
|
||||
|
||||
struct WasiInstance {
|
||||
@@ -220,6 +220,7 @@ mod abi {
|
||||
self.process_runtime.clone(),
|
||||
context.environment,
|
||||
context.packet_sink,
|
||||
context.event_sink,
|
||||
)?
|
||||
};
|
||||
|
||||
@@ -348,8 +349,11 @@ mod abi {
|
||||
|
||||
fn drive(&self) -> anyhow::Result<()> {
|
||||
let _domain = enter_domain(self.domain);
|
||||
let advance_timers = next_deadline_millis(self.domain) == Some(0);
|
||||
let mut execution = self.execution.lock().unwrap();
|
||||
execution.drive_again = execution.runtime_driver.drive(&execution.runtime)
|
||||
execution.drive_again = execution
|
||||
.runtime_driver
|
||||
.drive(&execution.runtime, advance_timers)
|
||||
== RuntimeDriveOutcome::BudgetExhausted;
|
||||
|
||||
if execution
|
||||
@@ -561,13 +565,15 @@ mod abi {
|
||||
#[unsafe(no_mangle)]
|
||||
/// Creates one core instance from a versioned envelope containing TOML.
|
||||
///
|
||||
/// `config_pointer` must name a live ABI buffer and `packet_sink_handle`
|
||||
/// identifies the host sink used for locally delivered raw IP packets.
|
||||
/// `config_pointer` must name a live ABI buffer. `packet_sink_handle` and
|
||||
/// `event_sink_handle` identify the host sinks used for locally delivered
|
||||
/// raw IP packets and best-effort instance events.
|
||||
/// Returns zero on failure; retrieve the reason through the error exports.
|
||||
pub extern "C" fn easytier_instance_create(
|
||||
config_pointer: u32,
|
||||
config_length: u32,
|
||||
packet_sink_handle: u64,
|
||||
event_sink_handle: u64,
|
||||
) -> u64 {
|
||||
let encoded = match read_guest_buffer(config_pointer, config_length, MAX_CREATE_CONFIG_LEN)
|
||||
{
|
||||
@@ -601,6 +607,7 @@ mod abi {
|
||||
domain: handle,
|
||||
environment: create_config.environment,
|
||||
packet_sink: HostPacketSinkHandle(packet_sink_handle),
|
||||
event_sink: event_sink_handle,
|
||||
},
|
||||
);
|
||||
let instance = match instance {
|
||||
|
||||
@@ -9,8 +9,8 @@ use crate::{
|
||||
},
|
||||
wasi::{
|
||||
abi::{
|
||||
DATA_PLANE_ABI_VERSION, DATA_PLANE_CAPABILITY, DATA_PLANE_TCP_CAPABILITY,
|
||||
DATA_PLANE_UDP_CAPABILITY,
|
||||
DATA_PLANE_ABI_VERSION, DATA_PLANE_CAPABILITY, DATA_PLANE_DEADLINE_READ,
|
||||
DATA_PLANE_DEADLINE_WRITE, DATA_PLANE_TCP_CAPABILITY, DATA_PLANE_UDP_CAPABILITY,
|
||||
},
|
||||
wire::{
|
||||
data_plane::{
|
||||
@@ -42,12 +42,10 @@ impl WasiInstance {
|
||||
self.core.core().data_plane_session()
|
||||
}
|
||||
|
||||
fn submit_data_plane(
|
||||
fn submit_data_plane<T>(
|
||||
&self,
|
||||
submit: impl FnOnce(
|
||||
&std::sync::Arc<WasiDataPlaneSession>,
|
||||
) -> Result<DataPlaneOperationId, DataPlaneError>,
|
||||
) -> Result<DataPlaneOperationId, DataPlaneError> {
|
||||
submit: impl FnOnce(&std::sync::Arc<WasiDataPlaneSession>) -> Result<T, DataPlaneError>,
|
||||
) -> Result<T, DataPlaneError> {
|
||||
let execution = self.execution.lock().unwrap();
|
||||
let _domain = crate::foundation::time::enter_domain(self.domain);
|
||||
let _runtime = execution.runtime.enter();
|
||||
@@ -316,7 +314,6 @@ pub extern "C" fn easytier_data_plane_tcp_read_submit(
|
||||
handle: u64,
|
||||
stream: u64,
|
||||
max_len: u32,
|
||||
timeout_ms: u64,
|
||||
output_operation: u32,
|
||||
) -> i32 {
|
||||
let stream = match resource_id(stream) {
|
||||
@@ -327,9 +324,7 @@ pub extern "C" fn easytier_data_plane_tcp_read_submit(
|
||||
}
|
||||
};
|
||||
submit_operation(handle, output_operation, |instance| {
|
||||
instance.submit_data_plane(|session| {
|
||||
session.submit_tcp_read(stream, max_len as usize, timeout(timeout_ms))
|
||||
})
|
||||
instance.submit_data_plane(|session| session.submit_tcp_read(stream, max_len as usize))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -339,7 +334,6 @@ pub extern "C" fn easytier_data_plane_tcp_write_submit(
|
||||
stream: u64,
|
||||
data_pointer: u32,
|
||||
data_length: u32,
|
||||
timeout_ms: u64,
|
||||
output_operation: u32,
|
||||
) -> i32 {
|
||||
let stream = match resource_id(stream) {
|
||||
@@ -357,9 +351,7 @@ pub extern "C" fn easytier_data_plane_tcp_write_submit(
|
||||
}
|
||||
};
|
||||
submit_operation(handle, output_operation, |instance| {
|
||||
instance.submit_data_plane(|session| {
|
||||
session.submit_tcp_write(stream, data, timeout(timeout_ms))
|
||||
})
|
||||
instance.submit_data_plane(|session| session.submit_tcp_write(stream, data))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -388,7 +380,6 @@ pub extern "C" fn easytier_data_plane_udp_receive_submit(
|
||||
handle: u64,
|
||||
socket: u64,
|
||||
max_len: u32,
|
||||
timeout_ms: u64,
|
||||
output_operation: u32,
|
||||
) -> i32 {
|
||||
let socket = match resource_id(socket) {
|
||||
@@ -399,9 +390,7 @@ pub extern "C" fn easytier_data_plane_udp_receive_submit(
|
||||
}
|
||||
};
|
||||
submit_operation(handle, output_operation, |instance| {
|
||||
instance.submit_data_plane(|session| {
|
||||
session.submit_udp_receive(socket, max_len as usize, timeout(timeout_ms))
|
||||
})
|
||||
instance.submit_data_plane(|session| session.submit_udp_receive(socket, max_len as usize))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -412,7 +401,6 @@ pub extern "C" fn easytier_data_plane_udp_send_submit(
|
||||
peer_address: u32,
|
||||
data_pointer: u32,
|
||||
data_length: u32,
|
||||
timeout_ms: u64,
|
||||
output_operation: u32,
|
||||
) -> i32 {
|
||||
let socket = match resource_id(socket) {
|
||||
@@ -437,9 +425,36 @@ pub extern "C" fn easytier_data_plane_udp_send_submit(
|
||||
}
|
||||
};
|
||||
submit_operation(handle, output_operation, |instance| {
|
||||
instance.submit_data_plane(|session| session.submit_udp_send(socket, peer_address, data))
|
||||
})
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn easytier_data_plane_resource_deadline_set(
|
||||
handle: u64,
|
||||
resource: u64,
|
||||
direction: u32,
|
||||
timeout_ms: u64,
|
||||
) -> i32 {
|
||||
let resource = match resource_id(resource) {
|
||||
Ok(resource) => resource,
|
||||
Err(error) => {
|
||||
set_instance_error(handle, error.message());
|
||||
return error_status(error.kind());
|
||||
}
|
||||
};
|
||||
let read = direction & DATA_PLANE_DEADLINE_READ != 0;
|
||||
let write = direction & DATA_PLANE_DEADLINE_WRITE != 0;
|
||||
if direction == 0 || direction & !(DATA_PLANE_DEADLINE_READ | DATA_PLANE_DEADLINE_WRITE) != 0 {
|
||||
let error = invalid_input(format!("invalid deadline direction {direction}"));
|
||||
set_instance_error(handle, error.message());
|
||||
return error_status(error.kind());
|
||||
}
|
||||
data_plane_call(handle, |instance| {
|
||||
instance.submit_data_plane(|session| {
|
||||
session.submit_udp_send(socket, peer_address, data, timeout(timeout_ms))
|
||||
})
|
||||
session.set_resource_deadline(resource, read, write, timeout(timeout_ms))
|
||||
})?;
|
||||
Ok(0)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -41,12 +41,14 @@ impl RuntimeDriver {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn drive(&self, runtime: &Runtime) -> RuntimeDriveOutcome {
|
||||
// First give the timer driver a non-blocking turn. The quiescence hook
|
||||
// stays disabled here so an expired timer can wake its task.
|
||||
runtime.block_on(async {
|
||||
tokio::time::sleep(Duration::ZERO).await;
|
||||
});
|
||||
pub(super) fn drive(&self, runtime: &Runtime, advance_timers: bool) -> RuntimeDriveOutcome {
|
||||
if advance_timers {
|
||||
// Give the timer driver a turn before enabling the quiescence hook
|
||||
// so an expired timer can wake its task.
|
||||
runtime.block_on(async {
|
||||
tokio::time::sleep(Duration::ZERO).await;
|
||||
});
|
||||
}
|
||||
|
||||
let _active = RuntimeDriverGuard::activate(self.state.as_ref());
|
||||
runtime.block_on(async {
|
||||
@@ -101,9 +103,10 @@ impl Drop for RuntimeDriverGuard<'_> {
|
||||
mod tests {
|
||||
use std::{future::poll_fn, sync::Arc, task::Poll};
|
||||
|
||||
use tokio::{runtime::Builder, sync::Notify};
|
||||
use tokio::{runtime::Builder, sync::Notify, time::Duration};
|
||||
|
||||
use super::{RuntimeDriveOutcome, RuntimeDriver};
|
||||
use crate::wasi::time::{enter_domain, next_deadline_millis};
|
||||
|
||||
fn runtime(driver: &RuntimeDriver) -> tokio::runtime::Runtime {
|
||||
let park_driver = driver.clone();
|
||||
@@ -124,10 +127,13 @@ mod tests {
|
||||
Poll::<()>::Pending
|
||||
}));
|
||||
|
||||
assert_eq!(driver.drive(&runtime), RuntimeDriveOutcome::BudgetExhausted);
|
||||
assert_eq!(
|
||||
driver.drive(&runtime, false),
|
||||
RuntimeDriveOutcome::BudgetExhausted
|
||||
);
|
||||
|
||||
task.abort();
|
||||
while driver.drive(&runtime) == RuntimeDriveOutcome::BudgetExhausted {}
|
||||
while driver.drive(&runtime, false) == RuntimeDriveOutcome::BudgetExhausted {}
|
||||
assert!(task.is_finished());
|
||||
}
|
||||
|
||||
@@ -141,11 +147,56 @@ mod tests {
|
||||
task_notify.notified().await;
|
||||
});
|
||||
|
||||
assert_eq!(driver.drive(&runtime), RuntimeDriveOutcome::Quiescent);
|
||||
assert_eq!(
|
||||
driver.drive(&runtime, false),
|
||||
RuntimeDriveOutcome::Quiescent
|
||||
);
|
||||
assert!(!task.is_finished());
|
||||
|
||||
notify.notify_one();
|
||||
while driver.drive(&runtime) == RuntimeDriveOutcome::BudgetExhausted {}
|
||||
while driver.drive(&runtime, false) == RuntimeDriveOutcome::BudgetExhausted {}
|
||||
assert!(task.is_finished());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advances_expired_timers_only_when_requested() {
|
||||
let driver = RuntimeDriver::default();
|
||||
let runtime = runtime(&driver);
|
||||
let task = runtime.spawn(async {
|
||||
tokio::time::sleep(Duration::ZERO).await;
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
driver.drive(&runtime, false),
|
||||
RuntimeDriveOutcome::Quiescent
|
||||
);
|
||||
assert!(!task.is_finished());
|
||||
|
||||
assert_eq!(driver.drive(&runtime, true), RuntimeDriveOutcome::Quiescent);
|
||||
assert!(task.is_finished());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tracked_expired_timer_requests_advancement() {
|
||||
let _domain = enter_domain(7);
|
||||
let driver = RuntimeDriver::default();
|
||||
let runtime = runtime(&driver);
|
||||
let task = runtime.spawn(async {
|
||||
crate::foundation::time::sleep(Duration::ZERO).await;
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
driver.drive(&runtime, false),
|
||||
RuntimeDriveOutcome::Quiescent
|
||||
);
|
||||
assert_eq!(next_deadline_millis(7), Some(0));
|
||||
|
||||
let advance_timers = next_deadline_millis(7) == Some(0);
|
||||
assert_eq!(
|
||||
driver.drive(&runtime, advance_timers),
|
||||
RuntimeDriveOutcome::Quiescent
|
||||
);
|
||||
assert!(task.is_finished());
|
||||
assert_eq!(next_deadline_millis(7), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ mod tracked {
|
||||
}
|
||||
}
|
||||
|
||||
pub use tracked::{Duration, Instant, Interval, error, interval, sleep, timeout};
|
||||
pub use tracked::{Duration, Instant, Interval, error, interval, sleep, sleep_until, timeout};
|
||||
|
||||
pub(crate) use tracked::{clear_domain, enter_domain, next_deadline_millis};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user