From c62d7f9ea1555b8ddb45461e2967358ec4d9e0f3 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 12 Apr 2021 18:11:21 -0700 Subject: [PATCH 01/72] wasmtime_wiggle: when async feature is disabled, run async on dummy executor --- crates/wiggle/wasmtime/Cargo.toml | 5 + crates/wiggle/wasmtime/macro/src/config.rs | 17 +- crates/wiggle/wasmtime/macro/src/lib.rs | 147 +++++++++++++--- crates/wiggle/wasmtime/tests/atoms_sync.rs | 188 +++++++++++++++++++++ 4 files changed, 316 insertions(+), 41 deletions(-) create mode 100644 crates/wiggle/wasmtime/tests/atoms_sync.rs diff --git a/crates/wiggle/wasmtime/Cargo.toml b/crates/wiggle/wasmtime/Cargo.toml index 06f17700ed..f51ea119bb 100644 --- a/crates/wiggle/wasmtime/Cargo.toml +++ b/crates/wiggle/wasmtime/Cargo.toml @@ -26,6 +26,11 @@ name = "atoms_async" path = "tests/atoms_async.rs" required-features = ["async", "wasmtime/wat"] +[[test]] +name = "atoms_sync" +path = "tests/atoms_sync.rs" +required-features = ["wasmtime/wat"] + [badges] maintenance = { status = "actively-developed" } diff --git a/crates/wiggle/wasmtime/macro/src/config.rs b/crates/wiggle/wasmtime/macro/src/config.rs index 76a23dd31f..e37d3492ea 100644 --- a/crates/wiggle/wasmtime/macro/src/config.rs +++ b/crates/wiggle/wasmtime/macro/src/config.rs @@ -16,7 +16,6 @@ pub struct Config { pub witx: WitxConf, pub ctx: CtxConf, pub modules: ModulesConf, - #[cfg(feature = "async")] pub async_: AsyncConf, } @@ -26,7 +25,6 @@ pub enum ConfigField { Witx(WitxConf), Ctx(CtxConf), Modules(ModulesConf), - #[cfg(feature = "async")] Async(AsyncConf), } @@ -67,17 +65,7 @@ impl Parse for ConfigField { } else if lookahead.peek(Token![async]) { input.parse::()?; input.parse::()?; - #[cfg(feature = "async")] - { - Ok(ConfigField::Async(input.parse()?)) - } - #[cfg(not(feature = "async"))] - { - Err(syn::Error::new( - input.span(), - "async not supported, enable cargo feature \"async\"", - )) - } + Ok(ConfigField::Async(input.parse()?)) } else { Err(lookahead.error()) } @@ -90,7 +78,6 @@ impl Config { let mut witx = None; let mut ctx = None; let mut modules = None; - #[cfg(feature = "async")] let mut async_ = None; for f in fields { match f { @@ -118,7 +105,6 @@ impl Config { } modules = Some(c); } - #[cfg(feature = "async")] ConfigField::Async(c) => { if async_.is_some() { return Err(Error::new(err_loc, "duplicate `async` field")); @@ -132,7 +118,6 @@ impl Config { witx: witx.ok_or_else(|| Error::new(err_loc, "`witx` field required"))?, ctx: ctx.ok_or_else(|| Error::new(err_loc, "`ctx` field required"))?, modules: modules.ok_or_else(|| Error::new(err_loc, "`modules` field required"))?, - #[cfg(feature = "async")] async_: async_.unwrap_or_default(), }) } diff --git a/crates/wiggle/wasmtime/macro/src/lib.rs b/crates/wiggle/wasmtime/macro/src/lib.rs index 24732c7f2e..b7bf5543c9 100644 --- a/crates/wiggle/wasmtime/macro/src/lib.rs +++ b/crates/wiggle/wasmtime/macro/src/lib.rs @@ -48,11 +48,6 @@ pub fn wasmtime_integration(args: TokenStream) -> TokenStream { let doc = config.load_document(); let names = Names::new(quote!(wasmtime_wiggle)); - #[cfg(feature = "async")] - let async_config = config.async_.clone(); - #[cfg(not(feature = "async"))] - let async_config = AsyncConf::default(); - let modules = config.modules.iter().map(|(name, module_conf)| { let module = doc .module(&witx::Id::new(name)) @@ -63,7 +58,7 @@ pub fn wasmtime_integration(args: TokenStream) -> TokenStream { &names, &config.target, &config.ctx.name, - &async_config, + &config.async_, ) }); quote!( #(#modules)* ).into() @@ -106,14 +101,22 @@ fn generate_module( let mut ctor_externs = Vec::new(); let mut host_funcs = Vec::new(); + #[cfg(not(feature = "async"))] + let mut requires_dummy_executor = false; + for f in module.funcs() { + let is_async = async_conf.is_async(module.name.as_str(), f.name.as_str()); + #[cfg(not(feature = "async"))] + if is_async { + requires_dummy_executor = true; + } generate_func( &module_id, &f, names, &target_module, ctx_type, - async_conf.is_async(module.name.as_str(), f.name.as_str()), + is_async, &mut fns, &mut ctor_externs, &mut host_funcs, @@ -157,6 +160,15 @@ contained in the `cx` parameter.", } }); + #[cfg(not(feature = "async"))] + let dummy_executor = if requires_dummy_executor { + dummy_executor() + } else { + quote!() + }; + #[cfg(feature = "async")] + let dummy_executor = quote!(); + quote! { #type_docs pub struct #type_name { @@ -219,6 +231,8 @@ contained in the `cx` parameter.", } #(#fns)* + + #dummy_executor } } } @@ -283,8 +297,10 @@ fn generate_func( }); if is_async { - let wrapper = format_ident!("wrap{}_async", params.len()); - ctors.push(quote! { + #[cfg(feature = "async")] + { + let wrapper = format_ident!("wrap{}_async", params.len()); + ctors.push(quote! { let #name_ident = wasmtime::Func::#wrapper( store, ctx.clone(), @@ -294,6 +310,23 @@ fn generate_func( } ); }); + } + + #[cfg(not(feature = "async"))] + { + // Emit a synchronous function. Self::#fn_ident returns a Future, so we need to + // use a dummy executor to let any synchronous code inside there execute correctly. If + // the future ends up Pending, this func will Trap. + ctors.push(quote! { + let my_ctx = ctx.clone(); + let #name_ident = wasmtime::Func::wrap( + store, + move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> { + Self::run_in_dummy_executor(Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*)) + } + ); + }); + } } else { ctors.push(quote! { let my_ctx = ctx.clone(); @@ -307,22 +340,45 @@ fn generate_func( } let host_wrapper = if is_async { - let wrapper = format_ident!("wrap{}_host_func_async", params.len()); - quote! { - config.#wrapper( - module, - field, - move |caller #(,#arg_decls)*| - -> Box>> { - Box::new(async move { - let ctx = caller.store() + #[cfg(feature = "async")] + { + let wrapper = format_ident!("wrap{}_host_func_async", params.len()); + quote! { + config.#wrapper( + module, + field, + move |caller #(,#arg_decls)*| + -> Box>> { + Box::new(async move { + let ctx = caller.store() + .get::>>() + .ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?; + let result = Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*).await; + result + }) + } + ); + } + } + + #[cfg(not(feature = "async"))] + { + // Emit a synchronous host function. Self::#fn_ident returns a Future, so we need to + // use a dummy executor to let any synchronous code inside there execute correctly. If + // the future ends up Pending, this func will Trap. + quote! { + config.wrap_host_func( + module, + field, + move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> { + let ctx = caller + .store() .get::>>() .ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?; - let result = Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*).await; - result - }) - } - ); + Self::run_in_dummy_executor(Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*)) + }, + ); + } } } else { quote! { @@ -334,11 +390,52 @@ fn generate_func( .store() .get::>>() .ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?; - let result = Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*); - result + Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*) }, ); } }; host_funcs.push((func.name.clone(), host_wrapper)); } +#[cfg(not(feature = "async"))] +fn dummy_executor() -> TokenStream2 { + quote! { + fn run_in_dummy_executor(future: F) -> F::Output { + use std::pin::Pin; + use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + + let mut f = Pin::from(Box::new(future)); + let waker = dummy_waker(); + let mut cx = Context::from_waker(&waker); + match f.as_mut().poll(&mut cx) { + Poll::Ready(val) => return val, + Poll::Pending => { + panic!("Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store") + } + } + + fn dummy_waker() -> Waker { + return unsafe { Waker::from_raw(clone(5 as *const _)) }; + + unsafe fn clone(ptr: *const ()) -> RawWaker { + assert_eq!(ptr as usize, 5); + const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); + RawWaker::new(ptr, &VTABLE) + } + + unsafe fn wake(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + + unsafe fn wake_by_ref(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + + unsafe fn drop(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + } + + } + } +} diff --git a/crates/wiggle/wasmtime/tests/atoms_sync.rs b/crates/wiggle/wasmtime/tests/atoms_sync.rs new file mode 100644 index 0000000000..057ebbaa6e --- /dev/null +++ b/crates/wiggle/wasmtime/tests/atoms_sync.rs @@ -0,0 +1,188 @@ +#![allow(unused)] +// These tests are designed to check the behavior with the crate's async feature (& wasmtimes async +// feature) disabled. Run with: +// `cargo test --no-default-features --features wasmtime/wat --test atoms_sync` +#[cfg(feature = "async")] +#[test] +fn these_tests_require_async_feature_disabled() {} + +use std::cell::RefCell; +use std::rc::Rc; + +wasmtime_wiggle::from_witx!({ + witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], + async: { + atoms::{double_int_return_float} + } +}); + +wasmtime_wiggle::wasmtime_integration!({ + target: crate, + witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], + ctx: Ctx, + modules: { atoms => { name: Atoms } }, + async: { + atoms::double_int_return_float + } +}); + +pub struct Ctx; +impl wiggle::GuestErrorType for types::Errno { + fn success() -> Self { + types::Errno::Ok + } +} + +#[wasmtime_wiggle::async_trait] +impl atoms::Atoms for Ctx { + fn int_float_args(&self, an_int: u32, an_float: f32) -> Result<(), types::Errno> { + println!("INT FLOAT ARGS: {} {}", an_int, an_float); + Ok(()) + } + async fn double_int_return_float( + &self, + an_int: u32, + ) -> Result { + Ok((an_int as f32) * 2.0) + } +} + +fn run_int_float_args(linker: &wasmtime::Linker) { + let shim_mod = shim_module(linker.store()); + let shim_inst = linker.instantiate(&shim_mod).unwrap(); + + let results = shim_inst + .get_func("int_float_args_shim") + .unwrap() + .call(&[0i32.into(), 123.45f32.into()]) + .unwrap(); + + assert_eq!(results.len(), 1, "one return value"); + assert_eq!( + results[0].unwrap_i32(), + types::Errno::Ok as i32, + "int_float_args errno" + ); +} + +fn run_double_int_return_float(linker: &wasmtime::Linker) { + let shim_mod = shim_module(linker.store()); + let shim_inst = linker.instantiate(&shim_mod).unwrap(); + + let input: i32 = 123; + let result_location: i32 = 0; + + let results = shim_inst + .get_func("double_int_return_float_shim") + .unwrap() + .call(&[input.into(), result_location.into()]) + .unwrap(); + + assert_eq!(results.len(), 1, "one return value"); + assert_eq!( + results[0].unwrap_i32(), + types::Errno::Ok as i32, + "double_int_return_float errno" + ); + + // The actual result is in memory: + let mem = shim_inst.get_memory("memory").unwrap(); + let mut result_bytes: [u8; 4] = [0, 0, 0, 0]; + mem.read(result_location as usize, &mut result_bytes) + .unwrap(); + let result = f32::from_le_bytes(result_bytes); + assert_eq!((input * 2) as f32, result); +} + +#[cfg(not(feature = "async"))] +#[test] +fn test_sync_host_func() { + let store = store(); + + let ctx = Rc::new(RefCell::new(Ctx)); + let atoms = Atoms::new(&store, ctx.clone()); + + let mut linker = wasmtime::Linker::new(&store); + atoms.add_to_linker(&mut linker).unwrap(); + + run_int_float_args(&linker); +} + +#[cfg(not(feature = "async"))] +#[test] +fn test_async_host_func() { + let store = store(); + + let ctx = Rc::new(RefCell::new(Ctx)); + let atoms = Atoms::new(&store, ctx.clone()); + + let mut linker = wasmtime::Linker::new(&store); + atoms.add_to_linker(&mut linker).unwrap(); + + run_double_int_return_float(&linker); +} + +#[cfg(not(feature = "async"))] +#[test] +fn test_sync_config_host_func() { + let mut config = wasmtime::Config::new(); + Atoms::add_to_config(&mut config); + + let engine = wasmtime::Engine::new(&config).unwrap(); + let store = wasmtime::Store::new(&engine); + + assert!(Atoms::set_context(&store, Ctx).is_ok()); + + let linker = wasmtime::Linker::new(&store); + run_int_float_args(&linker); +} + +#[cfg(not(feature = "async"))] +#[test] +fn test_async_config_host_func() { + let mut config = wasmtime::Config::new(); + Atoms::add_to_config(&mut config); + + let engine = wasmtime::Engine::new(&config).unwrap(); + let store = wasmtime::Store::new(&engine); + + assert!(Atoms::set_context(&store, Ctx).is_ok()); + + let linker = wasmtime::Linker::new(&store); + run_double_int_return_float(&linker); +} + +fn store() -> wasmtime::Store { + wasmtime::Store::new(&wasmtime::Engine::new(&wasmtime::Config::new()).unwrap()) +} + +// Wiggle expects the caller to have an exported memory. Wasmtime can only +// provide this if the caller is a WebAssembly module, so we need to write +// a shim module: +fn shim_module(store: &wasmtime::Store) -> wasmtime::Module { + wasmtime::Module::new( + store.engine(), + r#" + (module + (memory 1) + (export "memory" (memory 0)) + (import "atoms" "int_float_args" (func $int_float_args (param i32 f32) (result i32))) + (import "atoms" "double_int_return_float" (func $double_int_return_float (param i32 i32) (result i32))) + + (func $int_float_args_shim (param i32 f32) (result i32) + local.get 0 + local.get 1 + call $int_float_args + ) + (func $double_int_return_float_shim (param i32 i32) (result i32) + local.get 0 + local.get 1 + call $double_int_return_float + ) + (export "int_float_args_shim" (func $int_float_args_shim)) + (export "double_int_return_float_shim" (func $double_int_return_float_shim)) + ) + "#, + ) + .unwrap() +} From 68daec7e7d519783aec2471eaffd2a91c919a200 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 12 Apr 2021 18:55:25 -0700 Subject: [PATCH 02/72] wasi-common: WasiSched is an async trait now; poll_oneoff and sched_yield are async methods --- crates/wasi-common/src/sched.rs | 10 ++++++---- crates/wasi-common/src/snapshots/preview_0.rs | 18 ++++++++++------- crates/wasi-common/src/snapshots/preview_1.rs | 20 +++++++++++-------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/crates/wasi-common/src/sched.rs b/crates/wasi-common/src/sched.rs index 10d12d6621..3434ef62dc 100644 --- a/crates/wasi-common/src/sched.rs +++ b/crates/wasi-common/src/sched.rs @@ -1,16 +1,18 @@ use crate::clocks::WasiMonotonicClock; use crate::file::WasiFile; use crate::Error; -use cap_std::time::{Duration, Instant}; +use cap_std::time::Instant; use std::cell::Ref; pub mod subscription; +pub use cap_std::time::Duration; use subscription::{MonotonicClockSubscription, RwSubscription, Subscription, SubscriptionResult}; +#[wiggle::async_trait] pub trait WasiSched { - fn poll_oneoff(&self, poll: &Poll) -> Result<(), Error>; - fn sched_yield(&self) -> Result<(), Error>; - fn sleep(&self, duration: Duration) -> Result<(), Error>; + async fn poll_oneoff<'a>(&self, poll: &Poll<'a>) -> Result<(), Error>; + async fn sched_yield(&self) -> Result<(), Error>; + async fn sleep(&self, duration: Duration) -> Result<(), Error>; } pub struct Userdata(u64); diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs index 02e6289cd2..477f6648b2 100644 --- a/crates/wasi-common/src/snapshots/preview_0.rs +++ b/crates/wasi-common/src/snapshots/preview_0.rs @@ -16,6 +16,7 @@ use wiggle::GuestPtr; wiggle::from_witx!({ witx: ["$WASI_ROOT/phases/old/snapshot_0/witx/wasi_unstable.witx"], errors: { errno => Error }, + async: { wasi_unstable::{poll_oneoff, sched_yield} } }); impl wiggle::GuestErrorType for types::Errno { @@ -332,6 +333,7 @@ convert_flags_bidirectional!( // This implementation, wherever possible, delegates directly to the Snapshot1 implementation, // performing the no-op type conversions along the way. +#[wiggle::async_trait] impl<'a> wasi_unstable::WasiUnstable for WasiCtx { fn args_get<'b>( &self, @@ -720,10 +722,10 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { // The implementations are identical, but the `types::` in scope locally is different. // The bodies of these functions is mostly about converting the GuestPtr and types::-based // representation to use the Poll abstraction. - fn poll_oneoff( + async fn poll_oneoff<'b>( &self, - subs: &GuestPtr, - events: &GuestPtr, + subs: &GuestPtr<'b, types::Subscription>, + events: &GuestPtr<'b, types::Event>, nsubscriptions: types::Size, ) -> Result { if nsubscriptions == 0 { @@ -741,7 +743,9 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { .flags .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) { - self.sched.sleep(Duration::from_nanos(clocksub.timeout))?; + self.sched + .sleep(Duration::from_nanos(clocksub.timeout)) + .await?; events.write(types::Event { userdata: sub.userdata, error: types::Errno::Success, @@ -807,7 +811,7 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { } } - self.sched.poll_oneoff(&poll)?; + self.sched.poll_oneoff(&poll).await?; let results = poll.results(); let num_results = results.len(); @@ -890,8 +894,8 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { Err(Error::trap("proc_raise unsupported")) } - fn sched_yield(&self) -> Result<(), Error> { - Snapshot1::sched_yield(self) + async fn sched_yield(&self) -> Result<(), Error> { + Snapshot1::sched_yield(self).await } fn random_get(&self, buf: &GuestPtr, buf_len: types::Size) -> Result<(), Error> { diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index 0ee4044c37..7896189542 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -22,6 +22,7 @@ use wiggle::GuestPtr; wiggle::from_witx!({ witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"], errors: { errno => Error }, + async: { wasi_snapshot_preview1::{poll_oneoff, sched_yield} } }); impl wiggle::GuestErrorType for types::Errno { @@ -188,7 +189,8 @@ impl TryFrom for types::Errno { } } -impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { +#[wiggle::async_trait] +impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { fn args_get<'b>( &self, argv: &GuestPtr<'b, GuestPtr<'b, u8>>, @@ -891,10 +893,10 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .unlink_file(path.as_str()?.deref()) } - fn poll_oneoff( + async fn poll_oneoff<'b>( &self, - subs: &GuestPtr, - events: &GuestPtr, + subs: &GuestPtr<'b, types::Subscription>, + events: &GuestPtr<'b, types::Event>, nsubscriptions: types::Size, ) -> Result { if nsubscriptions == 0 { @@ -912,7 +914,9 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .flags .contains(types::Subclockflags::SUBSCRIPTION_CLOCK_ABSTIME) { - self.sched.sleep(Duration::from_nanos(clocksub.timeout))?; + self.sched + .sleep(Duration::from_nanos(clocksub.timeout)) + .await?; events.write(types::Event { userdata: sub.userdata, error: types::Errno::Success, @@ -978,7 +982,7 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } } - self.sched.poll_oneoff(&poll)?; + self.sched.poll_oneoff(&poll).await?; let results = poll.results(); let num_results = results.len(); @@ -1066,8 +1070,8 @@ impl<'a> wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Err(Error::trap("proc_raise unsupported")) } - fn sched_yield(&self) -> Result<(), Error> { - self.sched.sched_yield() + async fn sched_yield(&self) -> Result<(), Error> { + self.sched.sched_yield().await } fn random_get(&self, buf: &GuestPtr, buf_len: types::Size) -> Result<(), Error> { From 22d18ffb0dc46d3d0d2b38f27352d5f6af452a8b Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 12 Apr 2021 18:56:03 -0700 Subject: [PATCH 03/72] cap-std-sync: async-trait e-paperwork --- crates/wasi-common/cap-std-sync/Cargo.toml | 1 + crates/wasi-common/cap-std-sync/src/sched/unix.rs | 7 ++++--- crates/wasi-common/cap-std-sync/src/sched/windows.rs | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/wasi-common/cap-std-sync/Cargo.toml b/crates/wasi-common/cap-std-sync/Cargo.toml index 821fa8a5bb..e48ede5400 100644 --- a/crates/wasi-common/cap-std-sync/Cargo.toml +++ b/crates/wasi-common/cap-std-sync/Cargo.toml @@ -13,6 +13,7 @@ include = ["src/**/*", "LICENSE" ] [dependencies] wasi-common = { path = "../", version = "0.26.0" } +wiggle = { path = "../../wiggle", version = "0.26.0" } anyhow = "1.0" cap-std = "0.13.7" cap-fs-ext = "0.13.7" diff --git a/crates/wasi-common/cap-std-sync/src/sched/unix.rs b/crates/wasi-common/cap-std-sync/src/sched/unix.rs index da2ec1d143..eeb9c9c0a7 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/unix.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/unix.rs @@ -21,8 +21,9 @@ impl SyncSched { } } +#[wiggle::async_trait] impl WasiSched for SyncSched { - fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> { + async fn poll_oneoff<'a>(&self, poll: &'_ Poll<'a>) -> Result<(), Error> { if poll.is_empty() { return Ok(()); } @@ -104,11 +105,11 @@ impl WasiSched for SyncSched { } Ok(()) } - fn sched_yield(&self) -> Result<(), Error> { + async fn sched_yield(&self) -> Result<(), Error> { std::thread::yield_now(); Ok(()) } - fn sleep(&self, duration: Duration) -> Result<(), Error> { + async fn sleep(&self, duration: Duration) -> Result<(), Error> { std::thread::sleep(duration); Ok(()) } diff --git a/crates/wasi-common/cap-std-sync/src/sched/windows.rs b/crates/wasi-common/cap-std-sync/src/sched/windows.rs index f40cb08d72..752be626b0 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/windows.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/windows.rs @@ -21,8 +21,9 @@ impl SyncSched { } } +#[wiggle::async_trait] impl WasiSched for SyncSched { - fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> { + async fn poll_oneoff<'a>(&self, poll: &'_ Poll<'a>) -> Result<(), Error> { if poll.is_empty() { return Ok(()); } @@ -124,11 +125,11 @@ impl WasiSched for SyncSched { Ok(()) } - fn sched_yield(&self) -> Result<(), Error> { + async fn sched_yield(&self) -> Result<(), Error> { thread::yield_now(); Ok(()) } - fn sleep(&self, duration: Duration) -> Result<(), Error> { + async fn sleep(&self, duration: Duration) -> Result<(), Error> { std::thread::sleep(duration); Ok(()) } From ac3b4565239e8090cfe5c93ed0c0d886e56e6a10 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 12 Apr 2021 18:56:23 -0700 Subject: [PATCH 04/72] wasi-cap-std-async: exactly like wasi-cap-std-sync but with a different WasiSched --- Cargo.lock | 21 ++++ Cargo.toml | 1 + crates/wasi-common/cap-std-async/Cargo.toml | 18 ++++ crates/wasi-common/cap-std-async/src/lib.rs | 111 ++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 crates/wasi-common/cap-std-async/Cargo.toml create mode 100644 crates/wasi-common/cap-std-async/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 0eb0b74a07..ea8cb56aed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2808,6 +2808,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "tokio" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" +dependencies = [ + "autocfg 1.0.1", + "pin-project-lite", +] + [[package]] name = "toml" version = "0.5.8" @@ -3041,6 +3051,16 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasi-cap-std-async" +version = "0.26.0" +dependencies = [ + "tokio", + "wasi-cap-std-sync", + "wasi-common", + "wiggle", +] + [[package]] name = "wasi-cap-std-sync" version = "0.26.0" @@ -3059,6 +3079,7 @@ dependencies = [ "tracing", "unsafe-io", "wasi-common", + "wiggle", "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index ffff6d865e..7c8c352b48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,7 @@ members = [ "crates/wiggle/wasmtime", "crates/wasi-common", "crates/wasi-common/cap-std-sync", + "crates/wasi-common/cap-std-async", "examples/fib-debug/wasm", "examples/wasi/wasm", "fuzz", diff --git a/crates/wasi-common/cap-std-async/Cargo.toml b/crates/wasi-common/cap-std-async/Cargo.toml new file mode 100644 index 0000000000..471ca5640b --- /dev/null +++ b/crates/wasi-common/cap-std-async/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "wasi-cap-std-async" +version = "0.26.0" +authors = ["The Wasmtime Project Developers"] +description = "WASI implementation in Rust" +license = "Apache-2.0 WITH LLVM-exception" +categories = ["wasm"] +keywords = ["webassembly", "wasm"] +repository = "https://github.com/bytecodealliance/wasmtime" +readme = "README.md" +edition = "2018" +include = ["src/**/*", "LICENSE" ] + +[dependencies] +wasi-common = { path = "../", version = "0.26.0" } +wasi-cap-std-sync = { path = "../cap-std-sync", version = "0.26.0" } +wiggle = { path = "../../wiggle", version = "0.26.0" } +tokio = { version = "1.5.0", features = [ "rt", "time" ] } diff --git a/crates/wasi-common/cap-std-async/src/lib.rs b/crates/wasi-common/cap-std-async/src/lib.rs new file mode 100644 index 0000000000..f01d832c8f --- /dev/null +++ b/crates/wasi-common/cap-std-async/src/lib.rs @@ -0,0 +1,111 @@ +use std::cell::RefCell; +use std::path::Path; +use std::rc::Rc; +pub use wasi_cap_std_sync::{clocks_ctx, random_ctx, Dir}; +use wasi_common::{Table, WasiCtx}; + +pub fn sched_ctx() -> Box { + use wasi_common::{ + sched::{Duration, Poll, WasiSched}, + Error, + }; + struct AsyncSched; + + #[wiggle::async_trait] + impl WasiSched for AsyncSched { + async fn poll_oneoff<'a>(&self, _poll: &'_ Poll<'a>) -> Result<(), Error> { + todo!() + } + async fn sched_yield(&self) -> Result<(), Error> { + tokio::task::yield_now().await; + Ok(()) + } + async fn sleep(&self, duration: Duration) -> Result<(), Error> { + tokio::time::sleep(duration).await; + Ok(()) + } + } + + Box::new(AsyncSched) +} + +pub struct WasiCtxBuilder(wasi_common::WasiCtxBuilder); + +impl WasiCtxBuilder { + pub fn new() -> Self { + WasiCtxBuilder(WasiCtx::builder( + random_ctx(), + clocks_ctx(), + sched_ctx(), + Rc::new(RefCell::new(Table::new())), + )) + } + pub fn env(self, var: &str, value: &str) -> Result { + let s = self.0.env(var, value)?; + Ok(WasiCtxBuilder(s)) + } + pub fn envs(self, env: &[(String, String)]) -> Result { + let mut s = self; + for (k, v) in env { + s = s.env(k, v)?; + } + Ok(s) + } + pub fn inherit_env(self) -> Result { + let mut s = self.0; + for (key, value) in std::env::vars() { + s = s.env(&key, &value)?; + } + Ok(WasiCtxBuilder(s)) + } + pub fn arg(self, arg: &str) -> Result { + let s = self.0.arg(arg)?; + Ok(WasiCtxBuilder(s)) + } + pub fn args(self, arg: &[String]) -> Result { + let mut s = self; + for a in arg { + s = s.arg(&a)?; + } + Ok(s) + } + pub fn inherit_args(self) -> Result { + let mut s = self.0; + for arg in std::env::args() { + s = s.arg(&arg)?; + } + Ok(WasiCtxBuilder(s)) + } + pub fn stdin(self, f: Box) -> Self { + WasiCtxBuilder(self.0.stdin(f)) + } + pub fn stdout(self, f: Box) -> Self { + WasiCtxBuilder(self.0.stdout(f)) + } + pub fn stderr(self, f: Box) -> Self { + WasiCtxBuilder(self.0.stderr(f)) + } + pub fn inherit_stdin(self) -> Self { + self.stdin(Box::new(wasi_cap_std_sync::stdio::stdin())) + } + pub fn inherit_stdout(self) -> Self { + self.stdout(Box::new(wasi_cap_std_sync::stdio::stdout())) + } + pub fn inherit_stderr(self) -> Self { + self.stderr(Box::new(wasi_cap_std_sync::stdio::stderr())) + } + pub fn inherit_stdio(self) -> Self { + self.inherit_stdin().inherit_stdout().inherit_stderr() + } + pub fn preopened_dir( + self, + dir: Dir, + guest_path: impl AsRef, + ) -> Result { + let dir = Box::new(wasi_cap_std_sync::dir::Dir::from_cap_std(dir)); + Ok(WasiCtxBuilder(self.0.preopened_dir(dir, guest_path)?)) + } + pub fn build(self) -> Result { + self.0.build() + } +} From 8f9fb1f4e2986d17f214025b21dd71553d830c4b Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 12 Apr 2021 19:03:03 -0700 Subject: [PATCH 05/72] make wasi-cap-std-async work with wasmtime-wasi --- Cargo.lock | 1 + crates/wasi/Cargo.toml | 2 ++ crates/wasi/src/lib.rs | 9 +++++++++ 3 files changed, 12 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ea8cb56aed..b4c9069bd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3533,6 +3533,7 @@ name = "wasmtime-wasi" version = "0.26.0" dependencies = [ "anyhow", + "wasi-cap-std-async", "wasi-cap-std-sync", "wasi-common", "wasmtime", diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 9951e446a6..da5557f1b7 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -15,6 +15,7 @@ build = "build.rs" [dependencies] wasi-common = { path = "../wasi-common", version = "0.26.0" } wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", version = "0.26.0", optional = true } +wasi-cap-std-async = { path = "../wasi-common/cap-std-async", version = "0.26.0", optional = true } wiggle = { path = "../wiggle", default-features = false, version = "0.26.0" } wasmtime-wiggle = { path = "../wiggle/wasmtime", default-features = false, version = "0.26.0" } wasmtime = { path = "../wasmtime", default-features = false, version = "0.26.0" } @@ -23,3 +24,4 @@ anyhow = "1.0" [features] default = ["sync"] sync = ["wasi-cap-std-sync"] +async = ["wasi-cap-std-async", "wasmtime/async", "wasmtime-wiggle/async"] diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 2b71d7ab37..31045041d3 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -20,6 +20,13 @@ pub mod sync { pub use wasi_cap_std_sync::*; } +/// Re-export the wasi-cap-std-async crate here. This saves consumers of this library from having +/// to keep additional dependencies in sync. +#[cfg(feature = "async")] +pub mod async_ { + pub use wasi_cap_std_async::*; +} + /// An instantiated instance of all available wasi exports. Presently includes /// both the "preview1" snapshot and the "unstable" (preview0) snapshot. pub struct Wasi { @@ -79,6 +86,7 @@ necessary. Additionally [`Wasi::get_export`] can be used to do name-based resolution.", }, }, + async: { wasi_snapshot_preview1::{ poll_oneoff, sched_yield } } }); } pub mod preview_0 { @@ -106,6 +114,7 @@ necessary. Additionally [`Wasi::get_export`] can be used to do name-based resolution.", }, }, + async: { wasi_unstable::{ poll_oneoff, sched_yield } } }); } } From 201da20c63bc7742f08417ec6316d1d18e5d18e8 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 13 Apr 2021 12:27:01 -0700 Subject: [PATCH 06/72] make wasmtime-wasi configurable at macro whether its real async or block_on --- crates/wiggle/generate/src/config.rs | 14 ++-- crates/wiggle/wasmtime/macro/src/config.rs | 82 +++++++++++++++++++- crates/wiggle/wasmtime/macro/src/lib.rs | 89 ++++++++++++---------- crates/wiggle/wasmtime/tests/atoms_sync.rs | 14 +--- 4 files changed, 138 insertions(+), 61 deletions(-) diff --git a/crates/wiggle/generate/src/config.rs b/crates/wiggle/generate/src/config.rs index a430a8547a..2ab5e9119b 100644 --- a/crates/wiggle/generate/src/config.rs +++ b/crates/wiggle/generate/src/config.rs @@ -280,12 +280,14 @@ impl Parse for ErrorConfField { } #[derive(Clone, Default, Debug)] -/// Modules and funcs that should be async -pub struct AsyncConf(HashMap>); +/// Modules and funcs that have async signatures +pub struct AsyncConf { + functions: HashMap>, +} impl AsyncConf { pub fn is_async(&self, module: &str, function: &str) -> bool { - self.0 + self.functions .get(module) .and_then(|fs| fs.iter().find(|f| *f == function)) .is_some() @@ -298,7 +300,7 @@ impl Parse for AsyncConf { let _ = braced!(content in input); let items: Punctuated = content.parse_terminated(Parse::parse)?; - let mut m: HashMap> = HashMap::new(); + let mut functions: HashMap> = HashMap::new(); use std::collections::hash_map::Entry; for i in items { let function_names = i @@ -306,14 +308,14 @@ impl Parse for AsyncConf { .iter() .map(|i| i.to_string()) .collect::>(); - match m.entry(i.module_name.to_string()) { + match functions.entry(i.module_name.to_string()) { Entry::Occupied(o) => o.into_mut().extend(function_names), Entry::Vacant(v) => { v.insert(function_names); } } } - Ok(AsyncConf(m)) + Ok(AsyncConf { functions }) } } diff --git a/crates/wiggle/wasmtime/macro/src/config.rs b/crates/wiggle/wasmtime/macro/src/config.rs index e37d3492ea..0aa9adcce5 100644 --- a/crates/wiggle/wasmtime/macro/src/config.rs +++ b/crates/wiggle/wasmtime/macro/src/config.rs @@ -1,4 +1,4 @@ -pub use wiggle_generate::config::AsyncConf; +use wiggle_generate::config::AsyncConfField; use { proc_macro2::Span, std::collections::HashMap, @@ -37,6 +37,7 @@ mod kw { syn::custom_keyword!(name); syn::custom_keyword!(docs); syn::custom_keyword!(function_override); + syn::custom_keyword!(block_on); } impl Parse for ConfigField { @@ -66,6 +67,12 @@ impl Parse for ConfigField { input.parse::()?; input.parse::()?; Ok(ConfigField::Async(input.parse()?)) + } else if lookahead.peek(kw::block_on) { + input.parse::()?; + input.parse::()?; + let mut async_conf: AsyncConf = input.parse()?; + async_conf.blocking = true; + Ok(ConfigField::Async(async_conf)) } else { Err(lookahead.error()) } @@ -261,3 +268,76 @@ impl Parse for ModulesConf { }) } } + +#[derive(Clone, Default, Debug)] +/// Modules and funcs that have async signatures +pub struct AsyncConf { + blocking: bool, + functions: HashMap>, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Asyncness { + /// Wiggle function is synchronous, wasmtime Func is synchronous + Sync, + /// Wiggle function is asynchronous, but wasmtime Func is synchronous + Blocking, + /// Wiggle function and wasmtime Func are asynchronous. + Async, +} + +impl Asyncness { + pub fn is_sync(&self) -> bool { + match self { + Asyncness::Sync => true, + _ => false, + } + } +} + +impl AsyncConf { + pub fn is_async(&self, module: &str, function: &str) -> Asyncness { + if self + .functions + .get(module) + .and_then(|fs| fs.iter().find(|f| *f == function)) + .is_some() + { + if self.blocking { + Asyncness::Blocking + } else { + Asyncness::Async + } + } else { + Asyncness::Sync + } + } +} + +impl Parse for AsyncConf { + fn parse(input: ParseStream) -> Result { + let content; + let _ = braced!(content in input); + let items: Punctuated = + content.parse_terminated(Parse::parse)?; + let mut functions: HashMap> = HashMap::new(); + use std::collections::hash_map::Entry; + for i in items { + let function_names = i + .function_names + .iter() + .map(|i| i.to_string()) + .collect::>(); + match functions.entry(i.module_name.to_string()) { + Entry::Occupied(o) => o.into_mut().extend(function_names), + Entry::Vacant(v) => { + v.insert(function_names); + } + } + } + Ok(AsyncConf { + functions, + blocking: false, + }) + } +} diff --git a/crates/wiggle/wasmtime/macro/src/lib.rs b/crates/wiggle/wasmtime/macro/src/lib.rs index b7bf5543c9..4764d217c1 100644 --- a/crates/wiggle/wasmtime/macro/src/lib.rs +++ b/crates/wiggle/wasmtime/macro/src/lib.rs @@ -6,7 +6,7 @@ use wiggle_generate::Names; mod config; -use config::{AsyncConf, ModuleConf, TargetConf}; +use config::{AsyncConf, Asyncness, ModuleConf, TargetConf}; /// Define the structs required to integrate a Wiggle implementation with Wasmtime. /// @@ -101,14 +101,19 @@ fn generate_module( let mut ctor_externs = Vec::new(); let mut host_funcs = Vec::new(); - #[cfg(not(feature = "async"))] let mut requires_dummy_executor = false; for f in module.funcs() { - let is_async = async_conf.is_async(module.name.as_str(), f.name.as_str()); - #[cfg(not(feature = "async"))] - if is_async { - requires_dummy_executor = true; + let asyncness = async_conf.is_async(module.name.as_str(), f.name.as_str()); + match asyncness { + Asyncness::Blocking => requires_dummy_executor = true, + Asyncness::Async => { + assert!( + cfg!(feature = "async"), + "generating async wasmtime Funcs requires cargo feature \"async\"" + ); + } + _ => {} } generate_func( &module_id, @@ -116,7 +121,7 @@ fn generate_module( names, &target_module, ctx_type, - is_async, + asyncness, &mut fns, &mut ctor_externs, &mut host_funcs, @@ -160,14 +165,11 @@ contained in the `cx` parameter.", } }); - #[cfg(not(feature = "async"))] let dummy_executor = if requires_dummy_executor { dummy_executor() } else { quote!() }; - #[cfg(feature = "async")] - let dummy_executor = quote!(); quote! { #type_docs @@ -243,7 +245,7 @@ fn generate_func( names: &Names, target_module: &TokenStream2, ctx_type: &syn::Type, - is_async: bool, + asyncness: Asyncness, fns: &mut Vec, ctors: &mut Vec, host_funcs: &mut Vec<(witx::Id, TokenStream2)>, @@ -271,8 +273,16 @@ fn generate_func( _ => unimplemented!(), }; - let async_ = if is_async { quote!(async) } else { quote!() }; - let await_ = if is_async { quote!(.await) } else { quote!() }; + let async_ = if asyncness.is_sync() { + quote!() + } else { + quote!(async) + }; + let await_ = if asyncness.is_sync() { + quote!() + } else { + quote!(.await) + }; let runtime = names.runtime_mod(); let fn_ident = format_ident!("{}_{}", module_ident, name_ident); @@ -296,9 +306,8 @@ fn generate_func( } }); - if is_async { - #[cfg(feature = "async")] - { + match asyncness { + Asyncness::Async => { let wrapper = format_ident!("wrap{}_async", params.len()); ctors.push(quote! { let #name_ident = wasmtime::Func::#wrapper( @@ -309,11 +318,9 @@ fn generate_func( Box::new(async move { Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*).await }) } ); - }); + }); } - - #[cfg(not(feature = "async"))] - { + Asyncness::Blocking => { // Emit a synchronous function. Self::#fn_ident returns a Future, so we need to // use a dummy executor to let any synchronous code inside there execute correctly. If // the future ends up Pending, this func will Trap. @@ -327,8 +334,8 @@ fn generate_func( ); }); } - } else { - ctors.push(quote! { + Asyncness::Sync => { + ctors.push(quote! { let my_ctx = ctx.clone(); let #name_ident = wasmtime::Func::wrap( store, @@ -337,11 +344,11 @@ fn generate_func( } ); }); + } } - let host_wrapper = if is_async { - #[cfg(feature = "async")] - { + let host_wrapper = match asyncness { + Asyncness::Async => { let wrapper = format_ident!("wrap{}_host_func_async", params.len()); quote! { config.#wrapper( @@ -361,8 +368,7 @@ fn generate_func( } } - #[cfg(not(feature = "async"))] - { + Asyncness::Blocking => { // Emit a synchronous host function. Self::#fn_ident returns a Future, so we need to // use a dummy executor to let any synchronous code inside there execute correctly. If // the future ends up Pending, this func will Trap. @@ -380,24 +386,25 @@ fn generate_func( ); } } - } else { - quote! { - config.wrap_host_func( - module, - field, - move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> { - let ctx = caller - .store() - .get::>>() - .ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?; - Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*) - }, - ); + Asyncness::Sync => { + quote! { + config.wrap_host_func( + module, + field, + move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> { + let ctx = caller + .store() + .get::>>() + .ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?; + Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*) + }, + ); + } } }; host_funcs.push((func.name.clone(), host_wrapper)); } -#[cfg(not(feature = "async"))] + fn dummy_executor() -> TokenStream2 { quote! { fn run_in_dummy_executor(future: F) -> F::Output { diff --git a/crates/wiggle/wasmtime/tests/atoms_sync.rs b/crates/wiggle/wasmtime/tests/atoms_sync.rs index 057ebbaa6e..eee48f5338 100644 --- a/crates/wiggle/wasmtime/tests/atoms_sync.rs +++ b/crates/wiggle/wasmtime/tests/atoms_sync.rs @@ -1,11 +1,3 @@ -#![allow(unused)] -// These tests are designed to check the behavior with the crate's async feature (& wasmtimes async -// feature) disabled. Run with: -// `cargo test --no-default-features --features wasmtime/wat --test atoms_sync` -#[cfg(feature = "async")] -#[test] -fn these_tests_require_async_feature_disabled() {} - use std::cell::RefCell; use std::rc::Rc; @@ -21,7 +13,7 @@ wasmtime_wiggle::wasmtime_integration!({ witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], ctx: Ctx, modules: { atoms => { name: Atoms } }, - async: { + block_on: { atoms::double_int_return_float } }); @@ -94,7 +86,6 @@ fn run_double_int_return_float(linker: &wasmtime::Linker) { assert_eq!((input * 2) as f32, result); } -#[cfg(not(feature = "async"))] #[test] fn test_sync_host_func() { let store = store(); @@ -108,7 +99,6 @@ fn test_sync_host_func() { run_int_float_args(&linker); } -#[cfg(not(feature = "async"))] #[test] fn test_async_host_func() { let store = store(); @@ -122,7 +112,6 @@ fn test_async_host_func() { run_double_int_return_float(&linker); } -#[cfg(not(feature = "async"))] #[test] fn test_sync_config_host_func() { let mut config = wasmtime::Config::new(); @@ -137,7 +126,6 @@ fn test_sync_config_host_func() { run_int_float_args(&linker); } -#[cfg(not(feature = "async"))] #[test] fn test_async_config_host_func() { let mut config = wasmtime::Config::new(); From 2b7a93c4030b5078a1eb56f018f707ae35304c96 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 13 Apr 2021 12:38:49 -0700 Subject: [PATCH 07/72] wasmtime-wasi: two distinct definitions of the Wasi struct, when sync vs async --- crates/wasi/src/lib.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 31045041d3..0bb9ef6ea8 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -7,10 +7,7 @@ //! Individual snapshots are available through //! `wasmtime_wasi::snapshots::preview_{0, 1}::Wasi::new(&Store, Rc>)`. -use std::cell::RefCell; -use std::rc::Rc; pub use wasi_common::{Error, WasiCtx, WasiCtxBuilder, WasiDir, WasiFile}; -use wasmtime::{Config, Linker, Store}; /// Re-export the commonly used wasi-cap-std-sync crate here. This saves /// consumers of this library from having to keep additional dependencies @@ -18,6 +15,7 @@ use wasmtime::{Config, Linker, Store}; #[cfg(feature = "sync")] pub mod sync { pub use wasi_cap_std_sync::*; + super::define_wasi!(block_on); } /// Re-export the wasi-cap-std-async crate here. This saves consumers of this library from having @@ -25,8 +23,23 @@ pub mod sync { #[cfg(feature = "async")] pub mod async_ { pub use wasi_cap_std_async::*; + super::define_wasi!(async); } +// The only difference between these definitions for sync vs async is whether +// the wasmtime::Funcs generated are async (& therefore need an async Store and an executor to run) +// or whether they have an internal "dummy executor" that expects the implementation of all +// the async funcs to poll to Ready immediately. +#[doc(hidden)] +#[macro_export] +macro_rules! define_wasi { + ($async_mode: tt) => { + +use std::cell::RefCell; +use std::rc::Rc; +use wasmtime::{Config, Linker, Store}; +use wasi_common::WasiCtx; + /// An instantiated instance of all available wasi exports. Presently includes /// both the "preview1" snapshot and the "unstable" (preview0) snapshot. pub struct Wasi { @@ -86,7 +99,7 @@ necessary. Additionally [`Wasi::get_export`] can be used to do name-based resolution.", }, }, - async: { wasi_snapshot_preview1::{ poll_oneoff, sched_yield } } + $async_mode: { wasi_snapshot_preview1::{ poll_oneoff, sched_yield } } }); } pub mod preview_0 { @@ -114,7 +127,9 @@ necessary. Additionally [`Wasi::get_export`] can be used to do name-based resolution.", }, }, - async: { wasi_unstable::{ poll_oneoff, sched_yield } } + $async_mode: { wasi_unstable::{ poll_oneoff, sched_yield } } }); } } +} +} From bac02c50f2e7801ce3e3c2712307a70e18dd12d6 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 13 Apr 2021 12:42:00 -0700 Subject: [PATCH 08/72] port command and example to use sync wasmtime_wasi --- examples/wasi/main.rs | 2 +- src/commands/run.rs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/wasi/main.rs b/examples/wasi/main.rs index f808be3eca..0c3f077d93 100644 --- a/examples/wasi/main.rs +++ b/examples/wasi/main.rs @@ -5,7 +5,7 @@ use anyhow::Result; use wasmtime::*; -use wasmtime_wasi::{sync::WasiCtxBuilder, Wasi}; +use wasmtime_wasi::sync::{Wasi, WasiCtxBuilder}; fn main() -> Result<()> { tracing_subscriber::FmtSubscriber::builder() diff --git a/src/commands/run.rs b/src/commands/run.rs index 9c57add91f..f1f8da46e5 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -11,10 +11,7 @@ use std::{ }; use structopt::{clap::AppSettings, StructOpt}; use wasmtime::{Engine, Func, Linker, Module, Store, Trap, Val, ValType}; -use wasmtime_wasi::{ - sync::{Dir, WasiCtxBuilder}, - Wasi, -}; +use wasmtime_wasi::sync::{Dir, Wasi, WasiCtxBuilder}; #[cfg(feature = "wasi-nn")] use wasmtime_wasi_nn::{WasiNn, WasiNnCtx}; From 66251c2e78d122c90c4aa69ced984af4c25c4444 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 13 Apr 2021 15:14:51 -0700 Subject: [PATCH 09/72] c-api: always use synchronous wasi-common --- crates/c-api/src/wasi.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/c-api/src/wasi.rs b/crates/c-api/src/wasi.rs index 441fa6fa54..302fcde8b7 100644 --- a/crates/c-api/src/wasi.rs +++ b/crates/c-api/src/wasi.rs @@ -12,9 +12,10 @@ use std::slice; use std::str; use wasmtime::{Extern, Linker, Trap}; use wasmtime_wasi::{ - snapshots::preview_0::Wasi as WasiSnapshot0, - snapshots::preview_1::Wasi as WasiPreview1, - sync::{Dir, WasiCtxBuilder}, + sync::{ + snapshots::preview_0::Wasi as WasiSnapshot0, snapshots::preview_1::Wasi as WasiPreview1, + Dir, WasiCtxBuilder, + }, WasiCtx, }; From 759019811ef5b28a99ea56ec640102fb27a2f110 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 13 Apr 2021 17:15:31 -0700 Subject: [PATCH 10/72] add tokio example Co-authored-by: Alex Crichton --- Cargo.lock | 18 ++++ Cargo.toml | 6 ++ examples/tokio/main.rs | 162 ++++++++++++++++++++++++++++++ examples/tokio/wasm/Cargo.toml | 10 ++ examples/tokio/wasm/tokio-wasi.rs | 6 ++ 5 files changed, 202 insertions(+) create mode 100644 examples/tokio/main.rs create mode 100644 examples/tokio/wasm/Cargo.toml create mode 100644 examples/tokio/wasm/tokio-wasi.rs diff --git a/Cargo.lock b/Cargo.lock index b4c9069bd9..d47be3ddc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1102,6 +1102,10 @@ dependencies = [ name = "example-fib-debug-wasm" version = "0.0.0" +[[package]] +name = "example-tokio-wasm" +version = "0.0.0" + [[package]] name = "example-wasi-wasm" version = "0.0.0" @@ -2815,7 +2819,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" dependencies = [ "autocfg 1.0.1", + "num_cpus", "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -3305,6 +3322,7 @@ dependencies = [ "target-lexicon", "tempfile", "test-programs", + "tokio", "tracing-subscriber", "wasmparser", "wasmtime", diff --git a/Cargo.toml b/Cargo.toml index 7c8c352b48..7d4ebbfce5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ tempfile = "3.1.0" test-programs = { path = "crates/test-programs" } wasmtime-fuzzing = { path = "crates/fuzzing" } wasmtime-runtime = { path = "crates/runtime" } +tokio = { version = "1.5.0", features = ["rt", "time", "macros", "rt-multi-thread"] } tracing-subscriber = "0.2.16" wast = "35.0.0" @@ -81,6 +82,7 @@ members = [ "crates/wasi-common/cap-std-async", "examples/fib-debug/wasm", "examples/wasi/wasm", + "examples/tokio/wasm", "fuzz", ] @@ -108,5 +110,9 @@ maintenance = { status = "actively-developed" } name = "host_segfault" harness = false +[[example]] +name = "tokio" +required-features = ["wasmtime-wasi/async"] + [profile.dev.package.backtrace] debug = false # FIXME(#1813) diff --git a/examples/tokio/main.rs b/examples/tokio/main.rs new file mode 100644 index 0000000000..3df2a0ad3f --- /dev/null +++ b/examples/tokio/main.rs @@ -0,0 +1,162 @@ +use anyhow::{anyhow, Error}; +use std::future::Future; +use tokio::time::Duration; +use wasmtime::{Config, Engine, Linker, Module, Store}; +// For this example we want to use the async version of wasmtime_wasi. +// Notably, this version of wasi uses a scheduler that will async yield +// when sleeping in `poll_oneoff`. +use wasmtime_wasi::async_::{Wasi, WasiCtxBuilder}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + // Create an environment shared by all wasm execution. This contains + // the `Engine` and the `Module` we are executing. + let env = Environment::new()?; + + // The inputs to run_wasm are `Send`: we can create them here and send + // them to a new task that we spawn. + let inputs1 = Inputs::new(env.clone(), "Gussie"); + let inputs2 = Inputs::new(env.clone(), "Willa"); + let inputs3 = Inputs::new(env, "Sparky"); + + // Spawn some tasks. Insert sleeps before run_wasm so that the + // interleaving is easy to observe. + let join1 = tokio::task::spawn(async move { run_wasm(inputs1).await }); + let join2 = tokio::task::spawn(async move { + tokio::time::sleep(Duration::from_millis(750)).await; + run_wasm(inputs2).await + }); + let join3 = tokio::task::spawn(async move { + tokio::time::sleep(Duration::from_millis(1250)).await; + run_wasm(inputs3).await + }); + + // All tasks should join successfully. + join1.await??; + join2.await??; + join3.await??; + Ok(()) +} + +#[derive(Clone)] +struct Environment { + engine: Engine, + module: Module, +} + +impl Environment { + pub fn new() -> Result { + let mut config = Config::new(); + // We need this engine's `Store`s to be async, and consume fuel, so + // that they can co-operatively yield during execution. + config.async_support(true); + config.consume_fuel(true); + + // Install the host functions for `Wasi`. + Wasi::add_to_config(&mut config); + + let engine = Engine::new(&config)?; + let module = Module::from_file(&engine, "target/wasm32-wasi/debug/tokio-wasi.wasm")?; + + Ok(Self { engine, module }) + } +} + +struct Inputs { + env: Environment, + name: String, +} + +impl Inputs { + fn new(env: Environment, name: &str) -> Self { + Self { + env, + name: name.to_owned(), + } + } +} + +fn run_wasm(inputs: Inputs) -> impl Future> { + use std::pin::Pin; + use std::task::{Context, Poll}; + + // This is a "marker type future" which simply wraps some other future and + // the only purpose it serves is to forward the implementation of `Future` + // as well as have `unsafe impl Send` for itself, regardless of the + // underlying type. + // + // Note that the qctual safety of this relies on the fact that the inputs + // here are `Send`, the outputs (just () in this case) are `Send`, and the + // future itself is safe tu resume on other threads. + // + // For an in-depth discussion of the safety of moving Wasmtime's `Store` + // between threads, see + // https://docs.wasmtime.dev/examples-rust-multithreading.html. + struct UnsafeSend(T); + + // Note the `where` cause specifically ensures the output of the future to + // be `Send` is required. We specifically dont require `T` to be `Send` + // since that's the whole point of this function, but we require that + // everything used to construct `T` is `Send` below. + unsafe impl Send for UnsafeSend + where + T: Future, + T::Output: Send, + { + } + impl Future for UnsafeSend { + type Output = T::Output; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Note that this `unsafe` is unrelated to `Send`, it only has to do with "pin + // projection" and should be safe since it's all we do with the `Pin`. + unsafe { self.map_unchecked_mut(|p| &mut p.0).poll(cx) } + } + } + + // This is a crucial assertion that needs to be here. The compiler + // typically checks this for us, but do to our `UnsafeSend` type the + // compiler isn't automatically checking this. The assertion here must + // assert that all arguments to this function are indeed `Send` because + // we're closing over them and sending them to other threads. It's only + // everything *internal* to the computation of this function which doesn't + // have to be `Send`. + fn assert_send(_t: &T) {} + assert_send(&inputs); + + // Wrap up the `_run_wasm` function, which is *not* `Send`, but is safe to + // resume on other threads. + UnsafeSend(_run_wasm(inputs)) +} + +async fn _run_wasm(inputs: Inputs) -> Result<(), Error> { + let store = Store::new(&inputs.env.engine); + + // WebAssembly execution will be paused for an async yield every time it + // consumes 10000 fuel. Fuel will be refilled u32::MAX times. + store.out_of_fuel_async_yield(u32::MAX, 10000); + + Wasi::set_context( + &store, + WasiCtxBuilder::new() + // Let wasi print to this process's stdout. + .inherit_stdout() + // Set an environment variable so the wasm knows its name. + .env("NAME", &inputs.name)? + .build()?, + ) + .map_err(|_| anyhow!("setting wasi context"))?; + + let linker = Linker::new(&store); + + // Instantiate + let instance = linker.instantiate_async(&inputs.env.module).await?; + instance + .get_export("_start") + .ok_or_else(|| anyhow!("wasm is a wasi command with export _start"))? + .into_func() + .ok_or_else(|| anyhow!("_start is a func"))? + .call_async(&[]) + .await?; + + Ok(()) +} diff --git a/examples/tokio/wasm/Cargo.toml b/examples/tokio/wasm/Cargo.toml new file mode 100644 index 0000000000..5704f79630 --- /dev/null +++ b/examples/tokio/wasm/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "example-tokio-wasm" +version = "0.0.0" +authors = ["The Wasmtime Project Developers"] +edition = "2018" +publish = false + +[[bin]] +path = "tokio-wasi.rs" +name = "tokio-wasi" diff --git a/examples/tokio/wasm/tokio-wasi.rs b/examples/tokio/wasm/tokio-wasi.rs new file mode 100644 index 0000000000..3431e65a66 --- /dev/null +++ b/examples/tokio/wasm/tokio-wasi.rs @@ -0,0 +1,6 @@ +fn main() { + let name = std::env::var("NAME").unwrap(); + println!("Hello, world! My name is {}", name); + std::thread::sleep(std::time::Duration::from_secs(1)); + println!("Goodbye from {}", name); +} From 247795c7cae04afe8797c5b3204a9d05c9221597 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 08:37:53 -0700 Subject: [PATCH 11/72] fix --- crates/bench-api/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs index b2884d7738..6cf6104279 100644 --- a/crates/bench-api/src/lib.rs +++ b/crates/bench-api/src/lib.rs @@ -83,9 +83,8 @@ use std::env; use std::os::raw::{c_int, c_void}; use std::path::Path; use std::slice; -use wasi_cap_std_sync::WasiCtxBuilder; use wasmtime::{Config, Engine, Instance, Linker, Module, Store}; -use wasmtime_wasi::Wasi; +use wasmtime_wasi::sync::{Wasi, WasiCtxBuilder}; pub type ExitCode = c_int; pub const OK: ExitCode = 0; From c691d1864e6db6eb5b929c54ef693760fca66636 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 09:54:27 -0700 Subject: [PATCH 12/72] fix --- examples/linking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/linking.rs b/examples/linking.rs index b4cee53a4a..824bb13410 100644 --- a/examples/linking.rs +++ b/examples/linking.rs @@ -4,7 +4,7 @@ use anyhow::Result; use wasmtime::*; -use wasmtime_wasi::{sync::WasiCtxBuilder, Wasi}; +use wasmtime_wasi::sync::{Wasi, WasiCtxBuilder}; fn main() -> Result<()> { let engine = Engine::default(); From 0127676621406d5794428519338f49442eb9b750 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 14:02:50 -0700 Subject: [PATCH 13/72] wasi-cap-std-async is better named wasi-tokio --- Cargo.lock | 22 +++++++++---------- Cargo.toml | 4 ++-- .../{cap-std-async => tokio}/Cargo.toml | 2 +- .../{cap-std-async => tokio}/src/lib.rs | 0 crates/wasi/Cargo.toml | 4 ++-- crates/wasi/src/lib.rs | 8 +++---- examples/tokio/main.rs | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) rename crates/wasi-common/{cap-std-async => tokio}/Cargo.toml (95%) rename crates/wasi-common/{cap-std-async => tokio}/src/lib.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index d47be3ddc1..b7e1aa02e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3068,16 +3068,6 @@ version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" -[[package]] -name = "wasi-cap-std-async" -version = "0.26.0" -dependencies = [ - "tokio", - "wasi-cap-std-sync", - "wasi-common", - "wiggle", -] - [[package]] name = "wasi-cap-std-sync" version = "0.26.0" @@ -3144,6 +3134,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "wasi-tokio" +version = "0.26.0" +dependencies = [ + "tokio", + "wasi-cap-std-sync", + "wasi-common", + "wiggle", +] + [[package]] name = "wasm-encoder" version = "0.4.1" @@ -3551,9 +3551,9 @@ name = "wasmtime-wasi" version = "0.26.0" dependencies = [ "anyhow", - "wasi-cap-std-async", "wasi-cap-std-sync", "wasi-common", + "wasi-tokio", "wasmtime", "wasmtime-wiggle", "wiggle", diff --git a/Cargo.toml b/Cargo.toml index 7d4ebbfce5..98c5e65f07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,7 @@ members = [ "crates/wiggle/wasmtime", "crates/wasi-common", "crates/wasi-common/cap-std-sync", - "crates/wasi-common/cap-std-async", + "crates/wasi-common/tokio", "examples/fib-debug/wasm", "examples/wasi/wasm", "examples/tokio/wasm", @@ -112,7 +112,7 @@ harness = false [[example]] name = "tokio" -required-features = ["wasmtime-wasi/async"] +required-features = ["wasmtime-wasi/tokio"] [profile.dev.package.backtrace] debug = false # FIXME(#1813) diff --git a/crates/wasi-common/cap-std-async/Cargo.toml b/crates/wasi-common/tokio/Cargo.toml similarity index 95% rename from crates/wasi-common/cap-std-async/Cargo.toml rename to crates/wasi-common/tokio/Cargo.toml index 471ca5640b..b1a194bdff 100644 --- a/crates/wasi-common/cap-std-async/Cargo.toml +++ b/crates/wasi-common/tokio/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wasi-cap-std-async" +name = "wasi-tokio" version = "0.26.0" authors = ["The Wasmtime Project Developers"] description = "WASI implementation in Rust" diff --git a/crates/wasi-common/cap-std-async/src/lib.rs b/crates/wasi-common/tokio/src/lib.rs similarity index 100% rename from crates/wasi-common/cap-std-async/src/lib.rs rename to crates/wasi-common/tokio/src/lib.rs diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index da5557f1b7..3fa9dad245 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -15,7 +15,7 @@ build = "build.rs" [dependencies] wasi-common = { path = "../wasi-common", version = "0.26.0" } wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", version = "0.26.0", optional = true } -wasi-cap-std-async = { path = "../wasi-common/cap-std-async", version = "0.26.0", optional = true } +wasi-tokio = { path = "../wasi-common/tokio", version = "0.26.0", optional = true } wiggle = { path = "../wiggle", default-features = false, version = "0.26.0" } wasmtime-wiggle = { path = "../wiggle/wasmtime", default-features = false, version = "0.26.0" } wasmtime = { path = "../wasmtime", default-features = false, version = "0.26.0" } @@ -24,4 +24,4 @@ anyhow = "1.0" [features] default = ["sync"] sync = ["wasi-cap-std-sync"] -async = ["wasi-cap-std-async", "wasmtime/async", "wasmtime-wiggle/async"] +tokio = ["wasi-tokio", "wasmtime/async", "wasmtime-wiggle/async"] diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 0bb9ef6ea8..2eefcfccd2 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -18,11 +18,11 @@ pub mod sync { super::define_wasi!(block_on); } -/// Re-export the wasi-cap-std-async crate here. This saves consumers of this library from having +/// Re-export the wasi-tokio crate here. This saves consumers of this library from having /// to keep additional dependencies in sync. -#[cfg(feature = "async")] -pub mod async_ { - pub use wasi_cap_std_async::*; +#[cfg(feature = "tokio")] +pub mod tokio { + pub use wasi_tokio::*; super::define_wasi!(async); } diff --git a/examples/tokio/main.rs b/examples/tokio/main.rs index 3df2a0ad3f..03e83b4ac0 100644 --- a/examples/tokio/main.rs +++ b/examples/tokio/main.rs @@ -5,7 +5,7 @@ use wasmtime::{Config, Engine, Linker, Module, Store}; // For this example we want to use the async version of wasmtime_wasi. // Notably, this version of wasi uses a scheduler that will async yield // when sleeping in `poll_oneoff`. -use wasmtime_wasi::async_::{Wasi, WasiCtxBuilder}; +use wasmtime_wasi::tokio::{Wasi, WasiCtxBuilder}; #[tokio::main] async fn main() -> Result<(), Error> { From 228096c840b188d43fa4b53d047e128c27a0b879 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 14:51:24 -0700 Subject: [PATCH 14/72] wiggle: convenient syntax for marking all funcs async --- crates/wiggle/generate/src/config.rs | 70 +++++++++++++++------- crates/wiggle/tests/atoms_async.rs | 4 +- crates/wiggle/wasmtime/macro/src/config.rs | 69 ++++++++------------- 3 files changed, 73 insertions(+), 70 deletions(-) diff --git a/crates/wiggle/generate/src/config.rs b/crates/wiggle/generate/src/config.rs index 2ab5e9119b..bed0e5c834 100644 --- a/crates/wiggle/generate/src/config.rs +++ b/crates/wiggle/generate/src/config.rs @@ -47,7 +47,9 @@ impl Parse for ConfigField { } else if lookahead.peek(Token![async]) { input.parse::()?; input.parse::()?; - Ok(ConfigField::Async(input.parse()?)) + Ok(ConfigField::Async(AsyncConf { + functions: input.parse()?, + })) } else { Err(lookahead.error()) } @@ -282,40 +284,62 @@ impl Parse for ErrorConfField { #[derive(Clone, Default, Debug)] /// Modules and funcs that have async signatures pub struct AsyncConf { - functions: HashMap>, + functions: AsyncFunctions, +} + +#[derive(Clone, Debug)] +pub enum AsyncFunctions { + Some(HashMap>), + All, +} +impl Default for AsyncFunctions { + fn default() -> Self { + AsyncFunctions::Some(HashMap::default()) + } } impl AsyncConf { pub fn is_async(&self, module: &str, function: &str) -> bool { - self.functions - .get(module) - .and_then(|fs| fs.iter().find(|f| *f == function)) - .is_some() + match &self.functions { + AsyncFunctions::Some(fs) => fs + .get(module) + .and_then(|fs| fs.iter().find(|f| *f == function)) + .is_some(), + AsyncFunctions::All => true, + } } } -impl Parse for AsyncConf { +impl Parse for AsyncFunctions { fn parse(input: ParseStream) -> Result { let content; - let _ = braced!(content in input); - let items: Punctuated = - content.parse_terminated(Parse::parse)?; - let mut functions: HashMap> = HashMap::new(); - use std::collections::hash_map::Entry; - for i in items { - let function_names = i - .function_names - .iter() - .map(|i| i.to_string()) - .collect::>(); - match functions.entry(i.module_name.to_string()) { - Entry::Occupied(o) => o.into_mut().extend(function_names), - Entry::Vacant(v) => { - v.insert(function_names); + let lookahead = input.lookahead1(); + if lookahead.peek(syn::token::Brace) { + let _ = braced!(content in input); + let items: Punctuated = + content.parse_terminated(Parse::parse)?; + let mut functions: HashMap> = HashMap::new(); + use std::collections::hash_map::Entry; + for i in items { + let function_names = i + .function_names + .iter() + .map(|i| i.to_string()) + .collect::>(); + match functions.entry(i.module_name.to_string()) { + Entry::Occupied(o) => o.into_mut().extend(function_names), + Entry::Vacant(v) => { + v.insert(function_names); + } } } + Ok(AsyncFunctions::Some(functions)) + } else if lookahead.peek(Token![*]) { + let _: Token![*] = input.parse().unwrap(); + Ok(AsyncFunctions::All) + } else { + Err(lookahead.error()) } - Ok(AsyncConf { functions }) } } diff --git a/crates/wiggle/tests/atoms_async.rs b/crates/wiggle/tests/atoms_async.rs index 0057c9203a..7d41d3a273 100644 --- a/crates/wiggle/tests/atoms_async.rs +++ b/crates/wiggle/tests/atoms_async.rs @@ -7,9 +7,7 @@ use wiggle_test::{impl_errno, HostMemory, MemArea, WasiCtx}; wiggle::from_witx!({ witx: ["$CARGO_MANIFEST_DIR/tests/atoms.witx"], - async: { - atoms::{int_float_args, double_int_return_float} - } + async: *, }); impl_errno!(types::Errno); diff --git a/crates/wiggle/wasmtime/macro/src/config.rs b/crates/wiggle/wasmtime/macro/src/config.rs index 0aa9adcce5..30815817b2 100644 --- a/crates/wiggle/wasmtime/macro/src/config.rs +++ b/crates/wiggle/wasmtime/macro/src/config.rs @@ -1,4 +1,4 @@ -use wiggle_generate::config::AsyncConfField; +use wiggle_generate::config::AsyncFunctions; use { proc_macro2::Span, std::collections::HashMap, @@ -66,13 +66,17 @@ impl Parse for ConfigField { } else if lookahead.peek(Token![async]) { input.parse::()?; input.parse::()?; - Ok(ConfigField::Async(input.parse()?)) + Ok(ConfigField::Async(AsyncConf { + blocking: false, + functions: input.parse()?, + })) } else if lookahead.peek(kw::block_on) { input.parse::()?; input.parse::()?; - let mut async_conf: AsyncConf = input.parse()?; - async_conf.blocking = true; - Ok(ConfigField::Async(async_conf)) + Ok(ConfigField::Async(AsyncConf { + blocking: true, + functions: input.parse()?, + })) } else { Err(lookahead.error()) } @@ -273,7 +277,7 @@ impl Parse for ModulesConf { /// Modules and funcs that have async signatures pub struct AsyncConf { blocking: bool, - functions: HashMap>, + functions: AsyncFunctions, } #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -297,47 +301,24 @@ impl Asyncness { impl AsyncConf { pub fn is_async(&self, module: &str, function: &str) -> Asyncness { - if self - .functions - .get(module) - .and_then(|fs| fs.iter().find(|f| *f == function)) - .is_some() - { - if self.blocking { - Asyncness::Blocking - } else { - Asyncness::Async - } + let a = if self.blocking { + Asyncness::Blocking } else { - Asyncness::Sync - } - } -} - -impl Parse for AsyncConf { - fn parse(input: ParseStream) -> Result { - let content; - let _ = braced!(content in input); - let items: Punctuated = - content.parse_terminated(Parse::parse)?; - let mut functions: HashMap> = HashMap::new(); - use std::collections::hash_map::Entry; - for i in items { - let function_names = i - .function_names - .iter() - .map(|i| i.to_string()) - .collect::>(); - match functions.entry(i.module_name.to_string()) { - Entry::Occupied(o) => o.into_mut().extend(function_names), - Entry::Vacant(v) => { - v.insert(function_names); + Asyncness::Async + }; + match &self.functions { + AsyncFunctions::Some(fs) => { + if fs + .get(module) + .and_then(|fs| fs.iter().find(|f| *f == function)) + .is_some() + { + a + } else { + Asyncness::Sync } } + AsyncFunctions::All => a, } - Ok(AsyncConf { - functions, - blocking: false, - }) } } From 025a1ecff45ed06a6ddf9f72b296970a7c4be040 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 15:05:38 -0700 Subject: [PATCH 15/72] wasi-common: snapshots 0 and 1 traits are all async now --- crates/wasi-common/src/snapshots/preview_0.rs | 232 ++++++++++-------- crates/wasi-common/src/snapshots/preview_1.rs | 150 ++++++----- 2 files changed, 211 insertions(+), 171 deletions(-) diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs index 477f6648b2..a428c3f7ea 100644 --- a/crates/wasi-common/src/snapshots/preview_0.rs +++ b/crates/wasi-common/src/snapshots/preview_0.rs @@ -16,7 +16,7 @@ use wiggle::GuestPtr; wiggle::from_witx!({ witx: ["$WASI_ROOT/phases/old/snapshot_0/witx/wasi_unstable.witx"], errors: { errno => Error }, - async: { wasi_unstable::{poll_oneoff, sched_yield} } + async: *, }); impl wiggle::GuestErrorType for types::Errno { @@ -334,79 +334,79 @@ convert_flags_bidirectional!( // This implementation, wherever possible, delegates directly to the Snapshot1 implementation, // performing the no-op type conversions along the way. #[wiggle::async_trait] -impl<'a> wasi_unstable::WasiUnstable for WasiCtx { - fn args_get<'b>( +impl wasi_unstable::WasiUnstable for WasiCtx { + async fn args_get<'a>( &self, - argv: &GuestPtr<'b, GuestPtr<'b, u8>>, - argv_buf: &GuestPtr<'b, u8>, + argv: &GuestPtr<'a, GuestPtr<'a, u8>>, + argv_buf: &GuestPtr<'a, u8>, ) -> Result<(), Error> { - Snapshot1::args_get(self, argv, argv_buf) + Snapshot1::args_get(self, argv, argv_buf).await } - fn args_sizes_get(&self) -> Result<(types::Size, types::Size), Error> { - Snapshot1::args_sizes_get(self) + async fn args_sizes_get(&self) -> Result<(types::Size, types::Size), Error> { + Snapshot1::args_sizes_get(self).await } - fn environ_get<'b>( + async fn environ_get<'a>( &self, - environ: &GuestPtr<'b, GuestPtr<'b, u8>>, - environ_buf: &GuestPtr<'b, u8>, + environ: &GuestPtr<'a, GuestPtr<'a, u8>>, + environ_buf: &GuestPtr<'a, u8>, ) -> Result<(), Error> { - Snapshot1::environ_get(self, environ, environ_buf) + Snapshot1::environ_get(self, environ, environ_buf).await } - fn environ_sizes_get(&self) -> Result<(types::Size, types::Size), Error> { - Snapshot1::environ_sizes_get(self) + async fn environ_sizes_get(&self) -> Result<(types::Size, types::Size), Error> { + Snapshot1::environ_sizes_get(self).await } - fn clock_res_get(&self, id: types::Clockid) -> Result { - Snapshot1::clock_res_get(self, id.into()) + async fn clock_res_get(&self, id: types::Clockid) -> Result { + Snapshot1::clock_res_get(self, id.into()).await } - fn clock_time_get( + async fn clock_time_get( &self, id: types::Clockid, precision: types::Timestamp, ) -> Result { - Snapshot1::clock_time_get(self, id.into(), precision) + Snapshot1::clock_time_get(self, id.into(), precision).await } - fn fd_advise( + async fn fd_advise( &self, fd: types::Fd, offset: types::Filesize, len: types::Filesize, advice: types::Advice, ) -> Result<(), Error> { - Snapshot1::fd_advise(self, fd.into(), offset, len, advice.into()) + Snapshot1::fd_advise(self, fd.into(), offset, len, advice.into()).await } - fn fd_allocate( + async fn fd_allocate( &self, fd: types::Fd, offset: types::Filesize, len: types::Filesize, ) -> Result<(), Error> { - Snapshot1::fd_allocate(self, fd.into(), offset, len) + Snapshot1::fd_allocate(self, fd.into(), offset, len).await } - fn fd_close(&self, fd: types::Fd) -> Result<(), Error> { - Snapshot1::fd_close(self, fd.into()) + async fn fd_close(&self, fd: types::Fd) -> Result<(), Error> { + Snapshot1::fd_close(self, fd.into()).await } - fn fd_datasync(&self, fd: types::Fd) -> Result<(), Error> { - Snapshot1::fd_datasync(self, fd.into()) + async fn fd_datasync(&self, fd: types::Fd) -> Result<(), Error> { + Snapshot1::fd_datasync(self, fd.into()).await } - fn fd_fdstat_get(&self, fd: types::Fd) -> Result { - Ok(Snapshot1::fd_fdstat_get(self, fd.into())?.into()) + async fn fd_fdstat_get(&self, fd: types::Fd) -> Result { + Ok(Snapshot1::fd_fdstat_get(self, fd.into()).await?.into()) } - fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<(), Error> { - Snapshot1::fd_fdstat_set_flags(self, fd.into(), flags.into()) + async fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<(), Error> { + Snapshot1::fd_fdstat_set_flags(self, fd.into(), flags.into()).await } - fn fd_fdstat_set_rights( + async fn fd_fdstat_set_rights( &self, fd: types::Fd, fs_rights_base: types::Rights, @@ -418,24 +418,29 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { fs_rights_base.into(), fs_rights_inheriting.into(), ) + .await } - fn fd_filestat_get(&self, fd: types::Fd) -> Result { - Ok(Snapshot1::fd_filestat_get(self, fd.into())?.into()) + async fn fd_filestat_get(&self, fd: types::Fd) -> Result { + Ok(Snapshot1::fd_filestat_get(self, fd.into())?.into()).await } - fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<(), Error> { - Snapshot1::fd_filestat_set_size(self, fd.into(), size) + async fn fd_filestat_set_size( + &self, + fd: types::Fd, + size: types::Filesize, + ) -> Result<(), Error> { + Snapshot1::fd_filestat_set_size(self, fd.into(), size).await } - fn fd_filestat_set_times( + async fn fd_filestat_set_times( &self, fd: types::Fd, atim: types::Timestamp, mtim: types::Timestamp, fst_flags: types::Fstflags, ) -> Result<(), Error> { - Snapshot1::fd_filestat_set_times(self, fd.into(), atim, mtim, fst_flags.into()) + Snapshot1::fd_filestat_set_times(self, fd.into(), atim, mtim, fst_flags.into()).await } // NOTE on fd_read, fd_pread, fd_write, fd_pwrite implementations: @@ -446,7 +451,11 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { // The bodies of these functions is mostly about converting the GuestPtr and types::-based // representation to a std::io::IoSlice(Mut) representation. - fn fd_read(&self, fd: types::Fd, iovs: &types::IovecArray<'_>) -> Result { + async fn fd_read<'a>( + &self, + fd: types::Fd, + iovs: &types::IovecArray<'a>, + ) -> Result { let table = self.table(); let f = table.get_file(u32::from(fd))?.get_cap(FileCaps::READ)?; @@ -468,10 +477,10 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { Ok(types::Size::try_from(bytes_read)?) } - fn fd_pread( + async fn fd_pread<'a>( &self, fd: types::Fd, - iovs: &types::IovecArray<'_>, + iovs: &types::IovecArray<'a>, offset: types::Filesize, ) -> Result { let table = self.table(); @@ -497,10 +506,10 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { Ok(types::Size::try_from(bytes_read)?) } - fn fd_write( + async fn fd_write<'a>( &self, fd: types::Fd, - ciovs: &types::CiovecArray<'_>, + ciovs: &types::CiovecArray<'a>, ) -> Result { let table = self.table(); let f = table.get_file(u32::from(fd))?.get_cap(FileCaps::WRITE)?; @@ -523,10 +532,10 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { Ok(types::Size::try_from(bytes_written)?) } - fn fd_pwrite( + async fn fd_pwrite<'a>( &self, fd: types::Fd, - ciovs: &types::CiovecArray<'_>, + ciovs: &types::CiovecArray<'a>, offset: types::Filesize, ) -> Result { let table = self.table(); @@ -552,72 +561,76 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { Ok(types::Size::try_from(bytes_written)?) } - fn fd_prestat_get(&self, fd: types::Fd) -> Result { - Ok(Snapshot1::fd_prestat_get(self, fd.into())?.into()) + async fn fd_prestat_get(&self, fd: types::Fd) -> Result { + Ok(Snapshot1::fd_prestat_get(self, fd.into()).await?.into()) } - fn fd_prestat_dir_name( + async fn fd_prestat_dir_name<'a>( &self, fd: types::Fd, - path: &GuestPtr, + path: &GuestPtr<'a, u8>, path_max_len: types::Size, ) -> Result<(), Error> { - Snapshot1::fd_prestat_dir_name(self, fd.into(), path, path_max_len) + Snapshot1::fd_prestat_dir_name(self, fd.into(), path, path_max_len).await } - fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<(), Error> { - Snapshot1::fd_renumber(self, from.into(), to.into()) + async fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<(), Error> { + Snapshot1::fd_renumber(self, from.into(), to.into()).await } - fn fd_seek( + async fn fd_seek( &self, fd: types::Fd, offset: types::Filedelta, whence: types::Whence, ) -> Result { - Snapshot1::fd_seek(self, fd.into(), offset, whence.into()) + Snapshot1::fd_seek(self, fd.into(), offset, whence.into()).await } - fn fd_sync(&self, fd: types::Fd) -> Result<(), Error> { - Snapshot1::fd_sync(self, fd.into()) + async fn fd_sync(&self, fd: types::Fd) -> Result<(), Error> { + Snapshot1::fd_sync(self, fd.into()).await } - fn fd_tell(&self, fd: types::Fd) -> Result { - Snapshot1::fd_tell(self, fd.into()) + async fn fd_tell(&self, fd: types::Fd) -> Result { + Snapshot1::fd_tell(self, fd.into()).await } - fn fd_readdir( + async fn fd_readdir<'a>( &self, fd: types::Fd, - buf: &GuestPtr, + buf: &GuestPtr<'a, u8>, buf_len: types::Size, cookie: types::Dircookie, ) -> Result { - Snapshot1::fd_readdir(self, fd.into(), buf, buf_len, cookie) + Snapshot1::fd_readdir(self, fd.into(), buf, buf_len, cookie).await } - fn path_create_directory( + async fn path_create_directory<'a>( &self, dirfd: types::Fd, - path: &GuestPtr<'_, str>, + path: &GuestPtr<'a, str>, ) -> Result<(), Error> { - Snapshot1::path_create_directory(self, dirfd.into(), path) + Snapshot1::path_create_directory(self, dirfd.into(), path).await } - fn path_filestat_get( + async fn path_filestat_get<'a>( &self, dirfd: types::Fd, flags: types::Lookupflags, - path: &GuestPtr<'_, str>, + path: &GuestPtr<'a, str>, ) -> Result { - Ok(Snapshot1::path_filestat_get(self, dirfd.into(), flags.into(), path)?.into()) + Ok( + Snapshot1::path_filestat_get(self, dirfd.into(), flags.into(), path) + .await? + .into(), + ) } - fn path_filestat_set_times( + async fn path_filestat_set_times<'a>( &self, dirfd: types::Fd, flags: types::Lookupflags, - path: &GuestPtr<'_, str>, + path: &GuestPtr<'a, str>, atim: types::Timestamp, mtim: types::Timestamp, fst_flags: types::Fstflags, @@ -631,15 +644,16 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { mtim, fst_flags.into(), ) + .await } - fn path_link( + async fn path_link<'a>( &self, src_fd: types::Fd, src_flags: types::Lookupflags, - src_path: &GuestPtr<'_, str>, + src_path: &GuestPtr<'a, str>, target_fd: types::Fd, - target_path: &GuestPtr<'_, str>, + target_path: &GuestPtr<'a, str>, ) -> Result<(), Error> { Snapshot1::path_link( self, @@ -649,13 +663,14 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { target_fd.into(), target_path, ) + .await } - fn path_open( + async fn path_open<'a>( &self, dirfd: types::Fd, dirflags: types::Lookupflags, - path: &GuestPtr<'_, str>, + path: &GuestPtr<'a, str>, oflags: types::Oflags, fs_rights_base: types::Rights, fs_rights_inheriting: types::Rights, @@ -670,49 +685,54 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { fs_rights_base.into(), fs_rights_inheriting.into(), fdflags.into(), - )? + ) + .await? .into()) } - fn path_readlink( + async fn path_readlink<'a>( &self, dirfd: types::Fd, - path: &GuestPtr<'_, str>, - buf: &GuestPtr, + path: &GuestPtr<'a, str>, + buf: &GuestPtr<'a, u8>, buf_len: types::Size, ) -> Result { - Snapshot1::path_readlink(self, dirfd.into(), path, buf, buf_len) + Snapshot1::path_readlink(self, dirfd.into(), path, buf, buf_len).await } - fn path_remove_directory( + async fn path_remove_directory<'a>( &self, dirfd: types::Fd, - path: &GuestPtr<'_, str>, + path: &GuestPtr<'a, str>, ) -> Result<(), Error> { - Snapshot1::path_remove_directory(self, dirfd.into(), path) + Snapshot1::path_remove_directory(self, dirfd.into(), path).await } - fn path_rename( + async fn path_rename<'a>( &self, src_fd: types::Fd, - src_path: &GuestPtr<'_, str>, + src_path: &GuestPtr<'a, str>, dest_fd: types::Fd, - dest_path: &GuestPtr<'_, str>, + dest_path: &GuestPtr<'a, str>, ) -> Result<(), Error> { - Snapshot1::path_rename(self, src_fd.into(), src_path, dest_fd.into(), dest_path) + Snapshot1::path_rename(self, src_fd.into(), src_path, dest_fd.into(), dest_path).await } - fn path_symlink( + async fn path_symlink<'a>( &self, - src_path: &GuestPtr<'_, str>, + src_path: &GuestPtr<'a, str>, dirfd: types::Fd, - dest_path: &GuestPtr<'_, str>, + dest_path: &GuestPtr<'a, str>, ) -> Result<(), Error> { - Snapshot1::path_symlink(self, src_path, dirfd.into(), dest_path) + Snapshot1::path_symlink(self, src_path, dirfd.into(), dest_path).await } - fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<(), Error> { - Snapshot1::path_unlink_file(self, dirfd.into(), path) + async fn path_unlink_file<'a>( + &self, + dirfd: types::Fd, + path: &GuestPtr<'a, str>, + ) -> Result<(), Error> { + Snapshot1::path_unlink_file(self, dirfd.into(), path).await } // NOTE on poll_oneoff implementation: @@ -722,10 +742,10 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { // The implementations are identical, but the `types::` in scope locally is different. // The bodies of these functions is mostly about converting the GuestPtr and types::-based // representation to use the Poll abstraction. - async fn poll_oneoff<'b>( + async fn poll_oneoff<'a>( &self, - subs: &GuestPtr<'b, types::Subscription>, - events: &GuestPtr<'b, types::Event>, + subs: &GuestPtr<'a, types::Subscription>, + events: &GuestPtr<'a, types::Event>, nsubscriptions: types::Size, ) -> Result { if nsubscriptions == 0 { @@ -886,11 +906,11 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { Ok(num_results.try_into().expect("results fit into memory")) } - fn proc_exit(&self, status: types::Exitcode) -> wiggle::Trap { - Snapshot1::proc_exit(self, status) + async fn proc_exit(&self, status: types::Exitcode) -> wiggle::Trap { + Snapshot1::proc_exit(self, status).await } - fn proc_raise(&self, _sig: types::Signal) -> Result<(), Error> { + async fn proc_raise(&self, _sig: types::Signal) -> Result<(), Error> { Err(Error::trap("proc_raise unsupported")) } @@ -898,29 +918,33 @@ impl<'a> wasi_unstable::WasiUnstable for WasiCtx { Snapshot1::sched_yield(self).await } - fn random_get(&self, buf: &GuestPtr, buf_len: types::Size) -> Result<(), Error> { - Snapshot1::random_get(self, buf, buf_len) + async fn random_get<'a>( + &self, + buf: &GuestPtr<'a, u8>, + buf_len: types::Size, + ) -> Result<(), Error> { + Snapshot1::random_get(self, buf, buf_len).await } - fn sock_recv( + async fn sock_recv<'a>( &self, _fd: types::Fd, - _ri_data: &types::IovecArray<'_>, + _ri_data: &types::IovecArray<'a>, _ri_flags: types::Riflags, ) -> Result<(types::Size, types::Roflags), Error> { Err(Error::trap("sock_recv unsupported")) } - fn sock_send( + async fn sock_send<'a>( &self, _fd: types::Fd, - _si_data: &types::CiovecArray<'_>, + _si_data: &types::CiovecArray<'a>, _si_flags: types::Siflags, ) -> Result { Err(Error::trap("sock_send unsupported")) } - fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<(), Error> { + async fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<(), Error> { Err(Error::trap("sock_shutdown unsupported")) } } diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index 7896189542..1d5315b723 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -22,7 +22,7 @@ use wiggle::GuestPtr; wiggle::from_witx!({ witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"], errors: { errno => Error }, - async: { wasi_snapshot_preview1::{poll_oneoff, sched_yield} } + async: * }); impl wiggle::GuestErrorType for types::Errno { @@ -191,7 +191,7 @@ impl TryFrom for types::Errno { #[wiggle::async_trait] impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { - fn args_get<'b>( + async fn args_get<'b>( &self, argv: &GuestPtr<'b, GuestPtr<'b, u8>>, argv_buf: &GuestPtr<'b, u8>, @@ -199,11 +199,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { self.args.write_to_guest(argv_buf, argv) } - fn args_sizes_get(&self) -> Result<(types::Size, types::Size), Error> { + async fn args_sizes_get(&self) -> Result<(types::Size, types::Size), Error> { Ok((self.args.number_elements(), self.args.cumulative_size())) } - fn environ_get<'b>( + async fn environ_get<'b>( &self, environ: &GuestPtr<'b, GuestPtr<'b, u8>>, environ_buf: &GuestPtr<'b, u8>, @@ -211,11 +211,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { self.env.write_to_guest(environ_buf, environ) } - fn environ_sizes_get(&self) -> Result<(types::Size, types::Size), Error> { + async fn environ_sizes_get(&self) -> Result<(types::Size, types::Size), Error> { Ok((self.env.number_elements(), self.env.cumulative_size())) } - fn clock_res_get(&self, id: types::Clockid) -> Result { + async fn clock_res_get(&self, id: types::Clockid) -> Result { let resolution = match id { types::Clockid::Realtime => Ok(self.clocks.system.resolution()), types::Clockid::Monotonic => Ok(self.clocks.monotonic.resolution()), @@ -226,7 +226,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(resolution.as_nanos().try_into()?) } - fn clock_time_get( + async fn clock_time_get( &self, id: types::Clockid, precision: types::Timestamp, @@ -251,7 +251,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } } - fn fd_advise( + async fn fd_advise( &self, fd: types::Fd, offset: types::Filesize, @@ -265,7 +265,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(()) } - fn fd_allocate( + async fn fd_allocate( &self, fd: types::Fd, offset: types::Filesize, @@ -278,7 +278,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(()) } - fn fd_close(&self, fd: types::Fd) -> Result<(), Error> { + async fn fd_close(&self, fd: types::Fd) -> Result<(), Error> { let mut table = self.table(); let fd = u32::from(fd); @@ -304,7 +304,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(()) } - fn fd_datasync(&self, fd: types::Fd) -> Result<(), Error> { + async fn fd_datasync(&self, fd: types::Fd) -> Result<(), Error> { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::DATASYNC)? @@ -312,7 +312,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(()) } - fn fd_fdstat_get(&self, fd: types::Fd) -> Result { + async fn fd_fdstat_get(&self, fd: types::Fd) -> Result { let table = self.table(); let fd = u32::from(fd); if table.is::(fd) { @@ -328,14 +328,14 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } } - fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<(), Error> { + async fn fd_fdstat_set_flags(&self, fd: types::Fd, flags: types::Fdflags) -> Result<(), Error> { self.table() .get_file_mut(u32::from(fd))? .get_cap(FileCaps::FDSTAT_SET_FLAGS)? .set_fdflags(FdFlags::from(flags)) } - fn fd_fdstat_set_rights( + async fn fd_fdstat_set_rights( &self, fd: types::Fd, fs_rights_base: types::Rights, @@ -357,7 +357,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } } - fn fd_filestat_get(&self, fd: types::Fd) -> Result { + async fn fd_filestat_get(&self, fd: types::Fd) -> Result { let table = self.table(); let fd = u32::from(fd); if table.is::(fd) { @@ -377,7 +377,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } } - fn fd_filestat_set_size(&self, fd: types::Fd, size: types::Filesize) -> Result<(), Error> { + async fn fd_filestat_set_size( + &self, + fd: types::Fd, + size: types::Filesize, + ) -> Result<(), Error> { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::FILESTAT_SET_SIZE)? @@ -385,7 +389,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(()) } - fn fd_filestat_set_times( + async fn fd_filestat_set_times( &self, fd: types::Fd, atim: types::Timestamp, @@ -420,7 +424,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } } - fn fd_read(&self, fd: types::Fd, iovs: &types::IovecArray<'_>) -> Result { + async fn fd_read<'a>( + &self, + fd: types::Fd, + iovs: &types::IovecArray<'a>, + ) -> Result { let table = self.table(); let f = table.get_file(u32::from(fd))?.get_cap(FileCaps::READ)?; @@ -442,10 +450,10 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(types::Size::try_from(bytes_read)?) } - fn fd_pread( + async fn fd_pread<'a>( &self, fd: types::Fd, - iovs: &types::IovecArray<'_>, + iovs: &types::IovecArray<'a>, offset: types::Filesize, ) -> Result { let table = self.table(); @@ -471,10 +479,10 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(types::Size::try_from(bytes_read)?) } - fn fd_write( + async fn fd_write<'a>( &self, fd: types::Fd, - ciovs: &types::CiovecArray<'_>, + ciovs: &types::CiovecArray<'a>, ) -> Result { let table = self.table(); let f = table.get_file(u32::from(fd))?.get_cap(FileCaps::WRITE)?; @@ -497,10 +505,10 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(types::Size::try_from(bytes_written)?) } - fn fd_pwrite( + async fn fd_pwrite<'a>( &self, fd: types::Fd, - ciovs: &types::CiovecArray<'_>, + ciovs: &types::CiovecArray<'a>, offset: types::Filesize, ) -> Result { let table = self.table(); @@ -526,7 +534,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(types::Size::try_from(bytes_written)?) } - fn fd_prestat_get(&self, fd: types::Fd) -> Result { + async fn fd_prestat_get(&self, fd: types::Fd) -> Result { let table = self.table(); let dir_entry: Ref = table.get(u32::from(fd)).map_err(|_| Error::badf())?; if let Some(ref preopen) = dir_entry.preopen_path() { @@ -538,10 +546,10 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } } - fn fd_prestat_dir_name( + async fn fd_prestat_dir_name<'a>( &self, fd: types::Fd, - path: &GuestPtr, + path: &GuestPtr<'a, u8>, path_max_len: types::Size, ) -> Result<(), Error> { let table = self.table(); @@ -562,7 +570,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Err(Error::not_supported()) } } - fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<(), Error> { + async fn fd_renumber(&self, from: types::Fd, to: types::Fd) -> Result<(), Error> { let mut table = self.table(); let from = u32::from(from); let to = u32::from(to); @@ -579,7 +587,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(()) } - fn fd_seek( + async fn fd_seek( &self, fd: types::Fd, offset: types::Filedelta, @@ -606,7 +614,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(newoffset) } - fn fd_sync(&self, fd: types::Fd) -> Result<(), Error> { + async fn fd_sync(&self, fd: types::Fd) -> Result<(), Error> { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::SYNC)? @@ -614,7 +622,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(()) } - fn fd_tell(&self, fd: types::Fd) -> Result { + async fn fd_tell(&self, fd: types::Fd) -> Result { // XXX should this be stream_position? let offset = self .table() @@ -624,10 +632,10 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(offset) } - fn fd_readdir( + async fn fd_readdir<'a>( &self, fd: types::Fd, - buf: &GuestPtr, + buf: &GuestPtr<'a, u8>, buf_len: types::Size, cookie: types::Dircookie, ) -> Result { @@ -677,10 +685,10 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(bufused) } - fn path_create_directory( + async fn path_create_directory<'a>( &self, dirfd: types::Fd, - path: &GuestPtr<'_, str>, + path: &GuestPtr<'a, str>, ) -> Result<(), Error> { self.table() .get_dir(u32::from(dirfd))? @@ -688,11 +696,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .create_dir(path.as_str()?.deref()) } - fn path_filestat_get( + async fn path_filestat_get<'a>( &self, dirfd: types::Fd, flags: types::Lookupflags, - path: &GuestPtr<'_, str>, + path: &GuestPtr<'a, str>, ) -> Result { let filestat = self .table() @@ -705,11 +713,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(types::Filestat::from(filestat)) } - fn path_filestat_set_times( + async fn path_filestat_set_times<'a>( &self, dirfd: types::Fd, flags: types::Lookupflags, - path: &GuestPtr<'_, str>, + path: &GuestPtr<'a, str>, atim: types::Timestamp, mtim: types::Timestamp, fst_flags: types::Fstflags, @@ -732,13 +740,13 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { ) } - fn path_link( + async fn path_link<'a>( &self, src_fd: types::Fd, src_flags: types::Lookupflags, - src_path: &GuestPtr<'_, str>, + src_path: &GuestPtr<'a, str>, target_fd: types::Fd, - target_path: &GuestPtr<'_, str>, + target_path: &GuestPtr<'a, str>, ) -> Result<(), Error> { let table = self.table(); let src_dir = table @@ -760,11 +768,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { ) } - fn path_open( + async fn path_open<'a>( &self, dirfd: types::Fd, dirflags: types::Lookupflags, - path: &GuestPtr<'_, str>, + path: &GuestPtr<'a, str>, oflags: types::Oflags, fs_rights_base: types::Rights, fs_rights_inheriting: types::Rights, @@ -817,11 +825,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } } - fn path_readlink( + async fn path_readlink<'a>( &self, dirfd: types::Fd, - path: &GuestPtr<'_, str>, - buf: &GuestPtr, + path: &GuestPtr<'a, str>, + buf: &GuestPtr<'a, u8>, buf_len: types::Size, ) -> Result { let link = self @@ -842,10 +850,10 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(link_len as types::Size) } - fn path_remove_directory( + async fn path_remove_directory<'a>( &self, dirfd: types::Fd, - path: &GuestPtr<'_, str>, + path: &GuestPtr<'a, str>, ) -> Result<(), Error> { self.table() .get_dir(u32::from(dirfd))? @@ -853,12 +861,12 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .remove_dir(path.as_str()?.deref()) } - fn path_rename( + async fn path_rename<'a>( &self, src_fd: types::Fd, - src_path: &GuestPtr<'_, str>, + src_path: &GuestPtr<'a, str>, dest_fd: types::Fd, - dest_path: &GuestPtr<'_, str>, + dest_path: &GuestPtr<'a, str>, ) -> Result<(), Error> { let table = self.table(); let src_dir = table @@ -874,11 +882,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { ) } - fn path_symlink( + async fn path_symlink<'a>( &self, - src_path: &GuestPtr<'_, str>, + src_path: &GuestPtr<'a, str>, dirfd: types::Fd, - dest_path: &GuestPtr<'_, str>, + dest_path: &GuestPtr<'a, str>, ) -> Result<(), Error> { self.table() .get_dir(u32::from(dirfd))? @@ -886,17 +894,21 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .symlink(src_path.as_str()?.deref(), dest_path.as_str()?.deref()) } - fn path_unlink_file(&self, dirfd: types::Fd, path: &GuestPtr<'_, str>) -> Result<(), Error> { + async fn path_unlink_file<'a>( + &self, + dirfd: types::Fd, + path: &GuestPtr<'a, str>, + ) -> Result<(), Error> { self.table() .get_dir(u32::from(dirfd))? .get_cap(DirCaps::UNLINK_FILE)? .unlink_file(path.as_str()?.deref()) } - async fn poll_oneoff<'b>( + async fn poll_oneoff<'a>( &self, - subs: &GuestPtr<'b, types::Subscription>, - events: &GuestPtr<'b, types::Event>, + subs: &GuestPtr<'a, types::Subscription>, + events: &GuestPtr<'a, types::Event>, nsubscriptions: types::Size, ) -> Result { if nsubscriptions == 0 { @@ -1057,7 +1069,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { Ok(num_results.try_into().expect("results fit into memory")) } - fn proc_exit(&self, status: types::Exitcode) -> wiggle::Trap { + async fn proc_exit(&self, status: types::Exitcode) -> wiggle::Trap { // Check that the status is within WASI's range. if status < 126 { wiggle::Trap::I32Exit(status as i32) @@ -1066,7 +1078,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } } - fn proc_raise(&self, _sig: types::Signal) -> Result<(), Error> { + async fn proc_raise(&self, _sig: types::Signal) -> Result<(), Error> { Err(Error::trap("proc_raise unsupported")) } @@ -1074,31 +1086,35 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { self.sched.sched_yield().await } - fn random_get(&self, buf: &GuestPtr, buf_len: types::Size) -> Result<(), Error> { + async fn random_get<'a>( + &self, + buf: &GuestPtr<'a, u8>, + buf_len: types::Size, + ) -> Result<(), Error> { let mut buf = buf.as_array(buf_len).as_slice_mut()?; self.random.borrow_mut().try_fill_bytes(buf.deref_mut())?; Ok(()) } - fn sock_recv( + async fn sock_recv<'a>( &self, _fd: types::Fd, - _ri_data: &types::IovecArray<'_>, + _ri_data: &types::IovecArray<'a>, _ri_flags: types::Riflags, ) -> Result<(types::Size, types::Roflags), Error> { Err(Error::trap("sock_recv unsupported")) } - fn sock_send( + async fn sock_send<'a>( &self, _fd: types::Fd, - _si_data: &types::CiovecArray<'_>, + _si_data: &types::CiovecArray<'a>, _si_flags: types::Siflags, ) -> Result { Err(Error::trap("sock_send unsupported")) } - fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<(), Error> { + async fn sock_shutdown(&self, _fd: types::Fd, _how: types::Sdflags) -> Result<(), Error> { Err(Error::trap("sock_shutdown unsupported")) } } From 00e58567d926f6d7598aa462ced2f557533e8fae Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 15:17:31 -0700 Subject: [PATCH 16/72] WasiFile: all operations are now async only fn as_any(&self) -> &dyn Any doesnt get to be async. --- crates/wasi-common/src/file.rs | 50 ++++++----- crates/wasi-common/src/pipe.rs | 90 +++++++++++-------- crates/wasi-common/src/snapshots/preview_0.rs | 10 +-- crates/wasi-common/src/snapshots/preview_1.rs | 39 +++++--- 4 files changed, 114 insertions(+), 75 deletions(-) diff --git a/crates/wasi-common/src/file.rs b/crates/wasi-common/src/file.rs index 65fba7cd4a..b8eadb3673 100644 --- a/crates/wasi-common/src/file.rs +++ b/crates/wasi-common/src/file.rs @@ -4,30 +4,38 @@ use std::any::Any; use std::cell::{Ref, RefMut}; use std::ops::{Deref, DerefMut}; +#[wiggle::async_trait] pub trait WasiFile { fn as_any(&self) -> &dyn Any; - fn datasync(&self) -> Result<(), Error>; // write op - fn sync(&self) -> Result<(), Error>; // file op - fn get_filetype(&self) -> Result; // file op - fn get_fdflags(&self) -> Result; // file op - fn set_fdflags(&mut self, flags: FdFlags) -> Result<(), Error>; // file op - fn get_filestat(&self) -> Result; // split out get_length as a read & write op, rest is a file op - fn set_filestat_size(&self, _size: u64) -> Result<(), Error>; // write op - fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error>; // file op - fn allocate(&self, offset: u64, len: u64) -> Result<(), Error>; // write op - fn set_times( + async fn datasync(&self) -> Result<(), Error>; // write op + async fn sync(&self) -> Result<(), Error>; // file op + async fn get_filetype(&self) -> Result; // file op + async fn get_fdflags(&self) -> Result; // file op + async fn set_fdflags(&mut self, flags: FdFlags) -> Result<(), Error>; // file op + async fn get_filestat(&self) -> Result; // split out get_length as a read & write op, rest is a file op + async fn set_filestat_size(&self, _size: u64) -> Result<(), Error>; // write op + async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error>; // file op + async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error>; // write op + async fn set_times( &self, atime: Option, mtime: Option, ) -> Result<(), Error>; - fn read_vectored(&self, bufs: &mut [std::io::IoSliceMut]) -> Result; // read op - fn read_vectored_at(&self, bufs: &mut [std::io::IoSliceMut], offset: u64) - -> Result; // file op - fn write_vectored(&self, bufs: &[std::io::IoSlice]) -> Result; // write op - fn write_vectored_at(&self, bufs: &[std::io::IoSlice], offset: u64) -> Result; // file op - fn seek(&self, pos: std::io::SeekFrom) -> Result; // file op that generates a new stream from a file will supercede this - fn peek(&self, buf: &mut [u8]) -> Result; // read op - fn num_ready_bytes(&self) -> Result; // read op + async fn read_vectored<'a>(&self, bufs: &mut [std::io::IoSliceMut<'a>]) -> Result; // read op + async fn read_vectored_at<'a>( + &self, + bufs: &mut [std::io::IoSliceMut<'a>], + offset: u64, + ) -> Result; // file op + async fn write_vectored<'a>(&self, bufs: &[std::io::IoSlice<'a>]) -> Result; // write op + async fn write_vectored_at<'a>( + &self, + bufs: &[std::io::IoSlice<'a>], + offset: u64, + ) -> Result; // file op + async fn seek(&self, pos: std::io::SeekFrom) -> Result; // file op that generates a new stream from a file will supercede this + async fn peek(&self, buf: &mut [u8]) -> Result; // read op + async fn num_ready_bytes(&self) -> Result; // read op } #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -111,11 +119,11 @@ impl FileEntry { Ok(()) } - pub fn get_fdstat(&self) -> Result { + pub async fn get_fdstat(&self) -> Result { Ok(FdStat { - filetype: self.file.get_filetype()?, + filetype: self.file.get_filetype().await?, caps: self.caps, - flags: self.file.get_fdflags()?, + flags: self.file.get_fdflags().await?, }) } } diff --git a/crates/wasi-common/src/pipe.rs b/crates/wasi-common/src/pipe.rs index 8bbc8d73b7..5e5d418767 100644 --- a/crates/wasi-common/src/pipe.rs +++ b/crates/wasi-common/src/pipe.rs @@ -105,30 +105,31 @@ impl From<&str> for ReadPipe> { } } +#[wiggle::async_trait] impl WasiFile for ReadPipe { fn as_any(&self) -> &dyn Any { self } - fn datasync(&self) -> Result<(), Error> { + async fn datasync(&self) -> Result<(), Error> { Ok(()) // trivial: no implementation needed } - fn sync(&self) -> Result<(), Error> { + async fn sync(&self) -> Result<(), Error> { Ok(()) // trivial } - fn get_filetype(&self) -> Result { + async fn get_filetype(&self) -> Result { Ok(FileType::Pipe) } - fn get_fdflags(&self) -> Result { + async fn get_fdflags(&self) -> Result { Ok(FdFlags::empty()) } - fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { + async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { Err(Error::badf()) } - fn get_filestat(&self) -> Result { + async fn get_filestat(&self) -> Result { Ok(Filestat { device_id: 0, inode: 0, - filetype: self.get_filetype()?, + filetype: self.get_filetype().await?, nlink: 0, size: 0, // XXX no way to get a size out of a Read :( atim: None, @@ -136,42 +137,50 @@ impl WasiFile for ReadPipe { ctim: None, }) } - fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { + async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { Err(Error::badf()) } - fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { + async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { Err(Error::badf()) } - fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { + async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { Err(Error::badf()) } - fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result { + async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { let n = self.borrow().read_vectored(bufs)?; Ok(n.try_into()?) } - fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result { + async fn read_vectored_at<'a>( + &self, + bufs: &mut [io::IoSliceMut<'a>], + offset: u64, + ) -> Result { Err(Error::badf()) } - fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result { + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { Err(Error::badf()) } - fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result { + async fn write_vectored_at<'a>( + &self, + bufs: &[io::IoSlice<'a>], + offset: u64, + ) -> Result { Err(Error::badf()) } - fn seek(&self, pos: std::io::SeekFrom) -> Result { + async fn seek(&self, pos: std::io::SeekFrom) -> Result { Err(Error::badf()) } - fn peek(&self, buf: &mut [u8]) -> Result { + async fn peek(&self, buf: &mut [u8]) -> Result { Err(Error::badf()) } - fn set_times( + async fn set_times( &self, atime: Option, mtime: Option, ) -> Result<(), Error> { Err(Error::badf()) } - fn num_ready_bytes(&self) -> Result { + async fn num_ready_bytes(&self) -> Result { Ok(0) } } @@ -249,30 +258,31 @@ impl WritePipe>> { } } +#[wiggle::async_trait] impl WasiFile for WritePipe { fn as_any(&self) -> &dyn Any { self } - fn datasync(&self) -> Result<(), Error> { + async fn datasync(&self) -> Result<(), Error> { Ok(()) } - fn sync(&self) -> Result<(), Error> { + async fn sync(&self) -> Result<(), Error> { Ok(()) } - fn get_filetype(&self) -> Result { + async fn get_filetype(&self) -> Result { Ok(FileType::Pipe) } - fn get_fdflags(&self) -> Result { + async fn get_fdflags(&self) -> Result { Ok(FdFlags::APPEND) } - fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { + async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { Err(Error::badf()) } - fn get_filestat(&self) -> Result { + async fn get_filestat(&self) -> Result { Ok(Filestat { device_id: 0, inode: 0, - filetype: self.get_filetype()?, + filetype: self.get_filetype().await?, nlink: 0, size: 0, // XXX no way to get a size out of a Write :( atim: None, @@ -280,42 +290,50 @@ impl WasiFile for WritePipe { ctim: None, }) } - fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { + async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { Err(Error::badf()) } - fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { + async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { Err(Error::badf()) } - fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { + async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { Err(Error::badf()) } - fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result { + async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { Err(Error::badf()) } - fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result { + async fn read_vectored_at<'a>( + &self, + bufs: &mut [io::IoSliceMut<'a>], + offset: u64, + ) -> Result { Err(Error::badf()) } - fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result { + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { let n = self.borrow().write_vectored(bufs)?; Ok(n.try_into()?) } - fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result { + async fn write_vectored_at<'a>( + &self, + bufs: &[io::IoSlice<'a>], + offset: u64, + ) -> Result { Err(Error::badf()) } - fn seek(&self, pos: std::io::SeekFrom) -> Result { + async fn seek(&self, pos: std::io::SeekFrom) -> Result { Err(Error::badf()) } - fn peek(&self, buf: &mut [u8]) -> Result { + async fn peek(&self, buf: &mut [u8]) -> Result { Err(Error::badf()) } - fn set_times( + async fn set_times( &self, atime: Option, mtime: Option, ) -> Result<(), Error> { Err(Error::badf()) } - fn num_ready_bytes(&self) -> Result { + async fn num_ready_bytes(&self) -> Result { Ok(0) } } diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs index a428c3f7ea..362789bc35 100644 --- a/crates/wasi-common/src/snapshots/preview_0.rs +++ b/crates/wasi-common/src/snapshots/preview_0.rs @@ -422,7 +422,7 @@ impl wasi_unstable::WasiUnstable for WasiCtx { } async fn fd_filestat_get(&self, fd: types::Fd) -> Result { - Ok(Snapshot1::fd_filestat_get(self, fd.into())?.into()).await + Ok(Snapshot1::fd_filestat_get(self, fd.into()).await?.into()) } async fn fd_filestat_set_size( @@ -473,7 +473,7 @@ impl wasi_unstable::WasiUnstable for WasiCtx { .map(|s| IoSliceMut::new(&mut *s)) .collect(); - let bytes_read = f.read_vectored(&mut ioslices)?; + let bytes_read = f.read_vectored(&mut ioslices).await?; Ok(types::Size::try_from(bytes_read)?) } @@ -502,7 +502,7 @@ impl wasi_unstable::WasiUnstable for WasiCtx { .map(|s| IoSliceMut::new(&mut *s)) .collect(); - let bytes_read = f.read_vectored_at(&mut ioslices, offset)?; + let bytes_read = f.read_vectored_at(&mut ioslices, offset).await?; Ok(types::Size::try_from(bytes_read)?) } @@ -527,7 +527,7 @@ impl wasi_unstable::WasiUnstable for WasiCtx { .iter() .map(|s| IoSlice::new(s.deref())) .collect(); - let bytes_written = f.write_vectored(&ioslices)?; + let bytes_written = f.write_vectored(&ioslices).await?; Ok(types::Size::try_from(bytes_written)?) } @@ -556,7 +556,7 @@ impl wasi_unstable::WasiUnstable for WasiCtx { .iter() .map(|s| IoSlice::new(s.deref())) .collect(); - let bytes_written = f.write_vectored_at(&ioslices, offset)?; + let bytes_written = f.write_vectored_at(&ioslices, offset).await?; Ok(types::Size::try_from(bytes_written)?) } diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index 1d5315b723..4d1189f761 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -22,6 +22,9 @@ use wiggle::GuestPtr; wiggle::from_witx!({ witx: ["$WASI_ROOT/phases/snapshot/witx/wasi_snapshot_preview1.witx"], errors: { errno => Error }, + // Note: not every function actually needs to be async, however, nearly all of them do, and + // keeping that set the same in this macro and the wasmtime_wiggle / lucet_wiggle macros is + // tedious, and there is no cost to having a sync function be async in this case. async: * }); @@ -261,7 +264,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::ADVISE)? - .advise(offset, len, advice.into())?; + .advise(offset, len, advice.into()) + .await?; Ok(()) } @@ -274,7 +278,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::ALLOCATE)? - .allocate(offset, len)?; + .allocate(offset, len) + .await?; Ok(()) } @@ -308,7 +313,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::DATASYNC)? - .datasync()?; + .datasync() + .await?; Ok(()) } @@ -317,7 +323,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { let fd = u32::from(fd); if table.is::(fd) { let file_entry: Ref = table.get(fd)?; - let fdstat = file_entry.get_fdstat()?; + let fdstat = file_entry.get_fdstat().await?; Ok(types::Fdstat::from(&fdstat)) } else if table.is::(fd) { let dir_entry: Ref = table.get(fd)?; @@ -333,6 +339,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .get_file_mut(u32::from(fd))? .get_cap(FileCaps::FDSTAT_SET_FLAGS)? .set_fdflags(FdFlags::from(flags)) + .await } async fn fd_fdstat_set_rights( @@ -364,7 +371,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { let filestat = table .get_file(fd)? .get_cap(FileCaps::FILESTAT_GET)? - .get_filestat()?; + .get_filestat() + .await?; Ok(filestat.into()) } else if table.is::(fd) { let filestat = table @@ -385,7 +393,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::FILESTAT_SET_SIZE)? - .set_filestat_size(size)?; + .set_filestat_size(size) + .await?; Ok(()) } @@ -413,6 +422,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .expect("checked that entry is file") .get_cap(FileCaps::FILESTAT_SET_TIMES)? .set_times(atim, mtim) + .await } else if table.is::(fd) { table .get_dir(fd) @@ -446,7 +456,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .map(|s| IoSliceMut::new(&mut *s)) .collect(); - let bytes_read = f.read_vectored(&mut ioslices)?; + let bytes_read = f.read_vectored(&mut ioslices).await?; Ok(types::Size::try_from(bytes_read)?) } @@ -475,7 +485,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .map(|s| IoSliceMut::new(&mut *s)) .collect(); - let bytes_read = f.read_vectored_at(&mut ioslices, offset)?; + let bytes_read = f.read_vectored_at(&mut ioslices, offset).await?; Ok(types::Size::try_from(bytes_read)?) } @@ -500,7 +510,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .iter() .map(|s| IoSlice::new(s.deref())) .collect(); - let bytes_written = f.write_vectored(&ioslices)?; + let bytes_written = f.write_vectored(&ioslices).await?; Ok(types::Size::try_from(bytes_written)?) } @@ -529,7 +539,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .iter() .map(|s| IoSlice::new(s.deref())) .collect(); - let bytes_written = f.write_vectored_at(&ioslices, offset)?; + let bytes_written = f.write_vectored_at(&ioslices, offset).await?; Ok(types::Size::try_from(bytes_written)?) } @@ -610,7 +620,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .table() .get_file(u32::from(fd))? .get_cap(required_caps)? - .seek(whence)?; + .seek(whence) + .await?; Ok(newoffset) } @@ -618,7 +629,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { self.table() .get_file(u32::from(fd))? .get_cap(FileCaps::SYNC)? - .sync()?; + .sync() + .await?; Ok(()) } @@ -628,7 +640,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .table() .get_file(u32::from(fd))? .get_cap(FileCaps::TELL)? - .seek(std::io::SeekFrom::Current(0))?; + .seek(std::io::SeekFrom::Current(0)) + .await?; Ok(offset) } From 564e43d1b3810d25d71e20295325f46cb3c90d8d Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 15:22:25 -0700 Subject: [PATCH 17/72] WasiDir: make all operations async --- crates/wasi-common/src/dir.rs | 34 ++++++++----- crates/wasi-common/src/snapshots/preview_1.rs | 48 ++++++++++++------- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/crates/wasi-common/src/dir.rs b/crates/wasi-common/src/dir.rs index 1c7e3b2c56..df8850c29f 100644 --- a/crates/wasi-common/src/dir.rs +++ b/crates/wasi-common/src/dir.rs @@ -6,9 +6,10 @@ use std::cell::Ref; use std::ops::Deref; use std::path::PathBuf; +#[wiggle::async_trait] pub trait WasiDir { fn as_any(&self) -> &dyn Any; - fn open_file( + async fn open_file( &self, symlink_follow: bool, path: &str, @@ -17,26 +18,33 @@ pub trait WasiDir { write: bool, fdflags: FdFlags, ) -> Result, Error>; - fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error>; - fn create_dir(&self, path: &str) -> Result<(), Error>; - fn readdir( + async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error>; + async fn create_dir(&self, path: &str) -> Result<(), Error>; + // XXX the iterator here needs to be asyncified as well! + async fn readdir( &self, cursor: ReaddirCursor, ) -> Result>>, Error>; - fn symlink(&self, old_path: &str, new_path: &str) -> Result<(), Error>; - fn remove_dir(&self, path: &str) -> Result<(), Error>; - fn unlink_file(&self, path: &str) -> Result<(), Error>; - fn read_link(&self, path: &str) -> Result; - fn get_filestat(&self) -> Result; - fn get_path_filestat(&self, path: &str, follow_symlinks: bool) -> Result; - fn rename(&self, path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error>; - fn hard_link( + async fn symlink(&self, old_path: &str, new_path: &str) -> Result<(), Error>; + async fn remove_dir(&self, path: &str) -> Result<(), Error>; + async fn unlink_file(&self, path: &str) -> Result<(), Error>; + async fn read_link(&self, path: &str) -> Result; + async fn get_filestat(&self) -> Result; + async fn get_path_filestat(&self, path: &str, follow_symlinks: bool) + -> Result; + async fn rename( + &self, + path: &str, + dest_dir: &dyn WasiDir, + dest_path: &str, + ) -> Result<(), Error>; + async fn hard_link( &self, path: &str, target_dir: &dyn WasiDir, target_path: &str, ) -> Result<(), Error>; - fn set_times( + async fn set_times( &self, path: &str, atime: Option, diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index 4d1189f761..4d04e6e047 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -378,7 +378,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { let filestat = table .get_dir(fd)? .get_cap(DirCaps::FILESTAT_GET)? - .get_filestat()?; + .get_filestat() + .await?; Ok(filestat.into()) } else { Err(Error::badf()) @@ -429,6 +430,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .expect("checked that entry is dir") .get_cap(DirCaps::FILESTAT_SET_TIMES)? .set_times(".", atim, mtim, false) + .await } else { Err(Error::badf()) } @@ -658,7 +660,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .table() .get_dir(u32::from(fd))? .get_cap(DirCaps::READDIR)? - .readdir(ReaddirCursor::from(cookie))? + .readdir(ReaddirCursor::from(cookie)) + .await? { let entity = entity?; let dirent_raw = dirent_bytes(types::Dirent::try_from(&entity)?); @@ -707,6 +710,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .get_dir(u32::from(dirfd))? .get_cap(DirCaps::CREATE_DIRECTORY)? .create_dir(path.as_str()?.deref()) + .await } async fn path_filestat_get<'a>( @@ -722,7 +726,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .get_path_filestat( path.as_str()?.deref(), flags.contains(types::Lookupflags::SYMLINK_FOLLOW), - )?; + ) + .await?; Ok(types::Filestat::from(filestat)) } @@ -751,6 +756,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { mtim, flags.contains(types::Lookupflags::SYMLINK_FOLLOW), ) + .await } async fn path_link<'a>( @@ -774,11 +780,13 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .context("symlink following on path_link is not supported")); } - src_dir.hard_link( - src_path.as_str()?.deref(), - target_dir.deref(), - target_path.as_str()?.deref(), - ) + src_dir + .hard_link( + src_path.as_str()?.deref(), + target_dir.deref(), + target_path.as_str()?.deref(), + ) + .await } async fn path_open<'a>( @@ -813,7 +821,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { let dir_caps = dir_entry.child_dir_caps(DirCaps::from(&fs_rights_base)); let file_caps = dir_entry.child_file_caps(FileCaps::from(&fs_rights_inheriting)); let dir = dir_entry.get_cap(DirCaps::OPEN)?; - let child_dir = dir.open_dir(symlink_follow, path.deref())?; + let child_dir = dir.open_dir(symlink_follow, path.deref()).await?; drop(dir); let fd = table.push(Box::new(DirEntry::new( dir_caps, file_caps, None, child_dir, @@ -831,7 +839,9 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { let write = file_caps.contains(FileCaps::WRITE) || file_caps.contains(FileCaps::ALLOCATE) || file_caps.contains(FileCaps::FILESTAT_SET_SIZE); - let file = dir.open_file(symlink_follow, path.deref(), oflags, read, write, fdflags)?; + let file = dir + .open_file(symlink_follow, path.deref(), oflags, read, write, fdflags) + .await?; drop(dir); let fd = table.push(Box::new(FileEntry::new(file_caps, file)))?; Ok(types::Fd::from(fd)) @@ -849,7 +859,8 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .table() .get_dir(u32::from(dirfd))? .get_cap(DirCaps::READLINK)? - .read_link(path.as_str()?.deref())? + .read_link(path.as_str()?.deref()) + .await? .into_os_string() .into_string() .map_err(|_| Error::illegal_byte_sequence().context("link contents"))?; @@ -872,6 +883,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .get_dir(u32::from(dirfd))? .get_cap(DirCaps::REMOVE_DIRECTORY)? .remove_dir(path.as_str()?.deref()) + .await } async fn path_rename<'a>( @@ -888,11 +900,13 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { let dest_dir = table .get_dir(u32::from(dest_fd))? .get_cap(DirCaps::RENAME_TARGET)?; - src_dir.rename( - src_path.as_str()?.deref(), - dest_dir.deref(), - dest_path.as_str()?.deref(), - ) + src_dir + .rename( + src_path.as_str()?.deref(), + dest_dir.deref(), + dest_path.as_str()?.deref(), + ) + .await } async fn path_symlink<'a>( @@ -905,6 +919,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .get_dir(u32::from(dirfd))? .get_cap(DirCaps::SYMLINK)? .symlink(src_path.as_str()?.deref(), dest_path.as_str()?.deref()) + .await } async fn path_unlink_file<'a>( @@ -916,6 +931,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { .get_dir(u32::from(dirfd))? .get_cap(DirCaps::UNLINK_FILE)? .unlink_file(path.as_str()?.deref()) + .await } async fn poll_oneoff<'a>( From c0e89b8da8a1abb85f94cf014d139e55e0c8cf68 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 15:43:19 -0700 Subject: [PATCH 18/72] wasi-cap-std-sync: WasiFile and WasiDir converted to async_traits --- crates/wasi-common/cap-std-sync/src/dir.rs | 99 ++++++++++++++----- crates/wasi-common/cap-std-sync/src/file.rs | 43 ++++---- .../cap-std-sync/src/sched/unix.rs | 2 +- crates/wasi-common/cap-std-sync/src/stdio.rs | 95 ++++++++++-------- 4 files changed, 155 insertions(+), 84 deletions(-) diff --git a/crates/wasi-common/cap-std-sync/src/dir.rs b/crates/wasi-common/cap-std-sync/src/dir.rs index 2552787900..13e8391452 100644 --- a/crates/wasi-common/cap-std-sync/src/dir.rs +++ b/crates/wasi-common/cap-std-sync/src/dir.rs @@ -17,11 +17,12 @@ impl Dir { } } +#[wiggle::async_trait] impl WasiDir for Dir { fn as_any(&self) -> &dyn Any { self } - fn open_file( + async fn open_file( &self, symlink_follow: bool, path: &str, @@ -84,7 +85,7 @@ impl WasiDir for Dir { Ok(Box::new(File::from_cap_std(f))) } - fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { + async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { let d = if symlink_follow { self.0.open_dir(Path::new(path))? } else { @@ -93,11 +94,11 @@ impl WasiDir for Dir { Ok(Box::new(Dir::from_cap_std(d))) } - fn create_dir(&self, path: &str) -> Result<(), Error> { + async fn create_dir(&self, path: &str) -> Result<(), Error> { self.0.create_dir(Path::new(path))?; Ok(()) } - fn readdir( + async fn readdir( &self, cursor: ReaddirCursor, ) -> Result>>, Error> { @@ -146,24 +147,24 @@ impl WasiDir for Dir { Ok(Box::new(rd)) } - fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { + async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { self.0.symlink(src_path, dest_path)?; Ok(()) } - fn remove_dir(&self, path: &str) -> Result<(), Error> { + async fn remove_dir(&self, path: &str) -> Result<(), Error> { self.0.remove_dir(Path::new(path))?; Ok(()) } - fn unlink_file(&self, path: &str) -> Result<(), Error> { + async fn unlink_file(&self, path: &str) -> Result<(), Error> { self.0.remove_file_or_symlink(Path::new(path))?; Ok(()) } - fn read_link(&self, path: &str) -> Result { + async fn read_link(&self, path: &str) -> Result { let link = self.0.read_link(Path::new(path))?; Ok(link) } - fn get_filestat(&self) -> Result { + async fn get_filestat(&self) -> Result { let meta = self.0.dir_metadata()?; Ok(Filestat { device_id: meta.dev(), @@ -176,7 +177,11 @@ impl WasiDir for Dir { ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), }) } - fn get_path_filestat(&self, path: &str, follow_symlinks: bool) -> Result { + async fn get_path_filestat( + &self, + path: &str, + follow_symlinks: bool, + ) -> Result { let meta = if follow_symlinks { self.0.metadata(Path::new(path))? } else { @@ -193,7 +198,12 @@ impl WasiDir for Dir { ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), }) } - fn rename(&self, src_path: &str, dest_dir: &dyn WasiDir, dest_path: &str) -> Result<(), Error> { + async fn rename( + &self, + src_path: &str, + dest_dir: &dyn WasiDir, + dest_path: &str, + ) -> Result<(), Error> { let dest_dir = dest_dir .as_any() .downcast_ref::() @@ -202,7 +212,7 @@ impl WasiDir for Dir { .rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path))?; Ok(()) } - fn hard_link( + async fn hard_link( &self, src_path: &str, target_dir: &dyn WasiDir, @@ -217,7 +227,7 @@ impl WasiDir for Dir { self.0.hard_link(src_path, &target_dir.0, target_path)?; Ok(()) } - fn set_times( + async fn set_times( &self, path: &str, atime: Option, @@ -261,7 +271,7 @@ mod test { let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(tempdir.path()) } .expect("open ambient temporary dir"); let preopen_dir = Dir::from_cap_std(preopen_dir); - wasi_common::WasiDir::open_dir(&preopen_dir, false, ".") + run(wasi_common::WasiDir::open_dir(&preopen_dir, false, ".")) .expect("open the same directory via WasiDir abstraction"); } @@ -275,9 +285,8 @@ mod test { fn readdir_into_map(dir: &dyn WasiDir) -> HashMap { let mut out = HashMap::new(); - for readdir_result in dir - .readdir(ReaddirCursor::from(0)) - .expect("readdir succeeds") + for readdir_result in + run(dir.readdir(ReaddirCursor::from(0))).expect("readdir succeeds") { let entity = readdir_result.expect("readdir entry is valid"); out.insert(entity.name.clone(), entity); @@ -303,16 +312,15 @@ mod test { assert!(entities.get(".").is_some()); assert!(entities.get("..").is_some()); - preopen_dir - .open_file( - false, - "file1", - OFlags::CREATE, - true, - false, - FdFlags::empty(), - ) - .expect("create file1"); + run(preopen_dir.open_file( + false, + "file1", + OFlags::CREATE, + true, + false, + FdFlags::empty(), + )) + .expect("create file1"); let entities = readdir_into_map(&preopen_dir); assert_eq!(entities.len(), 3, "should be ., .., file1 {:?}", entities); @@ -329,4 +337,41 @@ mod test { FileType::RegularFile ); } + + fn run(future: F) -> F::Output { + use std::pin::Pin; + use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + + let mut f = Pin::from(Box::new(future)); + let waker = dummy_waker(); + let mut cx = Context::from_waker(&waker); + match f.as_mut().poll(&mut cx) { + Poll::Ready(val) => return val, + Poll::Pending => { + panic!("Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store") + } + } + + fn dummy_waker() -> Waker { + return unsafe { Waker::from_raw(clone(5 as *const _)) }; + + unsafe fn clone(ptr: *const ()) -> RawWaker { + assert_eq!(ptr as usize, 5); + const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); + RawWaker::new(ptr, &VTABLE) + } + + unsafe fn wake(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + + unsafe fn wake_by_ref(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + + unsafe fn drop(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + } + } } diff --git a/crates/wasi-common/cap-std-sync/src/file.rs b/crates/wasi-common/cap-std-sync/src/file.rs index f89a090ea1..ba9ff2743a 100644 --- a/crates/wasi-common/cap-std-sync/src/file.rs +++ b/crates/wasi-common/cap-std-sync/src/file.rs @@ -20,27 +20,28 @@ impl File { } } +#[wiggle::async_trait] impl WasiFile for File { fn as_any(&self) -> &dyn Any { self } - fn datasync(&self) -> Result<(), Error> { + async fn datasync(&self) -> Result<(), Error> { self.0.sync_data()?; Ok(()) } - fn sync(&self) -> Result<(), Error> { + async fn sync(&self) -> Result<(), Error> { self.0.sync_all()?; Ok(()) } - fn get_filetype(&self) -> Result { + async fn get_filetype(&self) -> Result { let meta = self.0.metadata()?; Ok(filetype_from(&meta.file_type())) } - fn get_fdflags(&self) -> Result { + async fn get_fdflags(&self) -> Result { let fdflags = self.0.get_fd_flags()?; Ok(from_sysif_fdflags(fdflags)) } - fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { + async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { if fdflags.intersects( wasi_common::file::FdFlags::DSYNC | wasi_common::file::FdFlags::SYNC @@ -50,7 +51,7 @@ impl WasiFile for File { } Ok(self.0.set_fd_flags(to_sysif_fdflags(fdflags))?) } - fn get_filestat(&self) -> Result { + async fn get_filestat(&self) -> Result { let meta = self.0.metadata()?; Ok(Filestat { device_id: meta.dev(), @@ -63,19 +64,19 @@ impl WasiFile for File { ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), }) } - fn set_filestat_size(&self, size: u64) -> Result<(), Error> { + async fn set_filestat_size(&self, size: u64) -> Result<(), Error> { self.0.set_len(size)?; Ok(()) } - fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { + async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { self.0.advise(offset, len, convert_advice(advice))?; Ok(()) } - fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { + async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { self.0.allocate(offset, len)?; Ok(()) } - fn set_times( + async fn set_times( &self, atime: Option, mtime: Option, @@ -84,30 +85,38 @@ impl WasiFile for File { .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; Ok(()) } - fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result { + async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { let n = self.0.read_vectored(bufs)?; Ok(n.try_into()?) } - fn read_vectored_at(&self, bufs: &mut [io::IoSliceMut], offset: u64) -> Result { + async fn read_vectored_at<'a>( + &self, + bufs: &mut [io::IoSliceMut<'a>], + offset: u64, + ) -> Result { let n = self.0.read_vectored_at(bufs, offset)?; Ok(n.try_into()?) } - fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result { + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { let n = self.0.write_vectored(bufs)?; Ok(n.try_into()?) } - fn write_vectored_at(&self, bufs: &[io::IoSlice], offset: u64) -> Result { + async fn write_vectored_at<'a>( + &self, + bufs: &[io::IoSlice<'a>], + offset: u64, + ) -> Result { let n = self.0.write_vectored_at(bufs, offset)?; Ok(n.try_into()?) } - fn seek(&self, pos: std::io::SeekFrom) -> Result { + async fn seek(&self, pos: std::io::SeekFrom) -> Result { Ok(self.0.seek(pos)?) } - fn peek(&self, buf: &mut [u8]) -> Result { + async fn peek(&self, buf: &mut [u8]) -> Result { let n = self.0.peek(buf)?; Ok(n.try_into()?) } - fn num_ready_bytes(&self) -> Result { + async fn num_ready_bytes(&self) -> Result { Ok(self.0.num_ready_bytes()?) } } diff --git a/crates/wasi-common/cap-std-sync/src/sched/unix.rs b/crates/wasi-common/cap-std-sync/src/sched/unix.rs index eeb9c9c0a7..37fbbc1c0f 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/unix.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/unix.rs @@ -79,7 +79,7 @@ impl WasiSched for SyncSched { if let Some(revents) = pollfd.revents() { let (nbytes, rwsub) = match rwsub { Subscription::Read(sub) => { - let ready = sub.file.num_ready_bytes()?; + let ready = sub.file.num_ready_bytes().await?; (std::cmp::max(ready, 1), sub) } Subscription::Write(sub) => (0, sub), diff --git a/crates/wasi-common/cap-std-sync/src/stdio.rs b/crates/wasi-common/cap-std-sync/src/stdio.rs index 92a76d47ff..d4d515cfeb 100644 --- a/crates/wasi-common/cap-std-sync/src/stdio.rs +++ b/crates/wasi-common/cap-std-sync/src/stdio.rs @@ -22,31 +22,32 @@ pub fn stdin() -> Stdin { Stdin(std::io::stdin()) } +#[wiggle::async_trait] impl WasiFile for Stdin { fn as_any(&self) -> &dyn Any { self } - fn datasync(&self) -> Result<(), Error> { + async fn datasync(&self) -> Result<(), Error> { Ok(()) } - fn sync(&self) -> Result<(), Error> { + async fn sync(&self) -> Result<(), Error> { Ok(()) } - fn get_filetype(&self) -> Result { + async fn get_filetype(&self) -> Result { Ok(FileType::Unknown) } - fn get_fdflags(&self) -> Result { + async fn get_fdflags(&self) -> Result { Ok(FdFlags::empty()) } - fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { + async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { Err(Error::badf()) } - fn get_filestat(&self) -> Result { + async fn get_filestat(&self) -> Result { let meta = self.0.as_file_view().metadata()?; Ok(Filestat { device_id: 0, inode: 0, - filetype: self.get_filetype()?, + filetype: self.get_filetype().await?, nlink: 0, size: meta.len(), atim: meta.accessed().ok(), @@ -54,35 +55,43 @@ impl WasiFile for Stdin { ctim: meta.created().ok(), }) } - fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { + async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { Err(Error::badf()) } - fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { + async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { Err(Error::badf()) } - fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> { + async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> { Err(Error::badf()) } - fn read_vectored(&self, bufs: &mut [io::IoSliceMut]) -> Result { + async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { let n = self.0.as_file_view().read_vectored(bufs)?; Ok(n.try_into().map_err(|_| Error::range())?) } - fn read_vectored_at(&self, _bufs: &mut [io::IoSliceMut], _offset: u64) -> Result { + async fn read_vectored_at<'a>( + &self, + _bufs: &mut [io::IoSliceMut<'a>], + _offset: u64, + ) -> Result { Err(Error::seek_pipe()) } - fn write_vectored(&self, _bufs: &[io::IoSlice]) -> Result { + async fn write_vectored<'a>(&self, _bufs: &[io::IoSlice<'a>]) -> Result { Err(Error::badf()) } - fn write_vectored_at(&self, _bufs: &[io::IoSlice], _offset: u64) -> Result { + async fn write_vectored_at<'a>( + &self, + _bufs: &[io::IoSlice<'a>], + _offset: u64, + ) -> Result { Err(Error::badf()) } - fn seek(&self, _pos: std::io::SeekFrom) -> Result { + async fn seek(&self, _pos: std::io::SeekFrom) -> Result { Err(Error::seek_pipe()) } - fn peek(&self, _buf: &mut [u8]) -> Result { + async fn peek(&self, _buf: &mut [u8]) -> Result { Err(Error::seek_pipe()) } - fn set_times( + async fn set_times( &self, atime: Option, mtime: Option, @@ -91,7 +100,7 @@ impl WasiFile for Stdin { .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; Ok(()) } - fn num_ready_bytes(&self) -> Result { + async fn num_ready_bytes(&self) -> Result { Ok(self.0.num_ready_bytes()?) } } @@ -110,31 +119,32 @@ impl AsRawFd for Stdin { macro_rules! wasi_file_write_impl { ($ty:ty) => { + #[wiggle::async_trait] impl WasiFile for $ty { fn as_any(&self) -> &dyn Any { self } - fn datasync(&self) -> Result<(), Error> { + async fn datasync(&self) -> Result<(), Error> { Ok(()) } - fn sync(&self) -> Result<(), Error> { + async fn sync(&self) -> Result<(), Error> { Ok(()) } - fn get_filetype(&self) -> Result { + async fn get_filetype(&self) -> Result { Ok(FileType::Unknown) } - fn get_fdflags(&self) -> Result { + async fn get_fdflags(&self) -> Result { Ok(FdFlags::APPEND) } - fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { + async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { Err(Error::badf()) } - fn get_filestat(&self) -> Result { + async fn get_filestat(&self) -> Result { let meta = self.0.as_file_view().metadata()?; Ok(Filestat { device_id: 0, inode: 0, - filetype: self.get_filetype()?, + filetype: self.get_filetype().await?, nlink: 0, size: meta.len(), atim: meta.accessed().ok(), @@ -142,39 +152,46 @@ macro_rules! wasi_file_write_impl { ctim: meta.created().ok(), }) } - fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { + async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { Err(Error::badf()) } - fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { + async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { Err(Error::badf()) } - fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> { + async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> { Err(Error::badf()) } - fn read_vectored(&self, _bufs: &mut [io::IoSliceMut]) -> Result { - Err(Error::badf()) - } - fn read_vectored_at( + async fn read_vectored<'a>( &self, - _bufs: &mut [io::IoSliceMut], + _bufs: &mut [io::IoSliceMut<'a>], + ) -> Result { + Err(Error::badf()) + } + async fn read_vectored_at<'a>( + &self, + _bufs: &mut [io::IoSliceMut<'a>], _offset: u64, ) -> Result { Err(Error::badf()) } - fn write_vectored(&self, bufs: &[io::IoSlice]) -> Result { + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { let n = self.0.as_file_view().write_vectored(bufs)?; Ok(n.try_into().map_err(|c| Error::range().context(c))?) } - fn write_vectored_at(&self, _bufs: &[io::IoSlice], _offset: u64) -> Result { + async fn write_vectored_at<'a>( + &self, + _bufs: &[io::IoSlice<'a>], + _offset: u64, + ) -> Result { Err(Error::seek_pipe()) } - fn seek(&self, _pos: std::io::SeekFrom) -> Result { + async fn seek(&self, _pos: std::io::SeekFrom) -> Result { Err(Error::seek_pipe()) } - fn peek(&self, _buf: &mut [u8]) -> Result { + async fn peek(&self, _buf: &mut [u8]) -> Result { Err(Error::badf()) } - fn set_times( + async fn set_times( &self, atime: Option, mtime: Option, @@ -183,7 +200,7 @@ macro_rules! wasi_file_write_impl { .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; Ok(()) } - fn num_ready_bytes(&self) -> Result { + async fn num_ready_bytes(&self) -> Result { Ok(0) } } From 2f0c7e59e7257cae7843c2aaed13f8d5900c08e5 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 16:04:41 -0700 Subject: [PATCH 19/72] wasmtime-wasi: all funcs are async now --- crates/wasi/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index 2eefcfccd2..f0ac46811c 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -99,7 +99,7 @@ necessary. Additionally [`Wasi::get_export`] can be used to do name-based resolution.", }, }, - $async_mode: { wasi_snapshot_preview1::{ poll_oneoff, sched_yield } } + $async_mode: * }); } pub mod preview_0 { @@ -127,7 +127,7 @@ necessary. Additionally [`Wasi::get_export`] can be used to do name-based resolution.", }, }, - $async_mode: { wasi_unstable::{ poll_oneoff, sched_yield } } + $async_mode: * }); } } From b883bda02290c6c6b4a98f9e6d9ba8c4f3dc0eb0 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 16:06:50 -0700 Subject: [PATCH 20/72] fix test-programs for sync wasi --- .../test-programs/tests/wasm_tests/runtime/cap_std_sync.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs index 1610880115..be3a35ac36 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs @@ -1,8 +1,8 @@ use anyhow::Context; use std::path::Path; -use wasi_cap_std_sync::WasiCtxBuilder; use wasi_common::pipe::WritePipe; use wasmtime::{Linker, Module, Store}; +use wasmtime_wasi::sync::{Wasi, WasiCtxBuilder}; pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> anyhow::Result<()> { let stdout = WritePipe::new_in_memory(); @@ -49,7 +49,7 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any // cap-std-sync does not yet support the sync family of fdflags builder = builder.env("NO_FDFLAGS_SYNC_SUPPORT", "1")?; - let wasi = wasmtime_wasi::Wasi::new(&store, builder.build()?); + let wasi = Wasi::new(&store, builder.build()?); let mut linker = Linker::new(&store); @@ -103,7 +103,7 @@ pub fn instantiate_inherit_stdio( builder = builder.preopened_dir(preopen_dir, ".")?; } - let snapshot1 = wasmtime_wasi::Wasi::new(&store, builder.build()?); + let snapshot1 = Wasi::new(&store, builder.build()?); let mut linker = Linker::new(&store); From 7202494e9d75efe9fc4775cc25c071b88ff95aa6 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 16:17:21 -0700 Subject: [PATCH 21/72] fix windows sched --- crates/wasi-common/cap-std-sync/src/sched/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasi-common/cap-std-sync/src/sched/windows.rs b/crates/wasi-common/cap-std-sync/src/sched/windows.rs index 752be626b0..8d11df4663 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/windows.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/windows.rs @@ -95,7 +95,7 @@ impl WasiSched for SyncSched { // XXX This doesnt strictly preserve the behavior in the earlier // implementation, which would always do complete(0) for reads from // stdout/err. - match r.file.num_ready_bytes() { + match r.file.num_ready_bytes().await { Ok(ready_bytes) => { r.complete(ready_bytes, RwEventFlags::empty()); ready = true; From 2ddf4c1da9af6b620a65b627c29771fe6d581140 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 14 Apr 2021 16:54:52 -0700 Subject: [PATCH 22/72] tokio example requires enabling a cargo feature and also a dummy C example --- crates/misc/run-examples/src/main.rs | 23 +++++++++++++++++------ examples/tokio/CARGO_FEATURES | 1 + examples/tokio/main.c | 5 +++++ 3 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 examples/tokio/CARGO_FEATURES create mode 100644 examples/tokio/main.c diff --git a/crates/misc/run-examples/src/main.rs b/crates/misc/run-examples/src/main.rs index 12746b2a38..0bff7d5c22 100644 --- a/crates/misc/run-examples/src/main.rs +++ b/crates/misc/run-examples/src/main.rs @@ -13,7 +13,11 @@ fn main() -> anyhow::Result<()> { continue; } - examples.insert((path.file_stem().unwrap().to_str().unwrap().to_owned(), dir)); + examples.insert(( + path.clone(), + path.file_stem().unwrap().to_str().unwrap().to_owned(), + dir, + )); } println!("======== Building libwasmtime.a ==========="); @@ -21,7 +25,7 @@ fn main() -> anyhow::Result<()> { .args(&["build"]) .current_dir("crates/c-api"))?; - for (example, is_dir) in examples { + for (example_path, example, is_dir) in examples { if example == "README" { continue; } @@ -45,10 +49,17 @@ fn main() -> anyhow::Result<()> { .arg(target))?; } println!("======== Rust example `{}` ============", example); - run(Command::new("cargo") - .arg("run") - .arg("--example") - .arg(&example))?; + let mut cargo_cmd = Command::new("cargo"); + cargo_cmd.arg("run").arg("--example").arg(&example); + if is_dir { + let mut features_path = std::path::PathBuf::from(example_path); + features_path.push("CARGO_FEATURES"); + if features_path.exists() { + let features = std::fs::read_to_string(features_path)?; + cargo_cmd.arg("--features").arg(features); + } + } + run(&mut cargo_cmd)?; println!("======== C/C++ example `{}` ============", example); for extension in ["c", "cc"].iter() { diff --git a/examples/tokio/CARGO_FEATURES b/examples/tokio/CARGO_FEATURES new file mode 100644 index 0000000000..c1c1ff5101 --- /dev/null +++ b/examples/tokio/CARGO_FEATURES @@ -0,0 +1 @@ +wasmtime-wasi/tokio diff --git a/examples/tokio/main.c b/examples/tokio/main.c new file mode 100644 index 0000000000..f6920d8bae --- /dev/null +++ b/examples/tokio/main.c @@ -0,0 +1,5 @@ +int main(int argc, char *argv[]) { + // This example is specific to integrating with Rust's tokio ecosystem, so + // it isnt applicable to C/C++. + return 0; +} From 33dbd4388c86e0ec6f0ecdc64733f753bfe350aa Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 15 Apr 2021 11:39:11 -0700 Subject: [PATCH 23/72] use tokio's File with a cap-std Dir --- Cargo.lock | 7 +++++++ crates/wasi-common/tokio/Cargo.toml | 12 +++++++++++- crates/wasi-common/tokio/src/lib.rs | 10 +++++----- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7e1aa02e4..3f5d294b2f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3138,7 +3138,14 @@ dependencies = [ name = "wasi-tokio" version = "0.26.0" dependencies = [ + "cap-fs-ext", + "cap-std", + "cap-time-ext", + "fs-set-times", + "system-interface", + "tempfile", "tokio", + "unsafe-io", "wasi-cap-std-sync", "wasi-common", "wiggle", diff --git a/crates/wasi-common/tokio/Cargo.toml b/crates/wasi-common/tokio/Cargo.toml index b1a194bdff..13c5b23281 100644 --- a/crates/wasi-common/tokio/Cargo.toml +++ b/crates/wasi-common/tokio/Cargo.toml @@ -15,4 +15,14 @@ include = ["src/**/*", "LICENSE" ] wasi-common = { path = "../", version = "0.26.0" } wasi-cap-std-sync = { path = "../cap-std-sync", version = "0.26.0" } wiggle = { path = "../../wiggle", version = "0.26.0" } -tokio = { version = "1.5.0", features = [ "rt", "time" ] } +tokio = { version = "1.5.0", features = [ "rt", "fs", "time" ] } +cap-std = "0.13.7" +cap-fs-ext = "0.13.7" +cap-time-ext = "0.13.7" +fs-set-times = "0.3.1" +unsafe-io = "0.6.2" +system-interface = { version = "0.6.3", features = ["cap_std_impls"] } + +[dev-dependencies] +tempfile = "3.1.0" +tokio = { features = [ "macros" ] } diff --git a/crates/wasi-common/tokio/src/lib.rs b/crates/wasi-common/tokio/src/lib.rs index f01d832c8f..d7d4687428 100644 --- a/crates/wasi-common/tokio/src/lib.rs +++ b/crates/wasi-common/tokio/src/lib.rs @@ -1,14 +1,14 @@ +mod dir; +mod file; + use std::cell::RefCell; use std::path::Path; use std::rc::Rc; pub use wasi_cap_std_sync::{clocks_ctx, random_ctx, Dir}; -use wasi_common::{Table, WasiCtx}; +use wasi_common::{Error, Table, WasiCtx}; pub fn sched_ctx() -> Box { - use wasi_common::{ - sched::{Duration, Poll, WasiSched}, - Error, - }; + use wasi_common::sched::{Duration, Poll, WasiSched}; struct AsyncSched; #[wiggle::async_trait] From 9880d09f1fbcbf4d82b81c9c915658bdcf7194b9 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 15 Apr 2021 14:50:17 -0700 Subject: [PATCH 24/72] do some programming --- crates/wasi-common/tokio/src/dir.rs | 353 +++++++++++++++++++++++++++ crates/wasi-common/tokio/src/file.rs | 278 +++++++++++++++++++++ crates/wasi-common/tokio/src/lib.rs | 17 ++ 3 files changed, 648 insertions(+) create mode 100644 crates/wasi-common/tokio/src/dir.rs create mode 100644 crates/wasi-common/tokio/src/file.rs diff --git a/crates/wasi-common/tokio/src/dir.rs b/crates/wasi-common/tokio/src/dir.rs new file mode 100644 index 0000000000..9188d0cca7 --- /dev/null +++ b/crates/wasi-common/tokio/src/dir.rs @@ -0,0 +1,353 @@ +use crate::{ + asyncify, + file::{filetype_from, File}, +}; +use cap_fs_ext::{DirEntryExt, DirExt, MetadataExt, SystemTimeSpec}; +use std::any::Any; +use std::path::{Path, PathBuf}; +use wasi_common::{ + dir::{ReaddirCursor, ReaddirEntity, WasiDir}, + file::{FdFlags, FileType, Filestat, OFlags, WasiFile}, + Error, ErrorExt, +}; + +pub struct Dir(cap_std::fs::Dir); + +impl Dir { + pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self { + Dir(dir) + } +} + +#[wiggle::async_trait] +impl WasiDir for Dir { + fn as_any(&self) -> &dyn Any { + self + } + async fn open_file( + &self, + symlink_follow: bool, + path: &str, + oflags: OFlags, + read: bool, + write: bool, + fdflags: FdFlags, + ) -> Result, Error> { + use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt}; + use wasi_common::file::FdFlags; + + let mut opts = cap_std::fs::OpenOptions::new(); + + if oflags.contains(OFlags::CREATE | OFlags::EXCLUSIVE) { + opts.create_new(true); + opts.write(true); + } else if oflags.contains(OFlags::CREATE) { + opts.create(true); + opts.write(true); + } + if oflags.contains(OFlags::TRUNCATE) { + opts.truncate(true); + } + if read { + opts.read(true); + } + if write { + opts.write(true); + } else { + // If not opened write, open read. This way the OS lets us open the file. + // If FileCaps::READ is not set, read calls will be rejected at the + // get_cap check. + opts.read(true); + } + if fdflags.contains(FdFlags::APPEND) { + opts.append(true); + } + + if symlink_follow { + opts.follow(FollowSymlinks::Yes); + } else { + opts.follow(FollowSymlinks::No); + } + // the DSYNC, SYNC, and RSYNC flags are ignored! We do not + // have support for them in cap-std yet. + // ideally OpenOptions would just support this though: + // https://github.com/bytecodealliance/cap-std/issues/146 + if fdflags.intersects(FdFlags::DSYNC | FdFlags::SYNC | FdFlags::RSYNC) { + return Err(Error::not_supported().context("SYNC family of FdFlags")); + } + + let f = asyncify(move || self.0.open_with(Path::new(path), &opts)).await?; + let mut f = File::from_cap_std(f); + // NONBLOCK does not have an OpenOption either, but we can patch that on with set_fd_flags: + if fdflags.contains(FdFlags::NONBLOCK) { + f.set_fdflags(FdFlags::NONBLOCK).await?; + } + Ok(Box::new(f)) + } + + async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { + let path = unsafe { std::mem::transmute::<_, &'static str>(path) }; + let d = if symlink_follow { + asyncify(move || self.0.open_dir(Path::new(path))).await? + } else { + asyncify(move || self.0.open_dir_nofollow(Path::new(path))).await? + }; + Ok(Box::new(Dir::from_cap_std(d))) + } + + async fn create_dir(&self, path: &str) -> Result<(), Error> { + asyncify(|| self.0.create_dir(Path::new(path))).await?; + Ok(()) + } + async fn readdir( + &self, + cursor: ReaddirCursor, + ) -> Result>>, Error> { + // cap_std's read_dir does not include . and .., we should prepend these. + // Why does the Ok contain a tuple? We can't construct a cap_std::fs::DirEntry, and we don't + // have enough info to make a ReaddirEntity yet. + let dir_meta = asyncify(|| self.0.dir_metadata()).await?; + let rd = vec![ + { + let name = ".".to_owned(); + Ok((FileType::Directory, dir_meta.ino(), name)) + }, + { + let name = "..".to_owned(); + Ok((FileType::Directory, dir_meta.ino(), name)) + }, + ] + .into_iter() + .chain( + // Now process the `DirEntry`s: + self.0.entries()?.map(|entry| { + let entry = entry?; + // XXX full_metadata blocks, but we arent in an async iterator: + let meta = entry.full_metadata()?; + let inode = meta.ino(); + let filetype = filetype_from(&meta.file_type()); + let name = entry + .file_name() + .into_string() + .map_err(|_| Error::illegal_byte_sequence().context("filename"))?; + Ok((filetype, inode, name)) + }), + ) + // Enumeration of the iterator makes it possible to define the ReaddirCursor + .enumerate() + .map(|(ix, r)| match r { + Ok((filetype, inode, name)) => Ok(ReaddirEntity { + next: ReaddirCursor::from(ix as u64 + 1), + filetype, + inode, + name, + }), + Err(e) => Err(e), + }) + .skip(u64::from(cursor) as usize); + + Ok(Box::new(rd)) + } + + async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { + asyncify(|| self.0.symlink(src_path, dest_path)).await?; + Ok(()) + } + async fn remove_dir(&self, path: &str) -> Result<(), Error> { + asyncify(|| self.0.remove_dir(Path::new(path))).await?; + Ok(()) + } + + async fn unlink_file(&self, path: &str) -> Result<(), Error> { + asyncify(|| self.0.remove_file_or_symlink(Path::new(path))).await?; + Ok(()) + } + async fn read_link(&self, path: &str) -> Result { + let link = asyncify(|| self.0.read_link(Path::new(path))).await?; + Ok(link) + } + async fn get_filestat(&self) -> Result { + let meta = asyncify(|| self.0.dir_metadata()).await?; + Ok(Filestat { + device_id: meta.dev(), + inode: meta.ino(), + filetype: filetype_from(&meta.file_type()), + nlink: meta.nlink(), + size: meta.len(), + atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), + mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), + ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), + }) + } + async fn get_path_filestat( + &self, + path: &str, + follow_symlinks: bool, + ) -> Result { + let meta = if follow_symlinks { + asyncify(|| self.0.metadata(Path::new(path))).await? + } else { + asyncify(|| self.0.symlink_metadata(Path::new(path))).await? + }; + Ok(Filestat { + device_id: meta.dev(), + inode: meta.ino(), + filetype: filetype_from(&meta.file_type()), + nlink: meta.nlink(), + size: meta.len(), + atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), + mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), + ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), + }) + } + async fn rename( + &self, + src_path: &str, + dest_dir: &dyn WasiDir, + dest_path: &str, + ) -> Result<(), Error> { + let dest_dir = dest_dir + .as_any() + .downcast_ref::() + .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; + asyncify(|| { + self.0 + .rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path)) + }) + .await?; + Ok(()) + } + async fn hard_link( + &self, + src_path: &str, + target_dir: &dyn WasiDir, + target_path: &str, + ) -> Result<(), Error> { + let target_dir = target_dir + .as_any() + .downcast_ref::() + .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; + let src_path = Path::new(src_path); + let target_path = Path::new(target_path); + asyncify(|| self.0.hard_link(src_path, &target_dir.0, target_path)).await?; + Ok(()) + } + async fn set_times( + &self, + path: &str, + atime: Option, + mtime: Option, + follow_symlinks: bool, + ) -> Result<(), Error> { + asyncify(|| { + if follow_symlinks { + self.0.set_times( + Path::new(path), + convert_systimespec(atime), + convert_systimespec(mtime), + ) + } else { + self.0.set_symlink_times( + Path::new(path), + convert_systimespec(atime), + convert_systimespec(mtime), + ) + } + }) + .await?; + Ok(()) + } +} + +fn convert_systimespec(t: Option) -> Option { + match t { + Some(wasi_common::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)), + Some(wasi_common::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), + None => None, + } +} + +#[cfg(test)] +mod test { + use super::Dir; + #[tokio::test] + async fn scratch_dir() { + let tempdir = tempfile::Builder::new() + .prefix("cap-std-sync") + .tempdir() + .expect("create temporary dir"); + let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(tempdir.path()) } + .expect("open ambient temporary dir"); + let preopen_dir = Dir::from_cap_std(preopen_dir); + wasi_common::WasiDir::open_dir(&preopen_dir, false, ".") + .await + .expect("open the same directory via WasiDir abstraction"); + } + + // Readdir does not work on windows, so we won't test it there. + #[cfg(not(windows))] + #[tokio::test] + async fn readdir() { + use std::collections::HashMap; + use wasi_common::dir::{ReaddirCursor, ReaddirEntity, WasiDir}; + use wasi_common::file::{FdFlags, FileType, OFlags}; + + async fn readdir_into_map(dir: &dyn WasiDir) -> HashMap { + let mut out = HashMap::new(); + for readdir_result in dir + .readdir(ReaddirCursor::from(0)) + .await + .expect("readdir succeeds") + { + let entity = readdir_result.expect("readdir entry is valid"); + out.insert(entity.name.clone(), entity); + } + out + } + + let tempdir = tempfile::Builder::new() + .prefix("cap-std-sync") + .tempdir() + .expect("create temporary dir"); + let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(tempdir.path()) } + .expect("open ambient temporary dir"); + let preopen_dir = Dir::from_cap_std(preopen_dir); + + let entities = readdir_into_map(&preopen_dir).await; + assert_eq!( + entities.len(), + 2, + "should just be . and .. in empty dir: {:?}", + entities + ); + assert!(entities.get(".").is_some()); + assert!(entities.get("..").is_some()); + + preopen_dir + .open_file( + false, + "file1", + OFlags::CREATE, + true, + false, + FdFlags::empty(), + ) + .await + .expect("create file1"); + + let entities = readdir_into_map(&preopen_dir).await; + assert_eq!(entities.len(), 3, "should be ., .., file1 {:?}", entities); + assert_eq!( + entities.get(".").expect(". entry").filetype, + FileType::Directory + ); + assert_eq!( + entities.get("..").expect(".. entry").filetype, + FileType::Directory + ); + assert_eq!( + entities.get("file1").expect("file1 entry").filetype, + FileType::RegularFile + ); + } +} diff --git a/crates/wasi-common/tokio/src/file.rs b/crates/wasi-common/tokio/src/file.rs new file mode 100644 index 0000000000..209e1eab15 --- /dev/null +++ b/crates/wasi-common/tokio/src/file.rs @@ -0,0 +1,278 @@ +use crate::asyncify; +use cap_fs_ext::MetadataExt; +use fs_set_times::{SetTimes, SystemTimeSpec}; +use std::any::Any; +use std::convert::TryInto; +use std::io; +use system_interface::fs::{FileIoExt, GetSetFdFlags}; +use system_interface::io::ReadReady; +use wasi_common::{ + file::{Advice, FdFlags, FileType, Filestat, WasiFile}, + Error, ErrorExt, +}; + +mod internal { + #[cfg(not(windows))] + use unsafe_io::os::posish::{AsRawFd, RawFd}; + #[cfg(windows)] + use unsafe_io::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; + use unsafe_io::OwnsRaw; + use wasi_common::Error; + + // This internal type wraps tokio's File so that we can impl the + // `AsUnsafeFile` trait. We impl this on an internal type, rather than on + // super::File, because we don't want consumers of this library to be able + // to use our `AsUnsafeFile`. + pub(super) struct Internal(pub(super) tokio::fs::File); + + #[cfg(not(windows))] + impl AsRawFd for Internal { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } + } + + #[cfg(windows)] + impl AsRawHandleOrSocket for Internal { + fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { + self.0.as_raw_handle_or_socket() + } + } + + // Safety: `Internal` owns its handle. + unsafe impl OwnsRaw for Internal {} + + // Tokio provides implementations of these methods, which are not + // available via AsUnsafeFile. + impl Internal { + pub async fn set_len(&self, size: u64) -> Result<(), Error> { + Ok(self.0.set_len(size).await?) + } + pub async fn sync_data(&self) -> Result<(), Error> { + Ok(self.0.sync_data().await?) + } + pub async fn sync_all(&self) -> Result<(), Error> { + Ok(self.0.sync_all().await?) + } + } +} + +pub struct File(internal::Internal); + +impl File { + pub fn from_cap_std(file: cap_std::fs::File) -> Self { + File(internal::Internal(tokio::fs::File::from_std( + file.into_std(), + ))) + } + + async fn metadata(&self) -> Result { + use unsafe_io::AsUnsafeFile; + asyncify(|| Ok(cap_std::fs::Metadata::from_file(&self.0.as_file_view())?)).await + } +} + +#[wiggle::async_trait] +impl WasiFile for File { + fn as_any(&self) -> &dyn Any { + self + } + async fn datasync(&self) -> Result<(), Error> { + self.0.sync_data().await + } + async fn sync(&self) -> Result<(), Error> { + self.0.sync_all().await + } + async fn get_filetype(&self) -> Result { + let meta = self.metadata().await?; + Ok(filetype_from(&meta.file_type())) + } + async fn get_fdflags(&self) -> Result { + let fdflags = asyncify(|| self.0.get_fd_flags()).await?; + Ok(from_sysif_fdflags(fdflags)) + } + async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { + if fdflags.intersects( + wasi_common::file::FdFlags::DSYNC + | wasi_common::file::FdFlags::SYNC + | wasi_common::file::FdFlags::RSYNC, + ) { + return Err(Error::invalid_argument().context("cannot set DSYNC, SYNC, or RSYNC flag")); + } + asyncify(move || self.0.set_fd_flags(to_sysif_fdflags(fdflags))).await?; + Ok(()) + } + async fn get_filestat(&self) -> Result { + let meta = self.metadata().await?; + Ok(Filestat { + device_id: meta.dev(), + inode: meta.ino(), + filetype: filetype_from(&meta.file_type()), + nlink: meta.nlink(), + size: meta.len(), + atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), + mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), + ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), + }) + } + async fn set_filestat_size(&self, size: u64) -> Result<(), Error> { + // Tokio asyncified this already: + self.0.set_len(size).await?; + Ok(()) + } + async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { + asyncify(move || self.0.advise(offset, len, convert_advice(advice))).await?; + Ok(()) + } + async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { + asyncify(move || self.0.allocate(offset, len)).await?; + Ok(()) + } + async fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + asyncify(|| { + self.0 + .set_times(convert_systimespec(atime), convert_systimespec(mtime)) + }) + .await?; + Ok(()) + } + async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { + // XXX use tokio's AsyncReadExt trait instead! + let n = self.0.read_vectored(bufs)?; + Ok(n.try_into()?) + } + async fn read_vectored_at<'a>( + &self, + bufs: &mut [io::IoSliceMut<'a>], + offset: u64, + ) -> Result { + let n = asyncify(move || self.0.read_vectored_at(bufs, offset)).await?; + Ok(n.try_into()?) + } + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { + // XXX use tokio's AsyncWriteExt trait instead! + let n = self.0.write_vectored(bufs)?; + Ok(n.try_into()?) + } + async fn write_vectored_at<'a>( + &self, + bufs: &[io::IoSlice<'a>], + offset: u64, + ) -> Result { + let n = asyncify(move || self.0.write_vectored_at(bufs, offset)).await?; + Ok(n.try_into()?) + } + async fn seek(&self, pos: std::io::SeekFrom) -> Result { + asyncify(move || self.0.seek(pos)).await + } + async fn peek(&self, buf: &mut [u8]) -> Result { + let n = asyncify(move || self.0.peek(buf)).await?; + Ok(n.try_into()?) + } + async fn num_ready_bytes(&self) -> Result { + use unsafe_io::AsUnsafeFile; + asyncify(|| self.0.as_file_view().num_ready_bytes()).await + } +} +pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType { + use cap_fs_ext::FileTypeExt; + if ft.is_dir() { + FileType::Directory + } else if ft.is_symlink() { + FileType::SymbolicLink + } else if ft.is_socket() { + if ft.is_block_device() { + FileType::SocketDgram + } else { + FileType::SocketStream + } + } else if ft.is_block_device() { + FileType::BlockDevice + } else if ft.is_char_device() { + FileType::CharacterDevice + } else if ft.is_file() { + FileType::RegularFile + } else { + FileType::Unknown + } +} + +#[cfg(windows)] +use std::os::windows::io::{AsRawHandle, RawHandle}; +#[cfg(windows)] +impl AsRawHandle for File { + fn as_raw_handle(&self) -> RawHandle { + self.0.as_raw_handle() + } +} + +#[cfg(unix)] +use std::os::unix::io::{AsRawFd, RawFd}; +#[cfg(unix)] +impl AsRawFd for File { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } +} +pub fn convert_systimespec(t: Option) -> Option { + match t { + Some(wasi_common::SystemTimeSpec::Absolute(t)) => { + Some(SystemTimeSpec::Absolute(t.into_std())) + } + Some(wasi_common::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), + None => None, + } +} + +pub fn to_sysif_fdflags(f: wasi_common::file::FdFlags) -> system_interface::fs::FdFlags { + let mut out = system_interface::fs::FdFlags::empty(); + if f.contains(wasi_common::file::FdFlags::APPEND) { + out |= system_interface::fs::FdFlags::APPEND; + } + if f.contains(wasi_common::file::FdFlags::DSYNC) { + out |= system_interface::fs::FdFlags::DSYNC; + } + if f.contains(wasi_common::file::FdFlags::NONBLOCK) { + out |= system_interface::fs::FdFlags::NONBLOCK; + } + if f.contains(wasi_common::file::FdFlags::RSYNC) { + out |= system_interface::fs::FdFlags::RSYNC; + } + if f.contains(wasi_common::file::FdFlags::SYNC) { + out |= system_interface::fs::FdFlags::SYNC; + } + out +} +pub fn from_sysif_fdflags(f: system_interface::fs::FdFlags) -> wasi_common::file::FdFlags { + let mut out = wasi_common::file::FdFlags::empty(); + if f.contains(system_interface::fs::FdFlags::APPEND) { + out |= wasi_common::file::FdFlags::APPEND; + } + if f.contains(system_interface::fs::FdFlags::DSYNC) { + out |= wasi_common::file::FdFlags::DSYNC; + } + if f.contains(system_interface::fs::FdFlags::NONBLOCK) { + out |= wasi_common::file::FdFlags::NONBLOCK; + } + if f.contains(system_interface::fs::FdFlags::RSYNC) { + out |= wasi_common::file::FdFlags::RSYNC; + } + if f.contains(system_interface::fs::FdFlags::SYNC) { + out |= wasi_common::file::FdFlags::SYNC; + } + out +} +pub fn convert_advice(advice: Advice) -> system_interface::fs::Advice { + match advice { + Advice::Normal => system_interface::fs::Advice::Normal, + Advice::Sequential => system_interface::fs::Advice::Sequential, + Advice::Random => system_interface::fs::Advice::Random, + Advice::WillNeed => system_interface::fs::Advice::WillNeed, + Advice::DontNeed => system_interface::fs::Advice::DontNeed, + Advice::NoReuse => system_interface::fs::Advice::NoReuse, + } +} diff --git a/crates/wasi-common/tokio/src/lib.rs b/crates/wasi-common/tokio/src/lib.rs index d7d4687428..be03c21216 100644 --- a/crates/wasi-common/tokio/src/lib.rs +++ b/crates/wasi-common/tokio/src/lib.rs @@ -109,3 +109,20 @@ impl WasiCtxBuilder { self.0.build() } } + +pub(crate) async fn asyncify<'a, F, T>(f: F) -> Result +where + F: FnOnce() -> Result + Send + 'a, + T: Send + 'static, +{ + // spawn_blocking requires a 'static function, but since we await on the + // JoinHandle the lifetime of the spawn will be no longer than this function's body + let f: Box Result + Send + 'a> = Box::new(f); + let f = unsafe { + std::mem::transmute::<_, Box Result + Send + 'static>>(f) + }; + match tokio::task::spawn_blocking(|| f()).await { + Ok(res) => Ok(res?), + Err(_) => panic!("spawn_blocking died"), + } +} From d2a98ced535b603c7c075f6da6e121a431db34b7 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 15 Apr 2021 16:00:11 -0700 Subject: [PATCH 25/72] use some better traits --- Cargo.lock | 8 +++ crates/wasi-common/tokio/Cargo.toml | 2 +- crates/wasi-common/tokio/src/file.rs | 79 ++++++++++++---------------- crates/wasi-common/tokio/src/lib.rs | 3 +- 4 files changed, 45 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f5d294b2f..3e6649aa34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,6 +285,12 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" +[[package]] +name = "bytes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" + [[package]] name = "cap-fs-ext" version = "0.13.7" @@ -2819,6 +2825,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" dependencies = [ "autocfg 1.0.1", + "bytes", + "memchr", "num_cpus", "pin-project-lite", "tokio-macros", diff --git a/crates/wasi-common/tokio/Cargo.toml b/crates/wasi-common/tokio/Cargo.toml index 13c5b23281..f480de0635 100644 --- a/crates/wasi-common/tokio/Cargo.toml +++ b/crates/wasi-common/tokio/Cargo.toml @@ -15,7 +15,7 @@ include = ["src/**/*", "LICENSE" ] wasi-common = { path = "../", version = "0.26.0" } wasi-cap-std-sync = { path = "../cap-std-sync", version = "0.26.0" } wiggle = { path = "../../wiggle", version = "0.26.0" } -tokio = { version = "1.5.0", features = [ "rt", "fs", "time" ] } +tokio = { version = "1.5.0", features = [ "rt", "fs", "time" , "io-util"] } cap-std = "0.13.7" cap-fs-ext = "0.13.7" cap-time-ext = "0.13.7" diff --git a/crates/wasi-common/tokio/src/file.rs b/crates/wasi-common/tokio/src/file.rs index 209e1eab15..fa63fe8b09 100644 --- a/crates/wasi-common/tokio/src/file.rs +++ b/crates/wasi-common/tokio/src/file.rs @@ -12,56 +12,52 @@ use wasi_common::{ }; mod internal { + use std::sync::{Mutex, MutexGuard}; #[cfg(not(windows))] use unsafe_io::os::posish::{AsRawFd, RawFd}; #[cfg(windows)] use unsafe_io::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; use unsafe_io::OwnsRaw; - use wasi_common::Error; // This internal type wraps tokio's File so that we can impl the // `AsUnsafeFile` trait. We impl this on an internal type, rather than on // super::File, because we don't want consumers of this library to be able // to use our `AsUnsafeFile`. - pub(super) struct Internal(pub(super) tokio::fs::File); + // Mutex is required because this type requires internal mutation for the + // tokio AsyncWriteExt methods to work, and must be Send. + pub(super) struct Internal(Mutex); + impl Internal { + pub fn new(f: tokio::fs::File) -> Self { + Internal(Mutex::new(f)) + } + pub fn inner(&self) -> MutexGuard { + self.0.lock().unwrap() + } + } #[cfg(not(windows))] impl AsRawFd for Internal { fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() + self.inner().as_raw_fd() } } #[cfg(windows)] impl AsRawHandleOrSocket for Internal { fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { - self.0.as_raw_handle_or_socket() + self.inner().as_raw_handle_or_socket() } } // Safety: `Internal` owns its handle. unsafe impl OwnsRaw for Internal {} - - // Tokio provides implementations of these methods, which are not - // available via AsUnsafeFile. - impl Internal { - pub async fn set_len(&self, size: u64) -> Result<(), Error> { - Ok(self.0.set_len(size).await?) - } - pub async fn sync_data(&self) -> Result<(), Error> { - Ok(self.0.sync_data().await?) - } - pub async fn sync_all(&self) -> Result<(), Error> { - Ok(self.0.sync_all().await?) - } - } } pub struct File(internal::Internal); impl File { pub fn from_cap_std(file: cap_std::fs::File) -> Self { - File(internal::Internal(tokio::fs::File::from_std( + File(internal::Internal::new(tokio::fs::File::from_std( file.into_std(), ))) } @@ -78,10 +74,12 @@ impl WasiFile for File { self } async fn datasync(&self) -> Result<(), Error> { - self.0.sync_data().await + self.0.inner().sync_data().await?; + Ok(()) } async fn sync(&self) -> Result<(), Error> { - self.0.sync_all().await + self.0.inner().sync_all().await?; + Ok(()) } async fn get_filetype(&self) -> Result { let meta = self.metadata().await?; @@ -116,8 +114,7 @@ impl WasiFile for File { }) } async fn set_filestat_size(&self, size: u64) -> Result<(), Error> { - // Tokio asyncified this already: - self.0.set_len(size).await?; + self.0.inner().set_len(size).await?; Ok(()) } async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { @@ -141,9 +138,17 @@ impl WasiFile for File { Ok(()) } async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { - // XXX use tokio's AsyncReadExt trait instead! - let n = self.0.read_vectored(bufs)?; - Ok(n.try_into()?) + use std::ops::DerefMut; + use tokio::io::AsyncReadExt; + let mut nbytes: usize = 0; + for b in bufs.iter_mut() { + let n = self.0.inner().read(b.deref_mut()).await?; + nbytes += n; + if n < b.len() { + break; + } + } + Ok(nbytes.try_into()?) } async fn read_vectored_at<'a>( &self, @@ -154,8 +159,9 @@ impl WasiFile for File { Ok(n.try_into()?) } async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { - // XXX use tokio's AsyncWriteExt trait instead! - let n = self.0.write_vectored(bufs)?; + use tokio::io::AsyncWriteExt; + let mut n: usize = 0; + n += self.0.inner().write_vectored(bufs).await?; Ok(n.try_into()?) } async fn write_vectored_at<'a>( @@ -201,23 +207,6 @@ pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType { } } -#[cfg(windows)] -use std::os::windows::io::{AsRawHandle, RawHandle}; -#[cfg(windows)] -impl AsRawHandle for File { - fn as_raw_handle(&self) -> RawHandle { - self.0.as_raw_handle() - } -} - -#[cfg(unix)] -use std::os::unix::io::{AsRawFd, RawFd}; -#[cfg(unix)] -impl AsRawFd for File { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } -} pub fn convert_systimespec(t: Option) -> Option { match t { Some(wasi_common::SystemTimeSpec::Absolute(t)) => { diff --git a/crates/wasi-common/tokio/src/lib.rs b/crates/wasi-common/tokio/src/lib.rs index be03c21216..7ae8849259 100644 --- a/crates/wasi-common/tokio/src/lib.rs +++ b/crates/wasi-common/tokio/src/lib.rs @@ -85,6 +85,7 @@ impl WasiCtxBuilder { pub fn stderr(self, f: Box) -> Self { WasiCtxBuilder(self.0.stderr(f)) } + // XXX our crate needs its own stdios pub fn inherit_stdin(self) -> Self { self.stdin(Box::new(wasi_cap_std_sync::stdio::stdin())) } @@ -102,7 +103,7 @@ impl WasiCtxBuilder { dir: Dir, guest_path: impl AsRef, ) -> Result { - let dir = Box::new(wasi_cap_std_sync::dir::Dir::from_cap_std(dir)); + let dir = Box::new(crate::dir::Dir::from_cap_std(dir)); Ok(WasiCtxBuilder(self.0.preopened_dir(dir, guest_path)?)) } pub fn build(self) -> Result { From 675b57936118f2ba03b5fe63074f98923de3d4b0 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 19 Apr 2021 14:35:46 -0700 Subject: [PATCH 26/72] use AsyncSeek --- crates/wasi-common/tokio/src/file.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/wasi-common/tokio/src/file.rs b/crates/wasi-common/tokio/src/file.rs index fa63fe8b09..503e2168c4 100644 --- a/crates/wasi-common/tokio/src/file.rs +++ b/crates/wasi-common/tokio/src/file.rs @@ -173,7 +173,8 @@ impl WasiFile for File { Ok(n.try_into()?) } async fn seek(&self, pos: std::io::SeekFrom) -> Result { - asyncify(move || self.0.seek(pos)).await + use tokio::io::AsyncSeekExt; + Ok(self.0.inner().seek(pos).await?) } async fn peek(&self, buf: &mut [u8]) -> Result { let n = asyncify(move || self.0.peek(buf)).await?; From 8667d8c244c536318af6256dfc72ec97af5980fd Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 19 Apr 2021 16:00:27 -0700 Subject: [PATCH 27/72] test-programs: test wasi-tokio --- crates/test-programs/Cargo.toml | 3 +- crates/test-programs/build.rs | 11 +- .../tests/wasm_tests/runtime/mod.rs | 1 + .../tests/wasm_tests/runtime/tokio.rs | 137 ++++++++++++++++++ 4 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 crates/test-programs/tests/wasm_tests/runtime/tokio.rs diff --git a/crates/test-programs/Cargo.toml b/crates/test-programs/Cargo.toml index 4cd5bcbc12..7c4d9c3ad4 100644 --- a/crates/test-programs/Cargo.toml +++ b/crates/test-programs/Cargo.toml @@ -14,7 +14,7 @@ cfg-if = "1.0" wasi-common = { path = "../wasi-common", version = "0.26.0" } wasi-cap-std-sync = { path = "../wasi-common/cap-std-sync", version = "0.26.0" } wasmtime = { path = "../wasmtime", version = "0.26.0" } -wasmtime-wasi = { path = "../wasi", version = "0.26.0" } +wasmtime-wasi = { path = "../wasi", version = "0.26.0", features = ["tokio"] } target-lexicon = "0.12.0" pretty_env_logger = "0.4.0" tempfile = "3.1.0" @@ -22,6 +22,7 @@ os_pipe = "0.9" anyhow = "1.0.19" wat = "1.0.37" cap-std = "0.13" +tokio = { version = "1.5.0", features = ["rt-multi-thread"] } [features] test_programs = [] diff --git a/crates/test-programs/build.rs b/crates/test-programs/build.rs index abef0738f0..6894f66a81 100644 --- a/crates/test-programs/build.rs +++ b/crates/test-programs/build.rs @@ -40,7 +40,9 @@ mod wasi_tests { File::create(out_dir.join("wasi_tests.rs")).expect("error generating test source file"); build_tests("wasi-tests", &out_dir).expect("building tests"); test_directory(&mut out, "wasi-cap-std-sync", "cap_std_sync", &out_dir) - .expect("generating tests"); + .expect("generating wasi-cap-std-sync tests"); + test_directory(&mut out, "wasi-tokio", "tokio", &out_dir) + .expect("generating wasi-tokio tests"); } fn build_tests(testsuite: &str, out_dir: &Path) -> io::Result<()> { @@ -173,6 +175,7 @@ mod wasi_tests { match testsuite { "wasi-cap-std-sync" => cap_std_sync_ignore(name), "wasi-virtfs" => virtfs_ignore(name), + "wasi-tokio" => tokio_ignore(name), _ => panic!("unknown test suite: {}", testsuite), } } @@ -200,6 +203,10 @@ mod wasi_tests { .contains(&name) } + /// Tokio should support the same things as cap_std_sync + fn tokio_ignore(name: &str) -> bool { + cap_std_sync_ignore(name) + } /// Virtfs barely works at all and is not suitable for any purpose fn virtfs_ignore(name: &str) -> bool { [ @@ -260,7 +267,7 @@ mod wasi_tests { /// Mark tests which require inheriting parent process stdio fn inherit_stdio(testsuite: &str, name: &str) -> bool { match testsuite { - "wasi-cap-std-sync" => match name { + "wasi-cap-std-sync" | "wasi-tokio" => match name { "poll_oneoff_stdio" => true, _ => false, }, diff --git a/crates/test-programs/tests/wasm_tests/runtime/mod.rs b/crates/test-programs/tests/wasm_tests/runtime/mod.rs index 035b1e83ea..caabcf7b30 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/mod.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/mod.rs @@ -1 +1,2 @@ pub mod cap_std_sync; +pub mod tokio; diff --git a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs new file mode 100644 index 0000000000..11cf28336c --- /dev/null +++ b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs @@ -0,0 +1,137 @@ +use anyhow::Context; +use std::path::Path; +use wasi_common::pipe::WritePipe; +use wasmtime::{Config, Engine, Linker, Module, Store}; +use wasmtime_wasi::tokio::{Wasi, WasiCtxBuilder}; + +pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> anyhow::Result<()> { + let stdout = WritePipe::new_in_memory(); + let stdout_ = stdout.clone(); + let stderr = WritePipe::new_in_memory(); + let stderr_ = stderr.clone(); + + let r = tokio::runtime::Runtime::new() + .expect("create runtime") + .block_on(async move { + let mut config = Config::new(); + config.async_support(true); + config.consume_fuel(true); + Wasi::add_to_config(&mut config); + let engine = Engine::new(&config)?; + let store = Store::new(&engine); + + // Create our wasi context. + // Additionally register any preopened directories if we have them. + let mut builder = WasiCtxBuilder::new(); + + builder = builder + .arg(bin_name)? + .arg(".")? + .stdout(Box::new(stdout_)) + .stderr(Box::new(stderr_)); + + if let Some(workspace) = workspace { + println!("preopen: {:?}", workspace); + let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?; + builder = builder.preopened_dir(preopen_dir, ".")?; + } + + #[cfg(windows)] + { + builder = builder + .env("ERRNO_MODE_WINDOWS", "1")? + .env("NO_DANGLING_FILESYSTEM", "1")? + .env("NO_FD_ALLOCATE", "1")? + .env("NO_RENAME_DIR_TO_EMPTY_DIR", "1")? + } + #[cfg(all(unix, not(target_os = "macos")))] + { + builder = builder.env("ERRNO_MODE_UNIX", "1")?; + } + #[cfg(target_os = "macos")] + { + builder = builder + .env("ERRNO_MODE_MACOS", "1")? + .env("NO_FD_ALLOCATE", "1")?; + } + + // cap-std-sync does not yet support the sync family of fdflags + builder = builder.env("NO_FDFLAGS_SYNC_SUPPORT", "1")?; + + store.out_of_fuel_async_yield(u32::MAX, 10000); + Wasi::set_context(&store, builder.build()?) + .map_err(|_| anyhow::anyhow!("wasi set_context failed"))?; + + let module = + Module::new(store.engine(), &data).context("failed to create wasm module")?; + let linker = Linker::new(&store); + let instance = linker.instantiate_async(&module).await?; + let start = instance.get_typed_func::<(), ()>("_start")?; + start.call_async(()).await.map_err(anyhow::Error::from) + }); + + match r { + Ok(()) => Ok(()), + Err(trap) => { + let stdout = stdout + .try_into_inner() + .expect("sole ref to stdout") + .into_inner(); + if !stdout.is_empty() { + println!("guest stdout:\n{}\n===", String::from_utf8_lossy(&stdout)); + } + let stderr = stderr + .try_into_inner() + .expect("sole ref to stderr") + .into_inner(); + if !stderr.is_empty() { + println!("guest stderr:\n{}\n===", String::from_utf8_lossy(&stderr)); + } + Err(trap.context(format!("error while testing Wasm module '{}'", bin_name,))) + } + } +} + +pub fn instantiate_inherit_stdio( + data: &[u8], + bin_name: &str, + workspace: Option<&Path>, +) -> anyhow::Result<()> { + let r = tokio::runtime::Runtime::new() + .expect("create runtime") + .block_on(async { + let mut config = Config::new(); + config.async_support(true); + config.consume_fuel(true); + Wasi::add_to_config(&mut config); + let engine = Engine::new(&config)?; + let store = Store::new(&engine); + + // Create our wasi context. + // Additionally register any preopened directories if we have them. + let mut builder = WasiCtxBuilder::new(); + + builder = builder.arg(bin_name)?.arg(".")?.inherit_stdio(); + + if let Some(workspace) = workspace { + println!("preopen: {:?}", workspace); + let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?; + builder = builder.preopened_dir(preopen_dir, ".")?; + } + + Wasi::set_context(&store, builder.build()?) + .map_err(|_| anyhow::anyhow!("wasi set_context failed"))?; + + let module = + Module::new(store.engine(), &data).context("failed to create wasm module")?; + let linker = Linker::new(&store); + let instance = linker.instantiate_async(&module).await?; + let start = instance.get_typed_func::<(), ()>("_start")?; + start.call_async(()).await.map_err(anyhow::Error::from) + }); + + match r { + Ok(()) => Ok(()), + Err(trap) => Err(trap.context(format!("error while testing Wasm module '{}'", bin_name,))), + } +} From 3d3a2acc1b19d1a2977fd6ea669675f868739d1c Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 19 Apr 2021 16:00:38 -0700 Subject: [PATCH 28/72] wasi-tokio: WIP need to port the sync scheduler into a spawn_blocking --- crates/wasi-common/tokio/Cargo.toml | 9 + crates/wasi-common/tokio/src/lib.rs | 23 +- crates/wasi-common/tokio/src/sched.rs | 35 +++ crates/wasi-common/tokio/src/sched/unix.rs | 176 +++++++++++++ crates/wasi-common/tokio/src/sched/windows.rs | 242 ++++++++++++++++++ 5 files changed, 464 insertions(+), 21 deletions(-) create mode 100644 crates/wasi-common/tokio/src/sched.rs create mode 100644 crates/wasi-common/tokio/src/sched/unix.rs create mode 100644 crates/wasi-common/tokio/src/sched/windows.rs diff --git a/crates/wasi-common/tokio/Cargo.toml b/crates/wasi-common/tokio/Cargo.toml index f480de0635..d67fc72914 100644 --- a/crates/wasi-common/tokio/Cargo.toml +++ b/crates/wasi-common/tokio/Cargo.toml @@ -22,6 +22,15 @@ cap-time-ext = "0.13.7" fs-set-times = "0.3.1" unsafe-io = "0.6.2" system-interface = { version = "0.6.3", features = ["cap_std_impls"] } +tracing = "0.1.19" +bitflags = "1.2" + +[target.'cfg(unix)'.dependencies] +libc = "0.2" + +[target.'cfg(windows)'.dependencies] +winapi = "0.3" +lazy_static = "1.4" [dev-dependencies] tempfile = "3.1.0" diff --git a/crates/wasi-common/tokio/src/lib.rs b/crates/wasi-common/tokio/src/lib.rs index 7ae8849259..a5ff5f8ca7 100644 --- a/crates/wasi-common/tokio/src/lib.rs +++ b/crates/wasi-common/tokio/src/lib.rs @@ -1,5 +1,6 @@ mod dir; mod file; +mod sched; use std::cell::RefCell; use std::path::Path; @@ -7,27 +8,7 @@ use std::rc::Rc; pub use wasi_cap_std_sync::{clocks_ctx, random_ctx, Dir}; use wasi_common::{Error, Table, WasiCtx}; -pub fn sched_ctx() -> Box { - use wasi_common::sched::{Duration, Poll, WasiSched}; - struct AsyncSched; - - #[wiggle::async_trait] - impl WasiSched for AsyncSched { - async fn poll_oneoff<'a>(&self, _poll: &'_ Poll<'a>) -> Result<(), Error> { - todo!() - } - async fn sched_yield(&self) -> Result<(), Error> { - tokio::task::yield_now().await; - Ok(()) - } - async fn sleep(&self, duration: Duration) -> Result<(), Error> { - tokio::time::sleep(duration).await; - Ok(()) - } - } - - Box::new(AsyncSched) -} +use crate::sched::sched_ctx; pub struct WasiCtxBuilder(wasi_common::WasiCtxBuilder); diff --git a/crates/wasi-common/tokio/src/sched.rs b/crates/wasi-common/tokio/src/sched.rs new file mode 100644 index 0000000000..fd41f32996 --- /dev/null +++ b/crates/wasi-common/tokio/src/sched.rs @@ -0,0 +1,35 @@ +#[cfg(unix)] +mod unix; +#[cfg(unix)] +use unix::poll_oneoff; + +#[cfg(windows)] +mod windows; +#[cfg(windows)] +use windows::poll_oneoff; + +use wasi_common::{ + sched::{Duration, Poll, WasiSched}, + Error, +}; + +pub fn sched_ctx() -> Box { + struct AsyncSched; + + #[wiggle::async_trait] + impl WasiSched for AsyncSched { + async fn poll_oneoff<'a>(&self, poll: &'_ Poll<'a>) -> Result<(), Error> { + poll_oneoff(poll).await + } + async fn sched_yield(&self) -> Result<(), Error> { + tokio::task::yield_now().await; + Ok(()) + } + async fn sleep(&self, duration: Duration) -> Result<(), Error> { + tokio::time::sleep(duration).await; + Ok(()) + } + } + + Box::new(AsyncSched) +} diff --git a/crates/wasi-common/tokio/src/sched/unix.rs b/crates/wasi-common/tokio/src/sched/unix.rs new file mode 100644 index 0000000000..83ddb2c839 --- /dev/null +++ b/crates/wasi-common/tokio/src/sched/unix.rs @@ -0,0 +1,176 @@ +use cap_std::time::Duration; +use std::convert::TryInto; +use std::ops::Deref; +use std::os::unix::io::{AsRawFd, RawFd}; +use wasi_common::{ + file::WasiFile, + sched::{ + subscription::{RwEventFlags, Subscription}, + Poll, + }, + Error, ErrorExt, +}; + +use poll::{PollFd, PollFlags}; + +pub async fn poll_oneoff<'a>(poll: &'_ Poll<'a>) -> Result<(), Error> { + if poll.is_empty() { + return Ok(()); + } + let mut pollfds = Vec::new(); + let timeout = poll.earliest_clock_deadline(); + for s in poll.rw_subscriptions() { + match s { + Subscription::Read(f) => { + let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or( + Error::invalid_argument().context("read subscription fd downcast failed"), + )?; + pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLIN) }); + } + + Subscription::Write(f) => { + let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or( + Error::invalid_argument().context("write subscription fd downcast failed"), + )?; + pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLOUT) }); + } + Subscription::MonotonicClock { .. } => unreachable!(), + } + } + + let ready = loop { + let poll_timeout = if let Some(t) = timeout { + let duration = t.duration_until().unwrap_or(Duration::from_secs(0)); + (duration.as_millis() + 1) // XXX try always rounding up? + .try_into() + .map_err(|_| Error::overflow().context("poll timeout"))? + } else { + libc::c_int::max_value() + }; + tracing::debug!( + poll_timeout = tracing::field::debug(poll_timeout), + poll_fds = tracing::field::debug(&pollfds), + "poll" + ); + match poll::poll(&mut pollfds, poll_timeout) { + Ok(ready) => break ready, + Err(_) => { + let last_err = std::io::Error::last_os_error(); + if last_err.raw_os_error().unwrap() == libc::EINTR { + continue; + } else { + return Err(last_err.into()); + } + } + } + }; + if ready > 0 { + for (rwsub, pollfd) in poll.rw_subscriptions().zip(pollfds.into_iter()) { + if let Some(revents) = pollfd.revents() { + let (nbytes, rwsub) = match rwsub { + Subscription::Read(sub) => { + let ready = sub.file.num_ready_bytes().await?; + (std::cmp::max(ready, 1), sub) + } + Subscription::Write(sub) => (0, sub), + _ => unreachable!(), + }; + if revents.contains(PollFlags::POLLNVAL) { + rwsub.error(Error::badf()); + } else if revents.contains(PollFlags::POLLERR) { + rwsub.error(Error::io()); + } else if revents.contains(PollFlags::POLLHUP) { + rwsub.complete(nbytes, RwEventFlags::HANGUP); + } else { + rwsub.complete(nbytes, RwEventFlags::empty()); + }; + } + } + } else { + timeout + .expect("timed out") + .result() + .expect("timer deadline is past") + .unwrap() + } + Ok(()) +} + +fn wasi_file_raw_fd(f: &dyn WasiFile) -> Option { + let a = f.as_any(); + if a.is::() { + Some(a.downcast_ref::().unwrap().as_raw_fd()) + } else if a.is::() { + Some(a.downcast_ref::().unwrap().as_raw_fd()) + } else if a.is::() { + Some( + a.downcast_ref::() + .unwrap() + .as_raw_fd(), + ) + } else if a.is::() { + Some( + a.downcast_ref::() + .unwrap() + .as_raw_fd(), + ) + } else { + None + } +} + +mod poll { + use bitflags::bitflags; + use std::convert::TryInto; + use std::os::unix::io::RawFd; + + bitflags! { + pub struct PollFlags: libc::c_short { + const POLLIN = libc::POLLIN; + const POLLPRI = libc::POLLPRI; + const POLLOUT = libc::POLLOUT; + const POLLRDNORM = libc::POLLRDNORM; + const POLLWRNORM = libc::POLLWRNORM; + const POLLRDBAND = libc::POLLRDBAND; + const POLLWRBAND = libc::POLLWRBAND; + const POLLERR = libc::POLLERR; + const POLLHUP = libc::POLLHUP; + const POLLNVAL = libc::POLLNVAL; + } + } + + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[repr(C)] + pub struct PollFd(libc::pollfd); + + impl PollFd { + pub unsafe fn new(fd: RawFd, events: PollFlags) -> Self { + Self(libc::pollfd { + fd, + events: events.bits(), + revents: PollFlags::empty().bits(), + }) + } + + pub fn revents(self) -> Option { + PollFlags::from_bits(self.0.revents) + } + } + + pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result { + let nready = unsafe { + libc::poll( + fds.as_mut_ptr() as *mut libc::pollfd, + fds.len() as libc::nfds_t, + timeout, + ) + }; + if nready == -1 { + Err(std::io::Error::last_os_error()) + } else { + // When poll doesn't fail, its return value is a non-negative int, which will + // always be convertable to usize, so we can unwrap() here. + Ok(nready.try_into().unwrap()) + } + } +} diff --git a/crates/wasi-common/tokio/src/sched/windows.rs b/crates/wasi-common/tokio/src/sched/windows.rs new file mode 100644 index 0000000000..a3c15f3c2f --- /dev/null +++ b/crates/wasi-common/tokio/src/sched/windows.rs @@ -0,0 +1,242 @@ +use anyhow::Context; +use std::ops::Deref; +use std::os::windows::io::{AsRawHandle, RawHandle}; +use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError}; +use std::sync::Mutex; +use std::thread; +use std::time::Duration; +use wasi_common::{ + file::WasiFile, + sched::{ + subscription::{RwEventFlags, Subscription}, + Poll, + }, + Error, ErrorExt, +}; +pub async fn poll_oneoff<'a>(poll: &'_ Poll<'a>) -> Result<(), Error> { + if poll.is_empty() { + return Ok(()); + } + + let mut ready = false; + let timeout = poll.earliest_clock_deadline(); + + let mut stdin_read_subs = Vec::new(); + let mut immediate_subs = Vec::new(); + for s in poll.rw_subscriptions() { + match s { + Subscription::Read(r) if r.file.as_any().is::() => { + stdin_read_subs.push(r); + } + Subscription::Read(rw) | Subscription::Write(rw) => { + if wasi_file_raw_handle(rw.file.deref()).is_some() { + immediate_subs.push(s); + } else { + return Err(Error::invalid_argument() + .context("read/write subscription fd downcast failed")); + } + } + Subscription::MonotonicClock { .. } => unreachable!(), + } + } + + if !stdin_read_subs.is_empty() { + let waitmode = if let Some(t) = timeout { + if let Some(duration) = t.duration_until() { + WaitMode::Timeout(duration) + } else { + WaitMode::Immediate + } + } else { + if ready { + WaitMode::Immediate + } else { + WaitMode::Infinite + } + }; + let state = STDIN_POLL + .lock() + .map_err(|_| Error::trap("failed to take lock of STDIN_POLL"))? + .poll(waitmode)?; + for readsub in stdin_read_subs.into_iter() { + match state { + PollState::Ready => { + readsub.complete(1, RwEventFlags::empty()); + ready = true; + } + PollState::NotReady | PollState::TimedOut => {} + PollState::Error(ref e) => { + // Unfortunately, we need to deliver the Error to each of the + // subscriptions, but there is no Clone on std::io::Error. So, we convert it to the + // kind, and then back to std::io::Error, and finally to anyhow::Error. + // When its time to turn this into an errno elsewhere, the error kind will + // be inspected. + let ekind = e.kind(); + let ioerror = std::io::Error::from(ekind); + readsub.error(ioerror.into()); + ready = true; + } + } + } + } + for sub in immediate_subs { + match sub { + Subscription::Read(r) => { + // XXX This doesnt strictly preserve the behavior in the earlier + // implementation, which would always do complete(0) for reads from + // stdout/err. + match r.file.num_ready_bytes().await { + Ok(ready_bytes) => { + r.complete(ready_bytes, RwEventFlags::empty()); + ready = true; + } + Err(e) => { + r.error(e); + ready = true; + } + } + } + Subscription::Write(w) => { + // Everything is always ready for writing, apparently? + w.complete(0, RwEventFlags::empty()); + ready = true; + } + Subscription::MonotonicClock { .. } => unreachable!(), + } + } + + if !ready { + if let Some(t) = timeout { + if let Some(duration) = t.duration_until() { + thread::sleep(duration); + } + } + } + + Ok(()) +} + +fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option { + let a = f.as_any(); + if a.is::() { + Some( + a.downcast_ref::() + .unwrap() + .as_raw_handle(), + ) + } else if a.is::() { + Some( + a.downcast_ref::() + .unwrap() + .as_raw_handle(), + ) + } else if a.is::() { + Some( + a.downcast_ref::() + .unwrap() + .as_raw_handle(), + ) + } else if a.is::() { + Some( + a.downcast_ref::() + .unwrap() + .as_raw_handle(), + ) + } else { + None + } +} + +enum PollState { + Ready, + NotReady, // Not ready, but did not wait + TimedOut, // Not ready, waited until timeout + Error(std::io::Error), +} + +enum WaitMode { + Timeout(Duration), + Infinite, + Immediate, +} + +struct StdinPoll { + request_tx: Sender<()>, + notify_rx: Receiver, +} + +lazy_static::lazy_static! { + static ref STDIN_POLL: Mutex = StdinPoll::new(); +} + +impl StdinPoll { + pub fn new() -> Mutex { + let (request_tx, request_rx) = mpsc::channel(); + let (notify_tx, notify_rx) = mpsc::channel(); + thread::spawn(move || Self::event_loop(request_rx, notify_tx)); + Mutex::new(StdinPoll { + request_tx, + notify_rx, + }) + } + + // This function should not be used directly. + // Correctness of this function crucially depends on the fact that + // mpsc::Receiver is !Sync. + fn poll(&self, wait_mode: WaitMode) -> Result { + match self.notify_rx.try_recv() { + // Clean up possibly unread result from previous poll. + Ok(_) | Err(TryRecvError::Empty) => {} + Err(TryRecvError::Disconnected) => { + return Err(Error::trap("StdinPoll notify_rx channel closed")) + } + } + + // Notify the worker thread to poll stdin + self.request_tx + .send(()) + .context("request_tx channel closed")?; + + // Wait for the worker thread to send a readiness notification + match wait_mode { + WaitMode::Timeout(timeout) => match self.notify_rx.recv_timeout(timeout) { + Ok(r) => Ok(r), + Err(RecvTimeoutError::Timeout) => Ok(PollState::TimedOut), + Err(RecvTimeoutError::Disconnected) => { + Err(Error::trap("StdinPoll notify_rx channel closed")) + } + }, + WaitMode::Infinite => self + .notify_rx + .recv() + .context("StdinPoll notify_rx channel closed"), + WaitMode::Immediate => match self.notify_rx.try_recv() { + Ok(r) => Ok(r), + Err(TryRecvError::Empty) => Ok(PollState::NotReady), + Err(TryRecvError::Disconnected) => { + Err(Error::trap("StdinPoll notify_rx channel closed")) + } + }, + } + } + + fn event_loop(request_rx: Receiver<()>, notify_tx: Sender) -> ! { + use std::io::BufRead; + loop { + // Wait on a request: + request_rx.recv().expect("request_rx channel"); + // Wait for data to appear in stdin. If fill_buf returns any slice, it means + // that either: + // (a) there is some data in stdin, if non-empty, + // (b) EOF was recieved, if its empty + // Linux returns `POLLIN` in both cases, so we imitate this behavior. + let resp = match std::io::stdin().lock().fill_buf() { + Ok(_) => PollState::Ready, + Err(e) => PollState::Error(e), + }; + // Notify about data in stdin. If the read on this channel has timed out, the + // next poller will have to clean the channel. + notify_tx.send(resp).expect("notify_tx channel"); + } + } +} From 27464c85f4d3bb6e7f3c45a04d06a8a095823fc4 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 19 Apr 2021 16:00:56 -0700 Subject: [PATCH 29/72] cargo.lock --- Cargo.lock | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 3e6649aa34..e7d6bac18d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2763,6 +2763,7 @@ dependencies = [ "pretty_env_logger", "target-lexicon", "tempfile", + "tokio", "wasi-cap-std-sync", "wasi-common", "wasmtime", @@ -3146,17 +3147,22 @@ dependencies = [ name = "wasi-tokio" version = "0.26.0" dependencies = [ + "bitflags", "cap-fs-ext", "cap-std", "cap-time-ext", "fs-set-times", + "lazy_static", + "libc", "system-interface", "tempfile", "tokio", + "tracing", "unsafe-io", "wasi-cap-std-sync", "wasi-common", "wiggle", + "winapi", ] [[package]] From fa44ec2da2e296f61c9a7a4c9f49882d3fa42b98 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 26 Apr 2021 14:42:22 -0700 Subject: [PATCH 30/72] tokio sched: comment out just to make it compile --- crates/wasi-common/tokio/src/sched/unix.rs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/crates/wasi-common/tokio/src/sched/unix.rs b/crates/wasi-common/tokio/src/sched/unix.rs index 83ddb2c839..07a1cf0a9f 100644 --- a/crates/wasi-common/tokio/src/sched/unix.rs +++ b/crates/wasi-common/tokio/src/sched/unix.rs @@ -97,26 +97,7 @@ pub async fn poll_oneoff<'a>(poll: &'_ Poll<'a>) -> Result<(), Error> { } fn wasi_file_raw_fd(f: &dyn WasiFile) -> Option { - let a = f.as_any(); - if a.is::() { - Some(a.downcast_ref::().unwrap().as_raw_fd()) - } else if a.is::() { - Some(a.downcast_ref::().unwrap().as_raw_fd()) - } else if a.is::() { - Some( - a.downcast_ref::() - .unwrap() - .as_raw_fd(), - ) - } else if a.is::() { - Some( - a.downcast_ref::() - .unwrap() - .as_raw_fd(), - ) - } else { - None - } + todo!() } mod poll { From b307dce2abcf24f141d3a63cc48a65dc4b655224 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 26 Apr 2021 14:43:16 -0700 Subject: [PATCH 31/72] add readable and writable futures to WasiFile trait --- crates/wasi-common/cap-std-sync/src/file.rs | 6 ++++++ crates/wasi-common/cap-std-sync/src/stdio.rs | 12 ++++++++++++ crates/wasi-common/src/file.rs | 3 +++ crates/wasi-common/src/pipe.rs | 12 ++++++++++++ crates/wasi-common/tokio/src/file.rs | 6 ++++++ 5 files changed, 39 insertions(+) diff --git a/crates/wasi-common/cap-std-sync/src/file.rs b/crates/wasi-common/cap-std-sync/src/file.rs index ba9ff2743a..751c5f21b1 100644 --- a/crates/wasi-common/cap-std-sync/src/file.rs +++ b/crates/wasi-common/cap-std-sync/src/file.rs @@ -119,6 +119,12 @@ impl WasiFile for File { async fn num_ready_bytes(&self) -> Result { Ok(self.0.num_ready_bytes()?) } + async fn readable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } + async fn writable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } } pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType { diff --git a/crates/wasi-common/cap-std-sync/src/stdio.rs b/crates/wasi-common/cap-std-sync/src/stdio.rs index d4d515cfeb..d86d181399 100644 --- a/crates/wasi-common/cap-std-sync/src/stdio.rs +++ b/crates/wasi-common/cap-std-sync/src/stdio.rs @@ -103,6 +103,12 @@ impl WasiFile for Stdin { async fn num_ready_bytes(&self) -> Result { Ok(self.0.num_ready_bytes()?) } + async fn readable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } + async fn writable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } } #[cfg(windows)] impl AsRawHandle for Stdin { @@ -203,6 +209,12 @@ macro_rules! wasi_file_write_impl { async fn num_ready_bytes(&self) -> Result { Ok(0) } + async fn readable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } + async fn writable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } } #[cfg(windows)] impl AsRawHandle for $ty { diff --git a/crates/wasi-common/src/file.rs b/crates/wasi-common/src/file.rs index b8eadb3673..dede0c0dc3 100644 --- a/crates/wasi-common/src/file.rs +++ b/crates/wasi-common/src/file.rs @@ -36,6 +36,9 @@ pub trait WasiFile { async fn seek(&self, pos: std::io::SeekFrom) -> Result; // file op that generates a new stream from a file will supercede this async fn peek(&self, buf: &mut [u8]) -> Result; // read op async fn num_ready_bytes(&self) -> Result; // read op + + async fn readable(&mut self) -> Result<(), Error>; + async fn writable(&mut self) -> Result<(), Error>; } #[derive(Debug, Copy, Clone, PartialEq, Eq)] diff --git a/crates/wasi-common/src/pipe.rs b/crates/wasi-common/src/pipe.rs index 5e5d418767..192fe5d10a 100644 --- a/crates/wasi-common/src/pipe.rs +++ b/crates/wasi-common/src/pipe.rs @@ -183,6 +183,12 @@ impl WasiFile for ReadPipe { async fn num_ready_bytes(&self) -> Result { Ok(0) } + async fn readable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } + async fn writable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } } /// A virtual pipe write end. @@ -336,4 +342,10 @@ impl WasiFile for WritePipe { async fn num_ready_bytes(&self) -> Result { Ok(0) } + async fn readable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } + async fn writable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } } diff --git a/crates/wasi-common/tokio/src/file.rs b/crates/wasi-common/tokio/src/file.rs index 503e2168c4..a47e6b4ccf 100644 --- a/crates/wasi-common/tokio/src/file.rs +++ b/crates/wasi-common/tokio/src/file.rs @@ -184,6 +184,12 @@ impl WasiFile for File { use unsafe_io::AsUnsafeFile; asyncify(|| self.0.as_file_view().num_ready_bytes()).await } + async fn readable(&mut self) -> Result<(), Error> { + todo!("implement this in terms of tokio::io::AsyncFd") + } + async fn writable(&mut self) -> Result<(), Error> { + todo!("implement this in terms of tokio::io::AsyncFd") + } } pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType { use cap_fs_ext::FileTypeExt; From 867d2c9a350daef9ed1ae1354c941bac29b177b1 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 26 Apr 2021 15:49:27 -0700 Subject: [PATCH 32/72] follow the types --- Cargo.lock | 33 +++++ crates/wasi-common/tokio/Cargo.toml | 4 +- crates/wasi-common/tokio/src/file.rs | 38 +++++- crates/wasi-common/tokio/src/sched/unix.rs | 143 +++------------------ 4 files changed, 88 insertions(+), 130 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7d6bac18d..2ac571fc39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1590,6 +1590,28 @@ dependencies = [ "autocfg 1.0.1", ] +[[package]] +name = "mio" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" +dependencies = [ + "libc", + "log", + "miow", + "ntapi", + "winapi", +] + +[[package]] +name = "miow" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +dependencies = [ + "winapi", +] + [[package]] name = "more-asserts" version = "0.2.1" @@ -1619,6 +1641,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + [[package]] name = "num-bigint" version = "0.2.6" @@ -2827,7 +2858,9 @@ checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5" dependencies = [ "autocfg 1.0.1", "bytes", + "libc", "memchr", + "mio", "num_cpus", "pin-project-lite", "tokio-macros", diff --git a/crates/wasi-common/tokio/Cargo.toml b/crates/wasi-common/tokio/Cargo.toml index d67fc72914..777578bd4b 100644 --- a/crates/wasi-common/tokio/Cargo.toml +++ b/crates/wasi-common/tokio/Cargo.toml @@ -15,7 +15,7 @@ include = ["src/**/*", "LICENSE" ] wasi-common = { path = "../", version = "0.26.0" } wasi-cap-std-sync = { path = "../cap-std-sync", version = "0.26.0" } wiggle = { path = "../../wiggle", version = "0.26.0" } -tokio = { version = "1.5.0", features = [ "rt", "fs", "time" , "io-util"] } +tokio = { version = "1.5.0", features = [ "rt", "fs", "time", "io-util", "net"] } cap-std = "0.13.7" cap-fs-ext = "0.13.7" cap-time-ext = "0.13.7" @@ -34,4 +34,4 @@ lazy_static = "1.4" [dev-dependencies] tempfile = "3.1.0" -tokio = { features = [ "macros" ] } +tokio = { version = "1.5.0", features = [ "macros" ] } diff --git a/crates/wasi-common/tokio/src/file.rs b/crates/wasi-common/tokio/src/file.rs index a47e6b4ccf..2acdbc0f4a 100644 --- a/crates/wasi-common/tokio/src/file.rs +++ b/crates/wasi-common/tokio/src/file.rs @@ -184,13 +184,47 @@ impl WasiFile for File { use unsafe_io::AsUnsafeFile; asyncify(|| self.0.as_file_view().num_ready_bytes()).await } + #[cfg(not(windows))] async fn readable(&mut self) -> Result<(), Error> { - todo!("implement this in terms of tokio::io::AsyncFd") + // The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object. + // AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce + // mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this + // async method to ensure this is the only Future which can access the RawFd during the + // lifetime of the AsyncFd. + use tokio::io::{unix::AsyncFd, Interest}; + use unsafe_io::os::posish::AsRawFd; + let rawfd = self.0.as_raw_fd(); + let asyncfd = AsyncFd::with_interest(rawfd, Interest::READABLE)?; + let _ = asyncfd.readable().await?; + Ok(()) } + #[cfg(windows)] + async fn readable(&mut self) -> Result<(), Error> { + // Windows uses a rawfd based scheduler :( + Err(Error::badf()) + } + + #[cfg(not(windows))] async fn writable(&mut self) -> Result<(), Error> { - todo!("implement this in terms of tokio::io::AsyncFd") + // The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object. + // AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce + // mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this + // async method to ensure this is the only Future which can access the RawFd during the + // lifetime of the AsyncFd. + use tokio::io::{unix::AsyncFd, Interest}; + use unsafe_io::os::posish::AsRawFd; + let rawfd = self.0.as_raw_fd(); + let asyncfd = AsyncFd::with_interest(rawfd, Interest::WRITABLE)?; + let _ = asyncfd.writable().await?; + Ok(()) + } + #[cfg(windows)] + async fn writable(&mut self) -> Result<(), Error> { + // Windows uses a rawfd based scheduler :( + Err(Error::badf()) } } + pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType { use cap_fs_ext::FileTypeExt; if ft.is_dir() { diff --git a/crates/wasi-common/tokio/src/sched/unix.rs b/crates/wasi-common/tokio/src/sched/unix.rs index 07a1cf0a9f..7445394840 100644 --- a/crates/wasi-common/tokio/src/sched/unix.rs +++ b/crates/wasi-common/tokio/src/sched/unix.rs @@ -1,7 +1,9 @@ use cap_std::time::Duration; use std::convert::TryInto; +use std::future::{Future, Poll as FPoll}; use std::ops::Deref; -use std::os::unix::io::{AsRawFd, RawFd}; +use std::pin::Pin; +use std::task::Context; use wasi_common::{ file::WasiFile, sched::{ @@ -11,147 +13,36 @@ use wasi_common::{ Error, ErrorExt, }; -use poll::{PollFd, PollFlags}; - pub async fn poll_oneoff<'a>(poll: &'_ Poll<'a>) -> Result<(), Error> { if poll.is_empty() { return Ok(()); } - let mut pollfds = Vec::new(); + let mut futures: Vec>>>> = Vec::new(); let timeout = poll.earliest_clock_deadline(); for s in poll.rw_subscriptions() { match s { Subscription::Read(f) => { - let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or( - Error::invalid_argument().context("read subscription fd downcast failed"), - )?; - pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLIN) }); + futures.push(Box::pin(async move { + f.file.readable().await?; + f.complete(f.file.num_ready_bytes().await?, RwEventFlags::empty()); + Ok(()) + })); } Subscription::Write(f) => { - let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or( - Error::invalid_argument().context("write subscription fd downcast failed"), - )?; - pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLOUT) }); + futures.push(Box::pin(async move { + f.file.writable().await?; + f.complete(0, RwEventFlags::empty()); + Ok(()) + })); } Subscription::MonotonicClock { .. } => unreachable!(), } } - let ready = loop { - let poll_timeout = if let Some(t) = timeout { - let duration = t.duration_until().unwrap_or(Duration::from_secs(0)); - (duration.as_millis() + 1) // XXX try always rounding up? - .try_into() - .map_err(|_| Error::overflow().context("poll timeout"))? - } else { - libc::c_int::max_value() - }; - tracing::debug!( - poll_timeout = tracing::field::debug(poll_timeout), - poll_fds = tracing::field::debug(&pollfds), - "poll" - ); - match poll::poll(&mut pollfds, poll_timeout) { - Ok(ready) => break ready, - Err(_) => { - let last_err = std::io::Error::last_os_error(); - if last_err.raw_os_error().unwrap() == libc::EINTR { - continue; - } else { - return Err(last_err.into()); - } - } - } - }; - if ready > 0 { - for (rwsub, pollfd) in poll.rw_subscriptions().zip(pollfds.into_iter()) { - if let Some(revents) = pollfd.revents() { - let (nbytes, rwsub) = match rwsub { - Subscription::Read(sub) => { - let ready = sub.file.num_ready_bytes().await?; - (std::cmp::max(ready, 1), sub) - } - Subscription::Write(sub) => (0, sub), - _ => unreachable!(), - }; - if revents.contains(PollFlags::POLLNVAL) { - rwsub.error(Error::badf()); - } else if revents.contains(PollFlags::POLLERR) { - rwsub.error(Error::io()); - } else if revents.contains(PollFlags::POLLHUP) { - rwsub.complete(nbytes, RwEventFlags::HANGUP); - } else { - rwsub.complete(nbytes, RwEventFlags::empty()); - }; - } - } - } else { - timeout - .expect("timed out") - .result() - .expect("timer deadline is past") - .unwrap() + // Incorrect, but lets get the type errors fixed before we write the right multiplexer here: + for f in futures { + f.await?; } Ok(()) } - -fn wasi_file_raw_fd(f: &dyn WasiFile) -> Option { - todo!() -} - -mod poll { - use bitflags::bitflags; - use std::convert::TryInto; - use std::os::unix::io::RawFd; - - bitflags! { - pub struct PollFlags: libc::c_short { - const POLLIN = libc::POLLIN; - const POLLPRI = libc::POLLPRI; - const POLLOUT = libc::POLLOUT; - const POLLRDNORM = libc::POLLRDNORM; - const POLLWRNORM = libc::POLLWRNORM; - const POLLRDBAND = libc::POLLRDBAND; - const POLLWRBAND = libc::POLLWRBAND; - const POLLERR = libc::POLLERR; - const POLLHUP = libc::POLLHUP; - const POLLNVAL = libc::POLLNVAL; - } - } - - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] - #[repr(C)] - pub struct PollFd(libc::pollfd); - - impl PollFd { - pub unsafe fn new(fd: RawFd, events: PollFlags) -> Self { - Self(libc::pollfd { - fd, - events: events.bits(), - revents: PollFlags::empty().bits(), - }) - } - - pub fn revents(self) -> Option { - PollFlags::from_bits(self.0.revents) - } - } - - pub fn poll(fds: &mut [PollFd], timeout: libc::c_int) -> Result { - let nready = unsafe { - libc::poll( - fds.as_mut_ptr() as *mut libc::pollfd, - fds.len() as libc::nfds_t, - timeout, - ) - }; - if nready == -1 { - Err(std::io::Error::last_os_error()) - } else { - // When poll doesn't fail, its return value is a non-negative int, which will - // always be convertable to usize, so we can unwrap() here. - Ok(nready.try_into().unwrap()) - } - } -} From a532d0f379b44b2dbeb2141be79e1bece918f445 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 26 Apr 2021 16:06:56 -0700 Subject: [PATCH 33/72] subscribe to mut fds --- crates/wasi-common/src/sched.rs | 6 +++--- crates/wasi-common/src/sched/subscription.rs | 6 +++--- crates/wasi-common/src/snapshots/preview_0.rs | 20 ++++++++++++++++--- crates/wasi-common/src/snapshots/preview_1.rs | 18 +++++++++++++++-- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/crates/wasi-common/src/sched.rs b/crates/wasi-common/src/sched.rs index 3434ef62dc..b9a8062a4b 100644 --- a/crates/wasi-common/src/sched.rs +++ b/crates/wasi-common/src/sched.rs @@ -2,7 +2,7 @@ use crate::clocks::WasiMonotonicClock; use crate::file::WasiFile; use crate::Error; use cap_std::time::Instant; -use std::cell::Ref; +use std::cell::RefMut; pub mod subscription; pub use cap_std::time::Duration; @@ -52,11 +52,11 @@ impl<'a> Poll<'a> { ud, )); } - pub fn subscribe_read(&mut self, file: Ref<'a, dyn WasiFile>, ud: Userdata) { + pub fn subscribe_read(&mut self, file: RefMut<'a, dyn WasiFile>, ud: Userdata) { self.subs .push((Subscription::Read(RwSubscription::new(file)), ud)); } - pub fn subscribe_write(&mut self, file: Ref<'a, dyn WasiFile>, ud: Userdata) { + pub fn subscribe_write(&mut self, file: RefMut<'a, dyn WasiFile>, ud: Userdata) { self.subs .push((Subscription::Write(RwSubscription::new(file)), ud)); } diff --git a/crates/wasi-common/src/sched/subscription.rs b/crates/wasi-common/src/sched/subscription.rs index 799cfc665f..3347d3a826 100644 --- a/crates/wasi-common/src/sched/subscription.rs +++ b/crates/wasi-common/src/sched/subscription.rs @@ -3,7 +3,7 @@ use crate::file::WasiFile; use crate::Error; use bitflags::bitflags; use cap_std::time::{Duration, Instant}; -use std::cell::{Cell, Ref}; +use std::cell::{Cell, RefMut}; bitflags! { pub struct RwEventFlags: u32 { @@ -12,12 +12,12 @@ bitflags! { } pub struct RwSubscription<'a> { - pub file: Ref<'a, dyn WasiFile>, + pub file: RefMut<'a, dyn WasiFile>, status: Cell>>, } impl<'a> RwSubscription<'a> { - pub fn new(file: Ref<'a, dyn WasiFile>) -> Self { + pub fn new(file: RefMut<'a, dyn WasiFile>) -> Self { Self { file, status: Cell::new(None), diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs index 362789bc35..775962afef 100644 --- a/crates/wasi-common/src/snapshots/preview_0.rs +++ b/crates/wasi-common/src/snapshots/preview_0.rs @@ -1,4 +1,4 @@ -use crate::file::{FileCaps, FileEntryExt, TableFileExt}; +use crate::file::{FileCaps, FileEntryExt, FileEntryMutExt, TableFileExt}; use crate::sched::{ subscription::{RwEventFlags, SubscriptionResult}, Poll, @@ -7,6 +7,7 @@ use crate::snapshots::preview_1::types as snapshot1_types; use crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1; use crate::{Error, ErrorExt, WasiCtx}; use cap_std::time::Duration; +use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; use std::io::{IoSlice, IoSliceMut}; use std::ops::Deref; @@ -778,6 +779,7 @@ impl wasi_unstable::WasiUnstable for WasiCtx { } let table = self.table(); + let mut subscribed_fds = HashSet::new(); let mut poll = Poll::new(); let subs = subs.as_array(nsubscriptions); @@ -816,15 +818,27 @@ impl wasi_unstable::WasiUnstable for WasiCtx { }, types::SubscriptionU::FdRead(readsub) => { let fd = readsub.file_descriptor; + if subscribed_fds.contains(&fd) { + Err(Error::invalid_argument() + .context("Fd can be subscribed to at most once per poll_oneoff"))?; + } else { + subscribed_fds.insert(fd); + } let file = table - .get_file(u32::from(fd))? + .get_file_mut(u32::from(fd))? .get_cap(FileCaps::POLL_READWRITE)?; poll.subscribe_read(file, sub.userdata.into()); } types::SubscriptionU::FdWrite(writesub) => { let fd = writesub.file_descriptor; + if subscribed_fds.contains(&fd) { + Err(Error::invalid_argument() + .context("Fd can be subscribed to at most once per poll_oneoff"))?; + } else { + subscribed_fds.insert(fd); + } let file = table - .get_file(u32::from(fd))? + .get_file_mut(u32::from(fd))? .get_cap(FileCaps::POLL_READWRITE)?; poll.subscribe_write(file, sub.userdata.into()); } diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index 4d04e6e047..04ba4313d0 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -13,6 +13,7 @@ use crate::{ use anyhow::Context; use cap_std::time::{Duration, SystemClock}; use std::cell::{Ref, RefMut}; +use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; use std::io::{IoSlice, IoSliceMut}; use std::ops::{Deref, DerefMut}; @@ -970,6 +971,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } let table = self.table(); + let mut subscribed_fds = HashSet::new(); let mut poll = Poll::new(); let subs = subs.as_array(nsubscriptions); @@ -1008,15 +1010,27 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { }, types::SubscriptionU::FdRead(readsub) => { let fd = readsub.file_descriptor; + if subscribed_fds.contains(&fd) { + return Err(Error::invalid_argument() + .context("Fd can be subscribed to at most once per poll_oneoff")); + } else { + subscribed_fds.insert(fd); + } let file = table - .get_file(u32::from(fd))? + .get_file_mut(u32::from(fd))? .get_cap(FileCaps::POLL_READWRITE)?; poll.subscribe_read(file, sub.userdata.into()); } types::SubscriptionU::FdWrite(writesub) => { let fd = writesub.file_descriptor; + if subscribed_fds.contains(&fd) { + return Err(Error::invalid_argument() + .context("Fd can be subscribed to at most once per poll_oneoff")); + } else { + subscribed_fds.insert(fd); + } let file = table - .get_file(u32::from(fd))? + .get_file_mut(u32::from(fd))? .get_cap(FileCaps::POLL_READWRITE)?; poll.subscribe_write(file, sub.userdata.into()); } From 02581ddda0598a84e18998d0137cb7f559c8d99a Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 27 Apr 2021 17:41:07 -0700 Subject: [PATCH 34/72] poll_oneoff test: don't try to poll same fd for read and write --- .../wasi-tests/src/bin/poll_oneoff.rs | 56 +++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/crates/test-programs/wasi-tests/src/bin/poll_oneoff.rs b/crates/test-programs/wasi-tests/src/bin/poll_oneoff.rs index 6407a32f3c..3246b5e505 100644 --- a/crates/test-programs/wasi-tests/src/bin/poll_oneoff.rs +++ b/crates/test-programs/wasi-tests/src/bin/poll_oneoff.rs @@ -55,7 +55,10 @@ unsafe fn test_timeout() { event.userdata, CLOCK_ID, "the event.userdata should contain clock_id specified by the user" ); - assert!(after - before >= timeout, "poll_oneoff should sleep for the specified interval"); + assert!( + after - before >= timeout, + "poll_oneoff should sleep for the specified interval" + ); } // Like test_timeout, but uses `CLOCKID_REALTIME`, as WASI libc's sleep @@ -90,20 +93,22 @@ unsafe fn test_sleep() { event.userdata, CLOCK_ID, "the event.userdata should contain clock_id specified by the user" ); - assert!(after - before >= timeout, "poll_oneoff should sleep for the specified interval"); + assert!( + after - before >= timeout, + "poll_oneoff should sleep for the specified interval" + ); } -unsafe fn test_fd_readwrite(fd: wasi::Fd, error_code: wasi::Errno) { - let fd_readwrite = wasi::SubscriptionFdReadwrite { - file_descriptor: fd, - }; +unsafe fn test_fd_readwrite(readable_fd: wasi::Fd, writable_fd: wasi::Fd, error_code: wasi::Errno) { let r#in = [ wasi::Subscription { userdata: 1, u: wasi::SubscriptionU { tag: wasi::EVENTTYPE_FD_READ, u: wasi::SubscriptionUU { - fd_read: fd_readwrite, + fd_read: wasi::SubscriptionFdReadwrite { + file_descriptor: readable_fd, + }, }, }, }, @@ -112,7 +117,9 @@ unsafe fn test_fd_readwrite(fd: wasi::Fd, error_code: wasi::Errno) { u: wasi::SubscriptionU { tag: wasi::EVENTTYPE_FD_WRITE, u: wasi::SubscriptionUU { - fd_write: fd_readwrite, + fd_write: wasi::SubscriptionFdReadwrite { + file_descriptor: writable_fd, + }, }, }, }, @@ -143,26 +150,43 @@ unsafe fn test_fd_readwrite(fd: wasi::Fd, error_code: wasi::Errno) { unsafe fn test_fd_readwrite_valid_fd(dir_fd: wasi::Fd) { // Create a file in the scratch directory. - let file_fd = wasi::path_open( + let readable_fd = wasi::path_open( dir_fd, 0, - "file", + "readable_file", wasi::OFLAGS_CREAT, - wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_POLL_FD_READWRITE, + wasi::RIGHTS_FD_READ | wasi::RIGHTS_POLL_FD_READWRITE, 0, 0, ) - .expect("opening a file"); + .expect("opening a readable file"); assert_gt!( - file_fd, + readable_fd, + libc::STDERR_FILENO as wasi::Fd, + "file descriptor range check", + ); + // Create a file in the scratch directory. + let writable_fd = wasi::path_open( + dir_fd, + 0, + "writable_file", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_WRITE | wasi::RIGHTS_POLL_FD_READWRITE, + 0, + 0, + ) + .expect("opening a writable file"); + assert_gt!( + writable_fd, libc::STDERR_FILENO as wasi::Fd, "file descriptor range check", ); - test_fd_readwrite(file_fd, wasi::ERRNO_SUCCESS); + test_fd_readwrite(readable_fd, writable_fd, wasi::ERRNO_SUCCESS); - wasi::fd_close(file_fd).expect("closing a file"); - wasi::path_unlink_file(dir_fd, "file").expect("removing a file"); + wasi::fd_close(readable_fd).expect("closing readable_file"); + wasi::path_unlink_file(dir_fd, "readable_file").expect("removing readable_file"); + wasi::path_unlink_file(dir_fd, "writable_file").expect("removing writable_file"); } unsafe fn test_fd_readwrite_invalid_fd() { From b3e1ab4553683dd4d097237a9c4c504758f904fa Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 28 Apr 2021 11:56:48 -0700 Subject: [PATCH 35/72] restructure Poll to hold a Table and fd instead of a RefMut unfortunately, the borrow checker defeated me: changing the RwSubscription file form a Ref to a RefMut turned into borrow checker errors in the impl of the poll_oneoff trait method. This implementation makes an end run by having Poll hold onto the table and fd, and borrow the file at the site of use, rather than try to own the RefMut. I have no idea why this convinces the borrow checker that anything is different, but it does and I need to get this PR done and I don't think comprimising on this internal abstraction is worth fighting against --- .../cap-std-sync/src/sched/unix.rs | 10 +++-- crates/wasi-common/src/sched.rs | 44 ++++++++++++++----- crates/wasi-common/src/sched/subscription.rs | 26 ++++++++--- crates/wasi-common/src/snapshots/preview_0.rs | 28 ++---------- crates/wasi-common/src/snapshots/preview_1.rs | 26 ++--------- crates/wasi-common/tokio/src/sched/unix.rs | 8 ++-- 6 files changed, 72 insertions(+), 70 deletions(-) diff --git a/crates/wasi-common/cap-std-sync/src/sched/unix.rs b/crates/wasi-common/cap-std-sync/src/sched/unix.rs index 37fbbc1c0f..030c1bcf68 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/unix.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/unix.rs @@ -32,14 +32,14 @@ impl WasiSched for SyncSched { for s in poll.rw_subscriptions() { match s { Subscription::Read(f) => { - let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or( + let raw_fd = wasi_file_raw_fd(f.file()?.deref()).ok_or( Error::invalid_argument().context("read subscription fd downcast failed"), )?; pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLIN) }); } Subscription::Write(f) => { - let raw_fd = wasi_file_raw_fd(f.file.deref()).ok_or( + let raw_fd = wasi_file_raw_fd(f.file()?.deref()).ok_or( Error::invalid_argument().context("write subscription fd downcast failed"), )?; pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLOUT) }); @@ -79,7 +79,11 @@ impl WasiSched for SyncSched { if let Some(revents) = pollfd.revents() { let (nbytes, rwsub) = match rwsub { Subscription::Read(sub) => { - let ready = sub.file.num_ready_bytes().await?; + let ready = sub + .file() + .expect("validated file already") + .num_ready_bytes() + .await?; (std::cmp::max(ready, 1), sub) } Subscription::Write(sub) => (0, sub), diff --git a/crates/wasi-common/src/sched.rs b/crates/wasi-common/src/sched.rs index b9a8062a4b..984b8d9848 100644 --- a/crates/wasi-common/src/sched.rs +++ b/crates/wasi-common/src/sched.rs @@ -1,8 +1,8 @@ use crate::clocks::WasiMonotonicClock; -use crate::file::WasiFile; -use crate::Error; +use crate::table::Table; +use crate::{Error, ErrorExt}; use cap_std::time::Instant; -use std::cell::RefMut; +use std::collections::HashSet; pub mod subscription; pub use cap_std::time::Duration; @@ -29,12 +29,18 @@ impl From for u64 { } pub struct Poll<'a> { + table: &'a Table, + fds: HashSet, subs: Vec<(Subscription<'a>, Userdata)>, } impl<'a> Poll<'a> { - pub fn new() -> Self { - Self { subs: Vec::new() } + pub fn new(table: &'a Table) -> Self { + Self { + table, + fds: HashSet::new(), + subs: Vec::new(), + } } pub fn subscribe_monotonic_clock( &mut self, @@ -52,13 +58,31 @@ impl<'a> Poll<'a> { ud, )); } - pub fn subscribe_read(&mut self, file: RefMut<'a, dyn WasiFile>, ud: Userdata) { + pub fn subscribe_read(&mut self, fd: u32, ud: Userdata) -> Result<(), Error> { + if self.fds.contains(&fd) { + return Err( + Error::invalid_argument().context("Fd can be subscribed to at most once per poll") + ); + } else { + self.fds.insert(fd); + } self.subs - .push((Subscription::Read(RwSubscription::new(file)), ud)); + .push((Subscription::Read(RwSubscription::new(self.table, fd)?), ud)); + Ok(()) } - pub fn subscribe_write(&mut self, file: RefMut<'a, dyn WasiFile>, ud: Userdata) { - self.subs - .push((Subscription::Write(RwSubscription::new(file)), ud)); + pub fn subscribe_write(&mut self, fd: u32, ud: Userdata) -> Result<(), Error> { + if self.fds.contains(&fd) { + return Err( + Error::invalid_argument().context("Fd can be subscribed to at most once per poll") + ); + } else { + self.fds.insert(fd); + } + self.subs.push(( + Subscription::Write(RwSubscription::new(self.table, fd)?), + ud, + )); + Ok(()) } pub fn results(self) -> Vec<(SubscriptionResult, Userdata)> { self.subs diff --git a/crates/wasi-common/src/sched/subscription.rs b/crates/wasi-common/src/sched/subscription.rs index 3347d3a826..73575ae69c 100644 --- a/crates/wasi-common/src/sched/subscription.rs +++ b/crates/wasi-common/src/sched/subscription.rs @@ -1,5 +1,6 @@ use crate::clocks::WasiMonotonicClock; -use crate::file::WasiFile; +use crate::file::{FileCaps, FileEntryMutExt, TableFileExt, WasiFile}; +use crate::table::Table; use crate::Error; use bitflags::bitflags; use cap_std::time::{Duration, Instant}; @@ -12,16 +13,29 @@ bitflags! { } pub struct RwSubscription<'a> { - pub file: RefMut<'a, dyn WasiFile>, + table: &'a Table, + fd: u32, status: Cell>>, } impl<'a> RwSubscription<'a> { - pub fn new(file: RefMut<'a, dyn WasiFile>) -> Self { - Self { - file, + /// Create an RwSubscription. This constructor checks to make sure the file we need exists, and + /// has the correct rights. But, we can't hold onto the WasiFile RefMut inside this structure + /// (Pat can't convince borrow checker, either not clever enough or a rustc bug), so we need to + /// re-borrow at use time. + pub fn new(table: &'a Table, fd: u32) -> Result { + let _ = table.get_file_mut(fd)?.get_cap(FileCaps::POLL_READWRITE)?; + Ok(Self { + table, + fd, status: Cell::new(None), - } + }) + } + /// This accessor could fail if there is an outstanding borrow of the file. + pub fn file(&self) -> Result, Error> { + self.table + .get_file_mut(self.fd)? + .get_cap(FileCaps::POLL_READWRITE) } pub fn complete(&self, size: u64, flags: RwEventFlags) { self.status.set(Some(Ok((size, flags)))) diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs index 775962afef..6028bf12da 100644 --- a/crates/wasi-common/src/snapshots/preview_0.rs +++ b/crates/wasi-common/src/snapshots/preview_0.rs @@ -1,4 +1,4 @@ -use crate::file::{FileCaps, FileEntryExt, FileEntryMutExt, TableFileExt}; +use crate::file::{FileCaps, FileEntryExt, TableFileExt}; use crate::sched::{ subscription::{RwEventFlags, SubscriptionResult}, Poll, @@ -7,7 +7,6 @@ use crate::snapshots::preview_1::types as snapshot1_types; use crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1; use crate::{Error, ErrorExt, WasiCtx}; use cap_std::time::Duration; -use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; use std::io::{IoSlice, IoSliceMut}; use std::ops::Deref; @@ -779,8 +778,7 @@ impl wasi_unstable::WasiUnstable for WasiCtx { } let table = self.table(); - let mut subscribed_fds = HashSet::new(); - let mut poll = Poll::new(); + let mut poll = Poll::new(&table); let subs = subs.as_array(nsubscriptions); for sub_elem in subs.iter() { @@ -818,29 +816,11 @@ impl wasi_unstable::WasiUnstable for WasiCtx { }, types::SubscriptionU::FdRead(readsub) => { let fd = readsub.file_descriptor; - if subscribed_fds.contains(&fd) { - Err(Error::invalid_argument() - .context("Fd can be subscribed to at most once per poll_oneoff"))?; - } else { - subscribed_fds.insert(fd); - } - let file = table - .get_file_mut(u32::from(fd))? - .get_cap(FileCaps::POLL_READWRITE)?; - poll.subscribe_read(file, sub.userdata.into()); + poll.subscribe_read(u32::from(fd), sub.userdata.into())?; } types::SubscriptionU::FdWrite(writesub) => { let fd = writesub.file_descriptor; - if subscribed_fds.contains(&fd) { - Err(Error::invalid_argument() - .context("Fd can be subscribed to at most once per poll_oneoff"))?; - } else { - subscribed_fds.insert(fd); - } - let file = table - .get_file_mut(u32::from(fd))? - .get_cap(FileCaps::POLL_READWRITE)?; - poll.subscribe_write(file, sub.userdata.into()); + poll.subscribe_write(u32::from(fd), sub.userdata.into())?; } } } diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index 04ba4313d0..2487ef339e 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -13,7 +13,6 @@ use crate::{ use anyhow::Context; use cap_std::time::{Duration, SystemClock}; use std::cell::{Ref, RefMut}; -use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; use std::io::{IoSlice, IoSliceMut}; use std::ops::{Deref, DerefMut}; @@ -971,8 +970,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } let table = self.table(); - let mut subscribed_fds = HashSet::new(); - let mut poll = Poll::new(); + let mut poll = Poll::new(&table); let subs = subs.as_array(nsubscriptions); for sub_elem in subs.iter() { @@ -1010,29 +1008,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { }, types::SubscriptionU::FdRead(readsub) => { let fd = readsub.file_descriptor; - if subscribed_fds.contains(&fd) { - return Err(Error::invalid_argument() - .context("Fd can be subscribed to at most once per poll_oneoff")); - } else { - subscribed_fds.insert(fd); - } - let file = table - .get_file_mut(u32::from(fd))? - .get_cap(FileCaps::POLL_READWRITE)?; - poll.subscribe_read(file, sub.userdata.into()); + poll.subscribe_read(u32::from(fd), sub.userdata.into())?; } types::SubscriptionU::FdWrite(writesub) => { let fd = writesub.file_descriptor; - if subscribed_fds.contains(&fd) { - return Err(Error::invalid_argument() - .context("Fd can be subscribed to at most once per poll_oneoff")); - } else { - subscribed_fds.insert(fd); - } - let file = table - .get_file_mut(u32::from(fd))? - .get_cap(FileCaps::POLL_READWRITE)?; - poll.subscribe_write(file, sub.userdata.into()); + poll.subscribe_write(u32::from(fd), sub.userdata.into())?; } } } diff --git a/crates/wasi-common/tokio/src/sched/unix.rs b/crates/wasi-common/tokio/src/sched/unix.rs index 7445394840..f286b0d29a 100644 --- a/crates/wasi-common/tokio/src/sched/unix.rs +++ b/crates/wasi-common/tokio/src/sched/unix.rs @@ -1,6 +1,6 @@ use cap_std::time::Duration; use std::convert::TryInto; -use std::future::{Future, Poll as FPoll}; +use std::future::Future; use std::ops::Deref; use std::pin::Pin; use std::task::Context; @@ -23,15 +23,15 @@ pub async fn poll_oneoff<'a>(poll: &'_ Poll<'a>) -> Result<(), Error> { match s { Subscription::Read(f) => { futures.push(Box::pin(async move { - f.file.readable().await?; - f.complete(f.file.num_ready_bytes().await?, RwEventFlags::empty()); + f.file()?.readable().await?; + f.complete(f.file()?.num_ready_bytes().await?, RwEventFlags::empty()); Ok(()) })); } Subscription::Write(f) => { futures.push(Box::pin(async move { - f.file.writable().await?; + f.file()?.writable().await?; f.complete(0, RwEventFlags::empty()); Ok(()) })); From ab4f5bb674946ba3d85cf985e486909e7db00036 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 29 Apr 2021 16:23:36 -0700 Subject: [PATCH 36/72] move dummy executor out to wiggle:: for reuse --- crates/wiggle/src/lib.rs | 37 ++++++++++++++++ crates/wiggle/wasmtime/macro/src/lib.rs | 59 ++----------------------- 2 files changed, 41 insertions(+), 55 deletions(-) diff --git a/crates/wiggle/src/lib.rs b/crates/wiggle/src/lib.rs index 7bcba7c065..602a8c9d17 100644 --- a/crates/wiggle/src/lib.rs +++ b/crates/wiggle/src/lib.rs @@ -953,3 +953,40 @@ impl From for Trap { Trap::String(err.to_string()) } } + +pub unsafe fn run_in_dummy_executor(future: F) -> F::Output { + use std::pin::Pin; + use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + + let mut f = Pin::from(Box::new(future)); + let waker = dummy_waker(); + let mut cx = Context::from_waker(&waker); + match f.as_mut().poll(&mut cx) { + Poll::Ready(val) => return val, + Poll::Pending => { + panic!("Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store") + } + } + + fn dummy_waker() -> Waker { + return unsafe { Waker::from_raw(clone(5 as *const _)) }; + + unsafe fn clone(ptr: *const ()) -> RawWaker { + assert_eq!(ptr as usize, 5); + const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); + RawWaker::new(ptr, &VTABLE) + } + + unsafe fn wake(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + + unsafe fn wake_by_ref(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + + unsafe fn drop(ptr: *const ()) { + assert_eq!(ptr as usize, 5); + } + } +} diff --git a/crates/wiggle/wasmtime/macro/src/lib.rs b/crates/wiggle/wasmtime/macro/src/lib.rs index 4764d217c1..67c91817c5 100644 --- a/crates/wiggle/wasmtime/macro/src/lib.rs +++ b/crates/wiggle/wasmtime/macro/src/lib.rs @@ -101,12 +101,10 @@ fn generate_module( let mut ctor_externs = Vec::new(); let mut host_funcs = Vec::new(); - let mut requires_dummy_executor = false; - for f in module.funcs() { let asyncness = async_conf.is_async(module.name.as_str(), f.name.as_str()); match asyncness { - Asyncness::Blocking => requires_dummy_executor = true, + Asyncness::Blocking => {} Asyncness::Async => { assert!( cfg!(feature = "async"), @@ -165,12 +163,6 @@ contained in the `cx` parameter.", } }); - let dummy_executor = if requires_dummy_executor { - dummy_executor() - } else { - quote!() - }; - quote! { #type_docs pub struct #type_name { @@ -233,8 +225,6 @@ contained in the `cx` parameter.", } #(#fns)* - - #dummy_executor } } } @@ -250,6 +240,7 @@ fn generate_func( ctors: &mut Vec, host_funcs: &mut Vec<(witx::Id, TokenStream2)>, ) { + let rt = names.runtime_mod(); let name_ident = names.func(&func.name); let (params, results) = func.wasm_signature(); @@ -329,7 +320,7 @@ fn generate_func( let #name_ident = wasmtime::Func::wrap( store, move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> { - Self::run_in_dummy_executor(Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*)) + unsafe { #rt::run_in_dummy_executor(Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*)) } } ); }); @@ -381,7 +372,7 @@ fn generate_func( .store() .get::>>() .ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?; - Self::run_in_dummy_executor(Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*)) + unsafe { #rt::run_in_dummy_executor(Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*)) } }, ); } @@ -404,45 +395,3 @@ fn generate_func( }; host_funcs.push((func.name.clone(), host_wrapper)); } - -fn dummy_executor() -> TokenStream2 { - quote! { - fn run_in_dummy_executor(future: F) -> F::Output { - use std::pin::Pin; - use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; - - let mut f = Pin::from(Box::new(future)); - let waker = dummy_waker(); - let mut cx = Context::from_waker(&waker); - match f.as_mut().poll(&mut cx) { - Poll::Ready(val) => return val, - Poll::Pending => { - panic!("Cannot wait on pending future: must enable wiggle \"async\" future and execute on an async Store") - } - } - - fn dummy_waker() -> Waker { - return unsafe { Waker::from_raw(clone(5 as *const _)) }; - - unsafe fn clone(ptr: *const ()) -> RawWaker { - assert_eq!(ptr as usize, 5); - const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop); - RawWaker::new(ptr, &VTABLE) - } - - unsafe fn wake(ptr: *const ()) { - assert_eq!(ptr as usize, 5); - } - - unsafe fn wake_by_ref(ptr: *const ()) { - assert_eq!(ptr as usize, 5); - } - - unsafe fn drop(ptr: *const ()) { - assert_eq!(ptr as usize, 5); - } - } - - } - } -} From b7efcbe80fd5abef0e08ebb4302418a9f8e2ad41 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 29 Apr 2021 16:44:45 -0700 Subject: [PATCH 37/72] jump through enough hoops for the poll lifetime to work out you program rust for a few years and you think you're done tearing your hair out over lifetimes, well, you'll find yourself wrong --- .../{poll_oneoff.rs => poll_oneoff_files.rs} | 0 .../cap-std-sync/src/sched/unix.rs | 18 ++---- crates/wasi-common/src/error.rs | 2 +- crates/wasi-common/src/lib.rs | 2 +- crates/wasi-common/src/sched.rs | 56 ++++++------------- crates/wasi-common/src/sched/subscription.rs | 34 ++++------- crates/wasi-common/src/snapshots/preview_0.rs | 34 +++++++++-- crates/wasi-common/src/snapshots/preview_1.rs | 40 +++++++++++-- 8 files changed, 99 insertions(+), 87 deletions(-) rename crates/test-programs/wasi-tests/src/bin/{poll_oneoff.rs => poll_oneoff_files.rs} (100%) diff --git a/crates/test-programs/wasi-tests/src/bin/poll_oneoff.rs b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs similarity index 100% rename from crates/test-programs/wasi-tests/src/bin/poll_oneoff.rs rename to crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs diff --git a/crates/wasi-common/cap-std-sync/src/sched/unix.rs b/crates/wasi-common/cap-std-sync/src/sched/unix.rs index 030c1bcf68..abf0338b1e 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/unix.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/unix.rs @@ -1,6 +1,5 @@ use cap_std::time::Duration; use std::convert::TryInto; -use std::ops::Deref; use std::os::unix::io::{AsRawFd, RawFd}; use wasi_common::{ file::WasiFile, @@ -23,23 +22,22 @@ impl SyncSched { #[wiggle::async_trait] impl WasiSched for SyncSched { - async fn poll_oneoff<'a>(&self, poll: &'_ Poll<'a>) -> Result<(), Error> { + async fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> { if poll.is_empty() { return Ok(()); } let mut pollfds = Vec::new(); - let timeout = poll.earliest_clock_deadline(); for s in poll.rw_subscriptions() { match s { Subscription::Read(f) => { - let raw_fd = wasi_file_raw_fd(f.file()?.deref()).ok_or( + let raw_fd = wasi_file_raw_fd(f.file).ok_or( Error::invalid_argument().context("read subscription fd downcast failed"), )?; pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLIN) }); } Subscription::Write(f) => { - let raw_fd = wasi_file_raw_fd(f.file()?.deref()).ok_or( + let raw_fd = wasi_file_raw_fd(f.file).ok_or( Error::invalid_argument().context("write subscription fd downcast failed"), )?; pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLOUT) }); @@ -49,7 +47,7 @@ impl WasiSched for SyncSched { } let ready = loop { - let poll_timeout = if let Some(t) = timeout { + let poll_timeout = if let Some(t) = poll.earliest_clock_deadline() { let duration = t.duration_until().unwrap_or(Duration::from_secs(0)); (duration.as_millis() + 1) // XXX try always rounding up? .try_into() @@ -79,11 +77,7 @@ impl WasiSched for SyncSched { if let Some(revents) = pollfd.revents() { let (nbytes, rwsub) = match rwsub { Subscription::Read(sub) => { - let ready = sub - .file() - .expect("validated file already") - .num_ready_bytes() - .await?; + let ready = sub.file.num_ready_bytes().await?; (std::cmp::max(ready, 1), sub) } Subscription::Write(sub) => (0, sub), @@ -101,7 +95,7 @@ impl WasiSched for SyncSched { } } } else { - timeout + poll.earliest_clock_deadline() .expect("timed out") .result() .expect("timer deadline is past") diff --git a/crates/wasi-common/src/error.rs b/crates/wasi-common/src/error.rs index cf132b59ee..20277554fc 100644 --- a/crates/wasi-common/src/error.rs +++ b/crates/wasi-common/src/error.rs @@ -23,7 +23,7 @@ //! The real value of using `anyhow::Error` here is being able to use //! `anyhow::Result::context` to aid in debugging of errors. -pub use anyhow::Error; +pub use anyhow::{Context, Error}; /// Internal error type for the `wasi-common` crate. /// Contains variants of the WASI `$errno` type are added according to what is actually used internally by diff --git a/crates/wasi-common/src/lib.rs b/crates/wasi-common/src/lib.rs index 4575b423b6..63910d4a60 100644 --- a/crates/wasi-common/src/lib.rs +++ b/crates/wasi-common/src/lib.rs @@ -66,7 +66,7 @@ pub use cap_rand::RngCore; pub use clocks::{SystemTimeSpec, WasiClocks, WasiMonotonicClock, WasiSystemClock}; pub use ctx::{WasiCtx, WasiCtxBuilder}; pub use dir::WasiDir; -pub use error::{Error, ErrorExt, ErrorKind}; +pub use error::{Context, Error, ErrorExt, ErrorKind}; pub use file::WasiFile; pub use sched::{Poll, WasiSched}; pub use string_array::StringArrayError; diff --git a/crates/wasi-common/src/sched.rs b/crates/wasi-common/src/sched.rs index 984b8d9848..e437f48727 100644 --- a/crates/wasi-common/src/sched.rs +++ b/crates/wasi-common/src/sched.rs @@ -1,8 +1,7 @@ use crate::clocks::WasiMonotonicClock; -use crate::table::Table; -use crate::{Error, ErrorExt}; +use crate::file::WasiFile; +use crate::Error; use cap_std::time::Instant; -use std::collections::HashSet; pub mod subscription; pub use cap_std::time::Duration; @@ -10,11 +9,12 @@ use subscription::{MonotonicClockSubscription, RwSubscription, Subscription, Sub #[wiggle::async_trait] pub trait WasiSched { - async fn poll_oneoff<'a>(&self, poll: &Poll<'a>) -> Result<(), Error>; + async fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error>; async fn sched_yield(&self) -> Result<(), Error>; async fn sleep(&self, duration: Duration) -> Result<(), Error>; } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct Userdata(u64); impl From for Userdata { fn from(u: u64) -> Userdata { @@ -28,19 +28,15 @@ impl From for u64 { } } +pub type PollResults = Vec<(SubscriptionResult, Userdata)>; + pub struct Poll<'a> { - table: &'a Table, - fds: HashSet, subs: Vec<(Subscription<'a>, Userdata)>, } impl<'a> Poll<'a> { - pub fn new(table: &'a Table) -> Self { - Self { - table, - fds: HashSet::new(), - subs: Vec::new(), - } + pub fn new() -> Self { + Self { subs: Vec::new() } } pub fn subscribe_monotonic_clock( &mut self, @@ -58,36 +54,18 @@ impl<'a> Poll<'a> { ud, )); } - pub fn subscribe_read(&mut self, fd: u32, ud: Userdata) -> Result<(), Error> { - if self.fds.contains(&fd) { - return Err( - Error::invalid_argument().context("Fd can be subscribed to at most once per poll") - ); - } else { - self.fds.insert(fd); - } + pub fn subscribe_read(&mut self, file: &'a mut dyn WasiFile, ud: Userdata) { self.subs - .push((Subscription::Read(RwSubscription::new(self.table, fd)?), ud)); - Ok(()) + .push((Subscription::Read(RwSubscription::new(file)), ud)); } - pub fn subscribe_write(&mut self, fd: u32, ud: Userdata) -> Result<(), Error> { - if self.fds.contains(&fd) { - return Err( - Error::invalid_argument().context("Fd can be subscribed to at most once per poll") - ); - } else { - self.fds.insert(fd); - } - self.subs.push(( - Subscription::Write(RwSubscription::new(self.table, fd)?), - ud, - )); - Ok(()) - } - pub fn results(self) -> Vec<(SubscriptionResult, Userdata)> { + pub fn subscribe_write(&mut self, file: &'a mut dyn WasiFile, ud: Userdata) { self.subs - .into_iter() - .filter_map(|(s, ud)| SubscriptionResult::from_subscription(s).map(|r| (r, ud))) + .push((Subscription::Write(RwSubscription::new(file)), ud)); + } + pub fn results(&self) -> Vec<(SubscriptionResult, Userdata)> { + self.subs + .iter() + .filter_map(|(s, ud)| SubscriptionResult::from_subscription(s).map(|r| (r, *ud))) .collect() } pub fn is_empty(&self) -> bool { diff --git a/crates/wasi-common/src/sched/subscription.rs b/crates/wasi-common/src/sched/subscription.rs index 73575ae69c..d333b74391 100644 --- a/crates/wasi-common/src/sched/subscription.rs +++ b/crates/wasi-common/src/sched/subscription.rs @@ -1,10 +1,9 @@ use crate::clocks::WasiMonotonicClock; -use crate::file::{FileCaps, FileEntryMutExt, TableFileExt, WasiFile}; -use crate::table::Table; +use crate::file::WasiFile; use crate::Error; use bitflags::bitflags; use cap_std::time::{Duration, Instant}; -use std::cell::{Cell, RefMut}; +use std::cell::Cell; bitflags! { pub struct RwEventFlags: u32 { @@ -13,29 +12,16 @@ bitflags! { } pub struct RwSubscription<'a> { - table: &'a Table, - fd: u32, + pub file: &'a mut dyn WasiFile, status: Cell>>, } impl<'a> RwSubscription<'a> { - /// Create an RwSubscription. This constructor checks to make sure the file we need exists, and - /// has the correct rights. But, we can't hold onto the WasiFile RefMut inside this structure - /// (Pat can't convince borrow checker, either not clever enough or a rustc bug), so we need to - /// re-borrow at use time. - pub fn new(table: &'a Table, fd: u32) -> Result { - let _ = table.get_file_mut(fd)?.get_cap(FileCaps::POLL_READWRITE)?; - Ok(Self { - table, - fd, + pub fn new(file: &'a mut dyn WasiFile) -> Self { + Self { + file, status: Cell::new(None), - }) - } - /// This accessor could fail if there is an outstanding borrow of the file. - pub fn file(&self) -> Result, Error> { - self.table - .get_file_mut(self.fd)? - .get_cap(FileCaps::POLL_READWRITE) + } } pub fn complete(&self, size: u64, flags: RwEventFlags) { self.status.set(Some(Ok((size, flags)))) @@ -43,8 +29,8 @@ impl<'a> RwSubscription<'a> { pub fn error(&self, error: Error) { self.status.set(Some(Err(error))) } - pub fn result(self) -> Option> { - self.status.into_inner() + pub fn result(&self) -> Option> { + self.status.take() } } @@ -83,7 +69,7 @@ pub enum SubscriptionResult { } impl SubscriptionResult { - pub fn from_subscription(s: Subscription) -> Option { + pub fn from_subscription(s: &Subscription) -> Option { match s { Subscription::Read(s) => s.result().map(SubscriptionResult::Read), Subscription::Write(s) => s.result().map(SubscriptionResult::Write), diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs index 6028bf12da..f933dfb04d 100644 --- a/crates/wasi-common/src/snapshots/preview_0.rs +++ b/crates/wasi-common/src/snapshots/preview_0.rs @@ -1,12 +1,14 @@ -use crate::file::{FileCaps, FileEntryExt, TableFileExt}; +use crate::file::{FileCaps, FileEntryExt, FileEntryMutExt, TableFileExt, WasiFile}; use crate::sched::{ subscription::{RwEventFlags, SubscriptionResult}, - Poll, + Poll, Userdata, }; use crate::snapshots::preview_1::types as snapshot1_types; use crate::snapshots::preview_1::wasi_snapshot_preview1::WasiSnapshotPreview1 as Snapshot1; use crate::{Error, ErrorExt, WasiCtx}; use cap_std::time::Duration; +use std::cell::RefMut; +use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; use std::io::{IoSlice, IoSliceMut}; use std::ops::Deref; @@ -778,7 +780,11 @@ impl wasi_unstable::WasiUnstable for WasiCtx { } let table = self.table(); - let mut poll = Poll::new(&table); + let mut sub_fds: HashSet = HashSet::new(); + // We need these refmuts to outlive Poll, which will hold the &mut dyn WasiFile inside + let mut read_refs: Vec<(RefMut<'_, dyn WasiFile>, Userdata)> = Vec::new(); + let mut write_refs: Vec<(RefMut<'_, dyn WasiFile>, Userdata)> = Vec::new(); + let mut poll = Poll::new(); let subs = subs.as_array(nsubscriptions); for sub_elem in subs.iter() { @@ -816,11 +822,29 @@ impl wasi_unstable::WasiUnstable for WasiCtx { }, types::SubscriptionU::FdRead(readsub) => { let fd = readsub.file_descriptor; - poll.subscribe_read(u32::from(fd), sub.userdata.into())?; + if sub_fds.contains(&fd) { + return Err(Error::invalid_argument() + .context("Fd can be subscribed to at most once per poll")); + } else { + sub_fds.insert(fd); + } + let file_ref = table + .get_file_mut(u32::from(fd))? + .get_cap(FileCaps::POLL_READWRITE)?; + read_refs.push((file_ref, sub.userdata.into())); } types::SubscriptionU::FdWrite(writesub) => { let fd = writesub.file_descriptor; - poll.subscribe_write(u32::from(fd), sub.userdata.into())?; + if sub_fds.contains(&fd) { + return Err(Error::invalid_argument() + .context("Fd can be subscribed to at most once per poll")); + } else { + sub_fds.insert(fd); + } + let file_ref = table + .get_file_mut(u32::from(fd))? + .get_cap(FileCaps::POLL_READWRITE)?; + write_refs.push((file_ref, sub.userdata.into())); } } } diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index 2487ef339e..2350e8663b 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -2,17 +2,18 @@ use crate::{ dir::{DirCaps, DirEntry, DirEntryExt, DirFdStat, ReaddirCursor, ReaddirEntity, TableDirExt}, file::{ Advice, FdFlags, FdStat, FileCaps, FileEntry, FileEntryExt, FileEntryMutExt, FileType, - Filestat, OFlags, TableFileExt, + Filestat, OFlags, TableFileExt, WasiFile, }, sched::{ subscription::{RwEventFlags, SubscriptionResult}, - Poll, + Poll, Userdata, }, Error, ErrorExt, ErrorKind, SystemTimeSpec, WasiCtx, }; use anyhow::Context; use cap_std::time::{Duration, SystemClock}; use std::cell::{Ref, RefMut}; +use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; use std::io::{IoSlice, IoSliceMut}; use std::ops::{Deref, DerefMut}; @@ -970,7 +971,11 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { } let table = self.table(); - let mut poll = Poll::new(&table); + let mut sub_fds: HashSet = HashSet::new(); + // We need these refmuts to outlive Poll, which will hold the &mut dyn WasiFile inside + let mut read_refs: Vec<(RefMut<'_, dyn WasiFile>, Userdata)> = Vec::new(); + let mut write_refs: Vec<(RefMut<'_, dyn WasiFile>, Userdata)> = Vec::new(); + let mut poll = Poll::new(); let subs = subs.as_array(nsubscriptions); for sub_elem in subs.iter() { @@ -1008,15 +1013,40 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { }, types::SubscriptionU::FdRead(readsub) => { let fd = readsub.file_descriptor; - poll.subscribe_read(u32::from(fd), sub.userdata.into())?; + if sub_fds.contains(&fd) { + return Err(Error::invalid_argument() + .context("Fd can be subscribed to at most once per poll")); + } else { + sub_fds.insert(fd); + } + let file_ref = table + .get_file_mut(u32::from(fd))? + .get_cap(FileCaps::POLL_READWRITE)?; + read_refs.push((file_ref, sub.userdata.into())); } types::SubscriptionU::FdWrite(writesub) => { let fd = writesub.file_descriptor; - poll.subscribe_write(u32::from(fd), sub.userdata.into())?; + if sub_fds.contains(&fd) { + return Err(Error::invalid_argument() + .context("Fd can be subscribed to at most once per poll")); + } else { + sub_fds.insert(fd); + } + let file_ref = table + .get_file_mut(u32::from(fd))? + .get_cap(FileCaps::POLL_READWRITE)?; + write_refs.push((file_ref, sub.userdata.into())); } } } + for (f, ud) in read_refs.iter_mut() { + poll.subscribe_read(f.deref_mut(), *ud); + } + for (f, ud) in write_refs.iter_mut() { + poll.subscribe_write(f.deref_mut(), *ud); + } + self.sched.poll_oneoff(&poll).await?; let results = poll.results(); From 7f34ccb909cf1d0e6592763302972fd72537791b Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 30 Apr 2021 15:36:56 -0700 Subject: [PATCH 38/72] various fixes to the design of Poll --- crates/wasi-common/cap-std-sync/src/sched/unix.rs | 2 +- crates/wasi-common/src/sched.rs | 14 +++++++------- crates/wasi-common/src/sched/subscription.rs | 2 +- crates/wasi-common/src/snapshots/preview_0.rs | 2 +- crates/wasi-common/src/snapshots/preview_1.rs | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/wasi-common/cap-std-sync/src/sched/unix.rs b/crates/wasi-common/cap-std-sync/src/sched/unix.rs index abf0338b1e..94a8614a60 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/unix.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/unix.rs @@ -22,7 +22,7 @@ impl SyncSched { #[wiggle::async_trait] impl WasiSched for SyncSched { - async fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error> { + async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { if poll.is_empty() { return Ok(()); } diff --git a/crates/wasi-common/src/sched.rs b/crates/wasi-common/src/sched.rs index e437f48727..d8be4deb99 100644 --- a/crates/wasi-common/src/sched.rs +++ b/crates/wasi-common/src/sched.rs @@ -9,7 +9,7 @@ use subscription::{MonotonicClockSubscription, RwSubscription, Subscription, Sub #[wiggle::async_trait] pub trait WasiSched { - async fn poll_oneoff<'a>(&self, poll: &'a Poll<'a>) -> Result<(), Error>; + async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error>; async fn sched_yield(&self) -> Result<(), Error>; async fn sleep(&self, duration: Duration) -> Result<(), Error>; } @@ -62,16 +62,16 @@ impl<'a> Poll<'a> { self.subs .push((Subscription::Write(RwSubscription::new(file)), ud)); } - pub fn results(&self) -> Vec<(SubscriptionResult, Userdata)> { + pub fn results(self) -> Vec<(SubscriptionResult, Userdata)> { self.subs - .iter() - .filter_map(|(s, ud)| SubscriptionResult::from_subscription(s).map(|r| (r, *ud))) + .into_iter() + .filter_map(|(s, ud)| SubscriptionResult::from_subscription(s).map(|r| (r, ud))) .collect() } pub fn is_empty(&self) -> bool { self.subs.is_empty() } - pub fn earliest_clock_deadline(&'a self) -> Option<&MonotonicClockSubscription<'a>> { + pub fn earliest_clock_deadline(&self) -> Option<&MonotonicClockSubscription<'a>> { self.subs .iter() .filter_map(|(s, _ud)| match s { @@ -80,8 +80,8 @@ impl<'a> Poll<'a> { }) .min_by(|a, b| a.deadline.cmp(&b.deadline)) } - pub fn rw_subscriptions(&'a self) -> impl Iterator> { - self.subs.iter().filter_map(|(s, _ud)| match s { + pub fn rw_subscriptions<'b>(&'b mut self) -> impl Iterator> { + self.subs.iter_mut().filter_map(|(s, _ud)| match s { Subscription::Read { .. } | Subscription::Write { .. } => Some(s), _ => None, }) diff --git a/crates/wasi-common/src/sched/subscription.rs b/crates/wasi-common/src/sched/subscription.rs index d333b74391..d49c646cfe 100644 --- a/crates/wasi-common/src/sched/subscription.rs +++ b/crates/wasi-common/src/sched/subscription.rs @@ -69,7 +69,7 @@ pub enum SubscriptionResult { } impl SubscriptionResult { - pub fn from_subscription(s: &Subscription) -> Option { + pub fn from_subscription(s: Subscription) -> Option { match s { Subscription::Read(s) => s.result().map(SubscriptionResult::Read), Subscription::Write(s) => s.result().map(SubscriptionResult::Write), diff --git a/crates/wasi-common/src/snapshots/preview_0.rs b/crates/wasi-common/src/snapshots/preview_0.rs index f933dfb04d..6eff280a47 100644 --- a/crates/wasi-common/src/snapshots/preview_0.rs +++ b/crates/wasi-common/src/snapshots/preview_0.rs @@ -849,7 +849,7 @@ impl wasi_unstable::WasiUnstable for WasiCtx { } } - self.sched.poll_oneoff(&poll).await?; + self.sched.poll_oneoff(&mut poll).await?; let results = poll.results(); let num_results = results.len(); diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index 2350e8663b..553066628b 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -1047,7 +1047,7 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { poll.subscribe_write(f.deref_mut(), *ud); } - self.sched.poll_oneoff(&poll).await?; + self.sched.poll_oneoff(&mut poll).await?; let results = poll.results(); let num_results = results.len(); From 62c4f0d1f76373eca1adaac2105f77577c3942b6 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Sat, 1 May 2021 15:39:09 -0700 Subject: [PATCH 39/72] wasi-tokio kinda sorta working --- Cargo.lock | 23 ++ crates/wasi-common/tokio/Cargo.toml | 6 +- crates/wasi-common/tokio/src/dir.rs | 36 +-- crates/wasi-common/tokio/src/file.rs | 51 ++- crates/wasi-common/tokio/src/lib.rs | 25 +- crates/wasi-common/tokio/src/sched.rs | 6 +- crates/wasi-common/tokio/src/sched/unix.rs | 81 +++-- crates/wasi-common/tokio/src/stdio.rs | 295 ++++++++++++++++++ crates/wasi-common/tokio/tests/poll_oneoff.rs | 27 ++ 9 files changed, 474 insertions(+), 76 deletions(-) create mode 100644 crates/wasi-common/tokio/src/stdio.rs create mode 100644 crates/wasi-common/tokio/tests/poll_oneoff.rs diff --git a/Cargo.lock b/Cargo.lock index 2ac571fc39..92c5e7611a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -345,6 +345,17 @@ dependencies = [ "unsafe-io", ] +[[package]] +name = "cap-tempfile" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2f6f45ddb06ff26f4cf2ba9838d5826d52e1a5f6b321d71f114bb38cf34a57" +dependencies = [ + "cap-std", + "rand 0.8.3", + "uuid", +] + [[package]] name = "cap-time-ext" version = "0.13.7" @@ -3060,6 +3071,15 @@ dependencies = [ "cfg-if 0.1.10", ] +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.2", +] + [[package]] name = "vec_map" version = "0.8.2" @@ -3180,13 +3200,16 @@ dependencies = [ name = "wasi-tokio" version = "0.26.0" dependencies = [ + "anyhow", "bitflags", "cap-fs-ext", "cap-std", + "cap-tempfile", "cap-time-ext", "fs-set-times", "lazy_static", "libc", + "posish", "system-interface", "tempfile", "tokio", diff --git a/crates/wasi-common/tokio/Cargo.toml b/crates/wasi-common/tokio/Cargo.toml index 777578bd4b..49e79f3d75 100644 --- a/crates/wasi-common/tokio/Cargo.toml +++ b/crates/wasi-common/tokio/Cargo.toml @@ -15,7 +15,7 @@ include = ["src/**/*", "LICENSE" ] wasi-common = { path = "../", version = "0.26.0" } wasi-cap-std-sync = { path = "../cap-std-sync", version = "0.26.0" } wiggle = { path = "../../wiggle", version = "0.26.0" } -tokio = { version = "1.5.0", features = [ "rt", "fs", "time", "io-util", "net"] } +tokio = { version = "1.5.0", features = [ "rt", "fs", "time", "io-util", "net", "io-std", "rt-multi-thread"] } cap-std = "0.13.7" cap-fs-ext = "0.13.7" cap-time-ext = "0.13.7" @@ -27,6 +27,8 @@ bitflags = "1.2" [target.'cfg(unix)'.dependencies] libc = "0.2" +posish = "0.6.1" + [target.'cfg(windows)'.dependencies] winapi = "0.3" @@ -35,3 +37,5 @@ lazy_static = "1.4" [dev-dependencies] tempfile = "3.1.0" tokio = { version = "1.5.0", features = [ "macros" ] } +anyhow = "1" +cap-tempfile = "0.13.7" diff --git a/crates/wasi-common/tokio/src/dir.rs b/crates/wasi-common/tokio/src/dir.rs index 9188d0cca7..f3d6623b62 100644 --- a/crates/wasi-common/tokio/src/dir.rs +++ b/crates/wasi-common/tokio/src/dir.rs @@ -76,7 +76,7 @@ impl WasiDir for Dir { return Err(Error::not_supported().context("SYNC family of FdFlags")); } - let f = asyncify(move || self.0.open_with(Path::new(path), &opts)).await?; + let f = asyncify(move || self.0.open_with(Path::new(path), &opts))?; let mut f = File::from_cap_std(f); // NONBLOCK does not have an OpenOption either, but we can patch that on with set_fd_flags: if fdflags.contains(FdFlags::NONBLOCK) { @@ -88,15 +88,15 @@ impl WasiDir for Dir { async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { let path = unsafe { std::mem::transmute::<_, &'static str>(path) }; let d = if symlink_follow { - asyncify(move || self.0.open_dir(Path::new(path))).await? + asyncify(move || self.0.open_dir(Path::new(path)))? } else { - asyncify(move || self.0.open_dir_nofollow(Path::new(path))).await? + asyncify(move || self.0.open_dir_nofollow(Path::new(path)))? }; Ok(Box::new(Dir::from_cap_std(d))) } async fn create_dir(&self, path: &str) -> Result<(), Error> { - asyncify(|| self.0.create_dir(Path::new(path))).await?; + asyncify(|| self.0.create_dir(Path::new(path)))?; Ok(()) } async fn readdir( @@ -106,7 +106,7 @@ impl WasiDir for Dir { // cap_std's read_dir does not include . and .., we should prepend these. // Why does the Ok contain a tuple? We can't construct a cap_std::fs::DirEntry, and we don't // have enough info to make a ReaddirEntity yet. - let dir_meta = asyncify(|| self.0.dir_metadata()).await?; + let dir_meta = asyncify(|| self.0.dir_metadata())?; let rd = vec![ { let name = ".".to_owned(); @@ -150,24 +150,24 @@ impl WasiDir for Dir { } async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { - asyncify(|| self.0.symlink(src_path, dest_path)).await?; + asyncify(|| self.0.symlink(src_path, dest_path))?; Ok(()) } async fn remove_dir(&self, path: &str) -> Result<(), Error> { - asyncify(|| self.0.remove_dir(Path::new(path))).await?; + asyncify(|| self.0.remove_dir(Path::new(path)))?; Ok(()) } async fn unlink_file(&self, path: &str) -> Result<(), Error> { - asyncify(|| self.0.remove_file_or_symlink(Path::new(path))).await?; + asyncify(|| self.0.remove_file_or_symlink(Path::new(path)))?; Ok(()) } async fn read_link(&self, path: &str) -> Result { - let link = asyncify(|| self.0.read_link(Path::new(path))).await?; + let link = asyncify(|| self.0.read_link(Path::new(path)))?; Ok(link) } async fn get_filestat(&self) -> Result { - let meta = asyncify(|| self.0.dir_metadata()).await?; + let meta = asyncify(|| self.0.dir_metadata())?; Ok(Filestat { device_id: meta.dev(), inode: meta.ino(), @@ -185,9 +185,9 @@ impl WasiDir for Dir { follow_symlinks: bool, ) -> Result { let meta = if follow_symlinks { - asyncify(|| self.0.metadata(Path::new(path))).await? + asyncify(|| self.0.metadata(Path::new(path)))? } else { - asyncify(|| self.0.symlink_metadata(Path::new(path))).await? + asyncify(|| self.0.symlink_metadata(Path::new(path)))? }; Ok(Filestat { device_id: meta.dev(), @@ -213,8 +213,7 @@ impl WasiDir for Dir { asyncify(|| { self.0 .rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path)) - }) - .await?; + })?; Ok(()) } async fn hard_link( @@ -229,7 +228,7 @@ impl WasiDir for Dir { .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; let src_path = Path::new(src_path); let target_path = Path::new(target_path); - asyncify(|| self.0.hard_link(src_path, &target_dir.0, target_path)).await?; + asyncify(|| self.0.hard_link(src_path, &target_dir.0, target_path))?; Ok(()) } async fn set_times( @@ -253,8 +252,7 @@ impl WasiDir for Dir { convert_systimespec(mtime), ) } - }) - .await?; + })?; Ok(()) } } @@ -270,7 +268,7 @@ fn convert_systimespec(t: Option) -> Option Result { use unsafe_io::AsUnsafeFile; - asyncify(|| Ok(cap_std::fs::Metadata::from_file(&self.0.as_file_view())?)).await + asyncify(|| Ok(cap_std::fs::Metadata::from_file(&self.0.as_file_view())?)) } } @@ -86,7 +86,7 @@ impl WasiFile for File { Ok(filetype_from(&meta.file_type())) } async fn get_fdflags(&self) -> Result { - let fdflags = asyncify(|| self.0.get_fd_flags()).await?; + let fdflags = asyncify(|| self.0.get_fd_flags())?; Ok(from_sysif_fdflags(fdflags)) } async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { @@ -97,7 +97,7 @@ impl WasiFile for File { ) { return Err(Error::invalid_argument().context("cannot set DSYNC, SYNC, or RSYNC flag")); } - asyncify(move || self.0.set_fd_flags(to_sysif_fdflags(fdflags))).await?; + asyncify(move || self.0.set_fd_flags(to_sysif_fdflags(fdflags)))?; Ok(()) } async fn get_filestat(&self) -> Result { @@ -118,11 +118,11 @@ impl WasiFile for File { Ok(()) } async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { - asyncify(move || self.0.advise(offset, len, convert_advice(advice))).await?; + asyncify(move || self.0.advise(offset, len, convert_advice(advice)))?; Ok(()) } async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { - asyncify(move || self.0.allocate(offset, len)).await?; + asyncify(move || self.0.allocate(offset, len))?; Ok(()) } async fn set_times( @@ -133,8 +133,7 @@ impl WasiFile for File { asyncify(|| { self.0 .set_times(convert_systimespec(atime), convert_systimespec(mtime)) - }) - .await?; + })?; Ok(()) } async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { @@ -155,7 +154,7 @@ impl WasiFile for File { bufs: &mut [io::IoSliceMut<'a>], offset: u64, ) -> Result { - let n = asyncify(move || self.0.read_vectored_at(bufs, offset)).await?; + let n = asyncify(move || self.0.read_vectored_at(bufs, offset))?; Ok(n.try_into()?) } async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { @@ -169,7 +168,7 @@ impl WasiFile for File { bufs: &[io::IoSlice<'a>], offset: u64, ) -> Result { - let n = asyncify(move || self.0.write_vectored_at(bufs, offset)).await?; + let n = asyncify(move || self.0.write_vectored_at(bufs, offset))?; Ok(n.try_into()?) } async fn seek(&self, pos: std::io::SeekFrom) -> Result { @@ -177,12 +176,12 @@ impl WasiFile for File { Ok(self.0.inner().seek(pos).await?) } async fn peek(&self, buf: &mut [u8]) -> Result { - let n = asyncify(move || self.0.peek(buf)).await?; + let n = asyncify(move || self.0.peek(buf))?; Ok(n.try_into()?) } async fn num_ready_bytes(&self) -> Result { use unsafe_io::AsUnsafeFile; - asyncify(|| self.0.as_file_view().num_ready_bytes()).await + asyncify(|| self.0.as_file_view().num_ready_bytes()) } #[cfg(not(windows))] async fn readable(&mut self) -> Result<(), Error> { @@ -194,9 +193,18 @@ impl WasiFile for File { use tokio::io::{unix::AsyncFd, Interest}; use unsafe_io::os::posish::AsRawFd; let rawfd = self.0.as_raw_fd(); - let asyncfd = AsyncFd::with_interest(rawfd, Interest::READABLE)?; - let _ = asyncfd.readable().await?; - Ok(()) + match AsyncFd::with_interest(rawfd, Interest::READABLE) { + Ok(asyncfd) => { + let _ = asyncfd.readable().await?; + Ok(()) + } + Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { + // if e is EPERM, this file isnt supported by epoll because it is immediately + // available for reading: + Ok(()) + } + Err(e) => Err(e.into()), + } } #[cfg(windows)] async fn readable(&mut self) -> Result<(), Error> { @@ -214,9 +222,18 @@ impl WasiFile for File { use tokio::io::{unix::AsyncFd, Interest}; use unsafe_io::os::posish::AsRawFd; let rawfd = self.0.as_raw_fd(); - let asyncfd = AsyncFd::with_interest(rawfd, Interest::WRITABLE)?; - let _ = asyncfd.writable().await?; - Ok(()) + match AsyncFd::with_interest(rawfd, Interest::WRITABLE) { + Ok(asyncfd) => { + let _ = asyncfd.writable().await?; + Ok(()) + } + Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { + // if e is EPERM, this file isnt supported by epoll because it is immediately + // available for writing: + Ok(()) + } + Err(e) => Err(e.into()), + } } #[cfg(windows)] async fn writable(&mut self) -> Result<(), Error> { diff --git a/crates/wasi-common/tokio/src/lib.rs b/crates/wasi-common/tokio/src/lib.rs index a5ff5f8ca7..455e97e6b3 100644 --- a/crates/wasi-common/tokio/src/lib.rs +++ b/crates/wasi-common/tokio/src/lib.rs @@ -1,13 +1,17 @@ mod dir; mod file; -mod sched; +pub mod sched; +pub mod stdio; use std::cell::RefCell; use std::path::Path; use std::rc::Rc; -pub use wasi_cap_std_sync::{clocks_ctx, random_ctx, Dir}; +pub use wasi_cap_std_sync::{clocks_ctx, random_ctx}; use wasi_common::{Error, Table, WasiCtx}; +pub use dir::Dir; +pub use file::File; + use crate::sched::sched_ctx; pub struct WasiCtxBuilder(wasi_common::WasiCtxBuilder); @@ -81,10 +85,10 @@ impl WasiCtxBuilder { } pub fn preopened_dir( self, - dir: Dir, + dir: cap_std::fs::Dir, guest_path: impl AsRef, ) -> Result { - let dir = Box::new(crate::dir::Dir::from_cap_std(dir)); + let dir = Box::new(Dir::from_cap_std(dir)); Ok(WasiCtxBuilder(self.0.preopened_dir(dir, guest_path)?)) } pub fn build(self) -> Result { @@ -92,19 +96,10 @@ impl WasiCtxBuilder { } } -pub(crate) async fn asyncify<'a, F, T>(f: F) -> Result +pub(crate) fn asyncify<'a, F, T>(f: F) -> Result where F: FnOnce() -> Result + Send + 'a, T: Send + 'static, { - // spawn_blocking requires a 'static function, but since we await on the - // JoinHandle the lifetime of the spawn will be no longer than this function's body - let f: Box Result + Send + 'a> = Box::new(f); - let f = unsafe { - std::mem::transmute::<_, Box Result + Send + 'static>>(f) - }; - match tokio::task::spawn_blocking(|| f()).await { - Ok(res) => Ok(res?), - Err(_) => panic!("spawn_blocking died"), - } + tokio::task::block_in_place(f).map_err(Into::into) } diff --git a/crates/wasi-common/tokio/src/sched.rs b/crates/wasi-common/tokio/src/sched.rs index fd41f32996..1b97166641 100644 --- a/crates/wasi-common/tokio/src/sched.rs +++ b/crates/wasi-common/tokio/src/sched.rs @@ -1,12 +1,12 @@ #[cfg(unix)] mod unix; #[cfg(unix)] -use unix::poll_oneoff; +pub use unix::poll_oneoff; #[cfg(windows)] mod windows; #[cfg(windows)] -use windows::poll_oneoff; +pub use windows::poll_oneoff; use wasi_common::{ sched::{Duration, Poll, WasiSched}, @@ -18,7 +18,7 @@ pub fn sched_ctx() -> Box { #[wiggle::async_trait] impl WasiSched for AsyncSched { - async fn poll_oneoff<'a>(&self, poll: &'_ Poll<'a>) -> Result<(), Error> { + async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { poll_oneoff(poll).await } async fn sched_yield(&self) -> Result<(), Error> { diff --git a/crates/wasi-common/tokio/src/sched/unix.rs b/crates/wasi-common/tokio/src/sched/unix.rs index f286b0d29a..614a5f16de 100644 --- a/crates/wasi-common/tokio/src/sched/unix.rs +++ b/crates/wasi-common/tokio/src/sched/unix.rs @@ -1,48 +1,87 @@ -use cap_std::time::Duration; -use std::convert::TryInto; use std::future::Future; -use std::ops::Deref; use std::pin::Pin; -use std::task::Context; +use std::task::{Context, Poll as FPoll}; use wasi_common::{ - file::WasiFile, sched::{ subscription::{RwEventFlags, Subscription}, Poll, }, - Error, ErrorExt, + Context as _, Error, }; -pub async fn poll_oneoff<'a>(poll: &'_ Poll<'a>) -> Result<(), Error> { +struct FirstReady<'a, T>(Vec + 'a>>>); + +impl<'a, T> FirstReady<'a, T> { + fn new() -> Self { + FirstReady(Vec::new()) + } + fn push(&mut self, f: impl Future + 'a) { + self.0.push(Box::pin(f)); + } +} + +impl<'a, T> Future for FirstReady<'a, T> { + type Output = T; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> FPoll { + let mut result = FPoll::Pending; + for f in self.as_mut().0.iter_mut() { + match f.as_mut().poll(cx) { + FPoll::Ready(r) => match result { + // First ready gets to set the result. But, continue the loop so all futures + // get the opportunity to become ready. + FPoll::Pending => { + result = FPoll::Ready(r); + } + _ => {} + }, + _ => continue, + } + } + return result; + } +} + +pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { if poll.is_empty() { return Ok(()); } - let mut futures: Vec>>>> = Vec::new(); - let timeout = poll.earliest_clock_deadline(); + + let duration = poll + .earliest_clock_deadline() + .map(|sub| sub.duration_until()); + + let mut futures = FirstReady::new(); for s in poll.rw_subscriptions() { match s { Subscription::Read(f) => { - futures.push(Box::pin(async move { - f.file()?.readable().await?; - f.complete(f.file()?.num_ready_bytes().await?, RwEventFlags::empty()); - Ok(()) - })); + futures.push(async move { + f.file.readable().await.context("readable future")?; + f.complete( + f.file + .num_ready_bytes() + .await + .context("read num_ready_bytes")?, + RwEventFlags::empty(), + ); + Ok::<(), Error>(()) + }); } Subscription::Write(f) => { - futures.push(Box::pin(async move { - f.file()?.writable().await?; + futures.push(async move { + f.file.writable().await.context("writable future")?; f.complete(0, RwEventFlags::empty()); Ok(()) - })); + }); } Subscription::MonotonicClock { .. } => unreachable!(), } } - - // Incorrect, but lets get the type errors fixed before we write the right multiplexer here: - for f in futures { - f.await?; + if let Some(Some(remaining_duration)) = duration { + tokio::time::timeout(remaining_duration, futures).await??; + } else { + futures.await?; } + Ok(()) } diff --git a/crates/wasi-common/tokio/src/stdio.rs b/crates/wasi-common/tokio/src/stdio.rs new file mode 100644 index 0000000000..1dd7431605 --- /dev/null +++ b/crates/wasi-common/tokio/src/stdio.rs @@ -0,0 +1,295 @@ +use crate::file::convert_systimespec; +use fs_set_times::SetTimes; +use std::any::Any; +use std::convert::TryInto; +use std::io; +use std::io::{Read, Write}; + +use unsafe_io::AsUnsafeFile; +use wasi_common::{ + file::{Advice, FdFlags, FileType, Filestat, WasiFile}, + Error, ErrorExt, +}; + +mod internal { + #[cfg(unix)] + use std::os::unix::io::{AsRawFd, RawFd}; + #[cfg(windows)] + use std::os::windows::io::{AsRawHandle, RawHandle}; + use unsafe_io::OwnsRaw; + + pub(super) struct TokioStdin(tokio::io::Stdin); + impl TokioStdin { + pub fn new() -> Self { + TokioStdin(tokio::io::stdin()) + } + } + + #[cfg(windows)] + impl AsRawHandle for TokioStdin { + fn as_raw_handle(&self) -> RawHandle { + self.0.as_raw_handle() + } + } + #[cfg(unix)] + impl AsRawFd for TokioStdin { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } + } + unsafe impl OwnsRaw for TokioStdin {} + + pub(super) struct TokioStdout(tokio::io::Stdout); + impl TokioStdout { + pub fn new() -> Self { + TokioStdout(tokio::io::stdout()) + } + } + + #[cfg(windows)] + impl AsRawHandle for TokioStdout { + fn as_raw_handle(&self) -> RawHandle { + self.0.as_raw_handle() + } + } + #[cfg(unix)] + impl AsRawFd for TokioStdout { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } + } + unsafe impl OwnsRaw for TokioStdout {} + + pub(super) struct TokioStderr(tokio::io::Stderr); + impl TokioStderr { + pub fn new() -> Self { + TokioStderr(tokio::io::stderr()) + } + } + #[cfg(windows)] + impl AsRawHandle for TokioStderr { + fn as_raw_handle(&self) -> RawHandle { + self.0.as_raw_handle() + } + } + #[cfg(unix)] + impl AsRawFd for TokioStderr { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } + } + unsafe impl OwnsRaw for TokioStderr {} +} + +pub struct Stdin(internal::TokioStdin); + +pub fn stdin() -> Stdin { + Stdin(internal::TokioStdin::new()) +} + +#[wiggle::async_trait] +impl WasiFile for Stdin { + fn as_any(&self) -> &dyn Any { + self + } + async fn datasync(&self) -> Result<(), Error> { + Ok(()) + } + async fn sync(&self) -> Result<(), Error> { + Ok(()) + } + async fn get_filetype(&self) -> Result { + Ok(FileType::Unknown) + } + async fn get_fdflags(&self) -> Result { + Ok(FdFlags::empty()) + } + async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { + Err(Error::badf()) + } + async fn get_filestat(&self) -> Result { + let meta = self.0.as_file_view().metadata()?; + Ok(Filestat { + device_id: 0, + inode: 0, + filetype: self.get_filetype().await?, + nlink: 0, + size: meta.len(), + atim: meta.accessed().ok(), + mtim: meta.modified().ok(), + ctim: meta.created().ok(), + }) + } + async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { + Err(Error::badf()) + } + async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { + Err(Error::badf()) + } + async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> { + Err(Error::badf()) + } + async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { + let n = self.0.as_file_view().read_vectored(bufs)?; + Ok(n.try_into().map_err(|_| Error::range())?) + } + async fn read_vectored_at<'a>( + &self, + _bufs: &mut [io::IoSliceMut<'a>], + _offset: u64, + ) -> Result { + Err(Error::seek_pipe()) + } + async fn write_vectored<'a>(&self, _bufs: &[io::IoSlice<'a>]) -> Result { + Err(Error::badf()) + } + async fn write_vectored_at<'a>( + &self, + _bufs: &[io::IoSlice<'a>], + _offset: u64, + ) -> Result { + Err(Error::badf()) + } + async fn seek(&self, _pos: std::io::SeekFrom) -> Result { + Err(Error::seek_pipe()) + } + async fn peek(&self, _buf: &mut [u8]) -> Result { + Err(Error::seek_pipe()) + } + async fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + self.0 + .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; + Ok(()) + } + + #[cfg(not(windows))] + async fn num_ready_bytes(&self) -> Result { + Ok(posish::io::fionread(&self.0)?) + } + #[cfg(windows)] + async fn num_ready_bytes(&self) -> Result { + // conservative but correct is the best we can do + Ok(0) + } + + async fn readable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } + async fn writable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } +} + +macro_rules! wasi_file_write_impl { + ($ty:ty) => { + #[wiggle::async_trait] + impl WasiFile for $ty { + fn as_any(&self) -> &dyn Any { + self + } + async fn datasync(&self) -> Result<(), Error> { + Ok(()) + } + async fn sync(&self) -> Result<(), Error> { + Ok(()) + } + async fn get_filetype(&self) -> Result { + Ok(FileType::Unknown) + } + async fn get_fdflags(&self) -> Result { + Ok(FdFlags::APPEND) + } + async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { + Err(Error::badf()) + } + async fn get_filestat(&self) -> Result { + let meta = self.0.as_file_view().metadata()?; + Ok(Filestat { + device_id: 0, + inode: 0, + filetype: self.get_filetype().await?, + nlink: 0, + size: meta.len(), + atim: meta.accessed().ok(), + mtim: meta.modified().ok(), + ctim: meta.created().ok(), + }) + } + async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { + Err(Error::badf()) + } + async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { + Err(Error::badf()) + } + async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> { + Err(Error::badf()) + } + async fn read_vectored<'a>( + &self, + _bufs: &mut [io::IoSliceMut<'a>], + ) -> Result { + Err(Error::badf()) + } + async fn read_vectored_at<'a>( + &self, + _bufs: &mut [io::IoSliceMut<'a>], + _offset: u64, + ) -> Result { + Err(Error::badf()) + } + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { + let n = self.0.as_file_view().write_vectored(bufs)?; + Ok(n.try_into().map_err(|c| Error::range().context(c))?) + } + async fn write_vectored_at<'a>( + &self, + _bufs: &[io::IoSlice<'a>], + _offset: u64, + ) -> Result { + Err(Error::seek_pipe()) + } + async fn seek(&self, _pos: std::io::SeekFrom) -> Result { + Err(Error::seek_pipe()) + } + async fn peek(&self, _buf: &mut [u8]) -> Result { + Err(Error::badf()) + } + async fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + self.0 + .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; + Ok(()) + } + async fn num_ready_bytes(&self) -> Result { + Ok(0) + } + async fn readable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } + async fn writable(&mut self) -> Result<(), Error> { + Err(Error::badf()) + } + } + }; +} + +pub struct Stdout(internal::TokioStdout); + +pub fn stdout() -> Stdout { + Stdout(internal::TokioStdout::new()) +} +wasi_file_write_impl!(Stdout); + +pub struct Stderr(internal::TokioStderr); + +pub fn stderr() -> Stderr { + Stderr(internal::TokioStderr::new()) +} +wasi_file_write_impl!(Stderr); diff --git a/crates/wasi-common/tokio/tests/poll_oneoff.rs b/crates/wasi-common/tokio/tests/poll_oneoff.rs new file mode 100644 index 0000000000..b0e220c86b --- /dev/null +++ b/crates/wasi-common/tokio/tests/poll_oneoff.rs @@ -0,0 +1,27 @@ +use anyhow::{Context, Error}; +use std::ops::Deref; +use wasi_common::{ + file::{FdFlags, OFlags}, + sched::{Poll, Userdata}, + WasiDir, WasiFile, +}; +use wasi_tokio::{sched::poll_oneoff, Dir, File}; + +#[tokio::test(flavor = "multi_thread")] +async fn main() -> Result<(), Error> { + let workspace = unsafe { cap_tempfile::tempdir().expect("create tempdir") }; + workspace.create_dir("d").context("create dir")?; + let d = workspace.open_dir("d").context("open dir")?; + let d = Dir::from_cap_std(d); + + let mut readable_f = d + .open_file(false, "f", OFlags::CREATE, true, false, FdFlags::empty()) + .await + .context("create readable file")?; + + let mut poll = Poll::new(); + poll.subscribe_read(&mut *readable_f, Userdata::from(123)); + let poll_events = poll_oneoff(&mut poll).await?; + + Ok(()) +} From 3df9cddf1028f56db291154eff958190c7934d0a Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 3 May 2021 11:58:12 -0700 Subject: [PATCH 40/72] better unit test --- crates/wasi-common/src/sched.rs | 4 +- crates/wasi-common/tokio/tests/poll_oneoff.rs | 52 ++++++++++++++++--- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/crates/wasi-common/src/sched.rs b/crates/wasi-common/src/sched.rs index d8be4deb99..fecac2c5ca 100644 --- a/crates/wasi-common/src/sched.rs +++ b/crates/wasi-common/src/sched.rs @@ -5,7 +5,9 @@ use cap_std::time::Instant; pub mod subscription; pub use cap_std::time::Duration; -use subscription::{MonotonicClockSubscription, RwSubscription, Subscription, SubscriptionResult}; +pub use subscription::{ + MonotonicClockSubscription, RwEventFlags, RwSubscription, Subscription, SubscriptionResult, +}; #[wiggle::async_trait] pub trait WasiSched { diff --git a/crates/wasi-common/tokio/tests/poll_oneoff.rs b/crates/wasi-common/tokio/tests/poll_oneoff.rs index b0e220c86b..6bd42c39ce 100644 --- a/crates/wasi-common/tokio/tests/poll_oneoff.rs +++ b/crates/wasi-common/tokio/tests/poll_oneoff.rs @@ -1,14 +1,13 @@ use anyhow::{Context, Error}; -use std::ops::Deref; use wasi_common::{ file::{FdFlags, OFlags}, - sched::{Poll, Userdata}, - WasiDir, WasiFile, + sched::{Poll, RwEventFlags, SubscriptionResult, Userdata}, + WasiDir, }; -use wasi_tokio::{sched::poll_oneoff, Dir, File}; +use wasi_tokio::{sched::poll_oneoff, Dir}; #[tokio::test(flavor = "multi_thread")] -async fn main() -> Result<(), Error> { +async fn empty_file_readable() -> Result<(), Error> { let workspace = unsafe { cap_tempfile::tempdir().expect("create tempdir") }; workspace.create_dir("d").context("create dir")?; let d = workspace.open_dir("d").context("open dir")?; @@ -21,7 +20,48 @@ async fn main() -> Result<(), Error> { let mut poll = Poll::new(); poll.subscribe_read(&mut *readable_f, Userdata::from(123)); - let poll_events = poll_oneoff(&mut poll).await?; + poll_oneoff(&mut poll).await?; + + let events = poll.results(); + + assert_eq!(events.len(), 1); + match events[0] { + (SubscriptionResult::Read(Ok((0, flags))), ud) => { + assert_eq!(flags, RwEventFlags::empty()); + assert_eq!(ud, Userdata::from(123)); + } + _ => panic!(""), + } + + Ok(()) +} + +#[tokio::test(flavor = "multi_thread")] +async fn empty_file_writable() -> Result<(), Error> { + let workspace = unsafe { cap_tempfile::tempdir().expect("create tempdir") }; + workspace.create_dir("d").context("create dir")?; + let d = workspace.open_dir("d").context("open dir")?; + let d = Dir::from_cap_std(d); + + let mut writable_f = d + .open_file(false, "f", OFlags::CREATE, true, true, FdFlags::empty()) + .await + .context("create writable file")?; + + let mut poll = Poll::new(); + poll.subscribe_write(&mut *writable_f, Userdata::from(123)); + poll_oneoff(&mut poll).await?; + + let events = poll.results(); + + assert_eq!(events.len(), 1); + match events[0] { + (SubscriptionResult::Write(Ok((0, flags))), ud) => { + assert_eq!(flags, RwEventFlags::empty()); + assert_eq!(ud, Userdata::from(123)); + } + _ => panic!(""), + } Ok(()) } From 5ab8346a05e47a2bf730d12e26c73d378e06d94e Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 3 May 2021 12:23:14 -0700 Subject: [PATCH 41/72] fix windows sync scheduler --- .../cap-std-sync/src/sched/windows.rs | 94 +++++++++---------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/crates/wasi-common/cap-std-sync/src/sched/windows.rs b/crates/wasi-common/cap-std-sync/src/sched/windows.rs index 8d11df4663..a98e558a7b 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/windows.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/windows.rs @@ -23,27 +23,47 @@ impl SyncSched { #[wiggle::async_trait] impl WasiSched for SyncSched { - async fn poll_oneoff<'a>(&self, poll: &'_ Poll<'a>) -> Result<(), Error> { + async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { if poll.is_empty() { return Ok(()); } let mut ready = false; - let timeout = poll.earliest_clock_deadline(); + let waitmode = if let Some(t) = poll.earliest_clock_deadline() { + if let Some(duration) = t.duration_until() { + WaitMode::Timeout(duration) + } else { + WaitMode::Immediate + } + } else { + if ready { + WaitMode::Immediate + } else { + WaitMode::Infinite + } + }; let mut stdin_read_subs = Vec::new(); - let mut immediate_subs = Vec::new(); + let mut immediate_reads = Vec::new(); + let mut immediate_writes = Vec::new(); for s in poll.rw_subscriptions() { match s { - Subscription::Read(r) if r.file.as_any().is::() => { - stdin_read_subs.push(r); - } - Subscription::Read(rw) | Subscription::Write(rw) => { - if wasi_file_raw_handle(rw.file.deref()).is_some() { - immediate_subs.push(s); + Subscription::Read(r) => { + if r.file.as_any().is::() { + stdin_read_subs.push(r); + } else if wasi_file_raw_handle(r.file.deref()).is_some() { + immediate_reads.push(r); } else { return Err(Error::invalid_argument() - .context("read/write subscription fd downcast failed")); + .context("read subscription fd downcast failed")); + } + } + Subscription::Write(w) => { + if wasi_file_raw_handle(w.file.deref()).is_some() { + immediate_writes.push(w); + } else { + return Err(Error::invalid_argument() + .context("write subscription fd downcast failed")); } } Subscription::MonotonicClock { .. } => unreachable!(), @@ -51,19 +71,6 @@ impl WasiSched for SyncSched { } if !stdin_read_subs.is_empty() { - let waitmode = if let Some(t) = timeout { - if let Some(duration) = t.duration_until() { - WaitMode::Timeout(duration) - } else { - WaitMode::Immediate - } - } else { - if ready { - WaitMode::Immediate - } else { - WaitMode::Infinite - } - }; let state = STDIN_POLL .lock() .map_err(|_| Error::trap("failed to take lock of STDIN_POLL"))? @@ -89,37 +96,27 @@ impl WasiSched for SyncSched { } } } - for sub in immediate_subs { - match sub { - Subscription::Read(r) => { - // XXX This doesnt strictly preserve the behavior in the earlier - // implementation, which would always do complete(0) for reads from - // stdout/err. - match r.file.num_ready_bytes().await { - Ok(ready_bytes) => { - r.complete(ready_bytes, RwEventFlags::empty()); - ready = true; - } - Err(e) => { - r.error(e); - ready = true; - } - } - } - Subscription::Write(w) => { - // Everything is always ready for writing, apparently? - w.complete(0, RwEventFlags::empty()); + for r in immediate_reads { + match r.file.num_ready_bytes().await { + Ok(ready_bytes) => { + r.complete(ready_bytes, RwEventFlags::empty()); + ready = true; + } + Err(e) => { + r.error(e); ready = true; } - Subscription::MonotonicClock { .. } => unreachable!(), } } + for w in immediate_writes { + // Everything is always ready for writing, apparently? + w.complete(0, RwEventFlags::empty()); + ready = true; + } if !ready { - if let Some(t) = timeout { - if let Some(duration) = t.duration_until() { - thread::sleep(duration); - } + if let WaitMode::Timeout(duration) = waitmode { + thread::sleep(duration); } } @@ -173,6 +170,7 @@ enum PollState { Error(std::io::Error), } +#[derive(Copy, Clone)] enum WaitMode { Timeout(Duration), Infinite, From 686d8c22f978102228c3e2809a088d3ae6a35ff4 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 4 May 2021 11:18:20 -0700 Subject: [PATCH 42/72] fix test harness --- crates/test-programs/tests/wasm_tests/runtime/tokio.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs index 11cf28336c..587ee27f40 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs @@ -119,6 +119,7 @@ pub fn instantiate_inherit_stdio( builder = builder.preopened_dir(preopen_dir, ".")?; } + store.out_of_fuel_async_yield(u32::MAX, 10000); Wasi::set_context(&store, builder.build()?) .map_err(|_| anyhow::anyhow!("wasi set_context failed"))?; From f76fe8b7640134e11fb50bf5812d9dbbefb425ab Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 4 May 2021 11:29:02 -0700 Subject: [PATCH 43/72] rewrite wasi-tokio as just an task::block_in_place wrapper on cap-std-sync --- Cargo.lock | 4 +- crates/wasi-common/cap-std-sync/Cargo.toml | 2 +- crates/wasi-common/cap-std-sync/src/dir.rs | 68 ++- crates/wasi-common/src/dir.rs | 4 +- crates/wasi-common/src/file.rs | 2 +- crates/wasi-common/src/pipe.rs | 4 +- crates/wasi-common/tokio/Cargo.toml | 3 +- crates/wasi-common/tokio/src/dir.rs | 215 ++------ crates/wasi-common/tokio/src/file.rs | 467 ++++++------------ crates/wasi-common/tokio/src/lib.rs | 18 +- crates/wasi-common/tokio/src/sched/unix.rs | 5 +- crates/wasi-common/tokio/src/sched/windows.rs | 101 ++-- crates/wasi-common/tokio/src/stdio.rs | 296 +---------- 13 files changed, 319 insertions(+), 870 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92c5e7611a..6ba3fe8586 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3039,9 +3039,9 @@ dependencies = [ [[package]] name = "unsafe-io" -version = "0.6.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0301dd0f2c21baed606faa2717fbfbb1a68b7e289ea29b40bc21a16f5ae9f5aa" +checksum = "fe39acfe60d3754452ea6881613c3240100b23ffd94a627c138863f8cd314b1b" dependencies = [ "rustc_version", "winapi", diff --git a/crates/wasi-common/cap-std-sync/Cargo.toml b/crates/wasi-common/cap-std-sync/Cargo.toml index e48ede5400..aa85bf9d55 100644 --- a/crates/wasi-common/cap-std-sync/Cargo.toml +++ b/crates/wasi-common/cap-std-sync/Cargo.toml @@ -20,7 +20,7 @@ cap-fs-ext = "0.13.7" cap-time-ext = "0.13.7" cap-rand = "0.13.2" fs-set-times = "0.3.1" -unsafe-io = "0.6.2" +unsafe-io = "0.6.5" system-interface = { version = "0.6.3", features = ["cap_std_impls"] } tracing = "0.1.19" bitflags = "1.2" diff --git a/crates/wasi-common/cap-std-sync/src/dir.rs b/crates/wasi-common/cap-std-sync/src/dir.rs index 13e8391452..c52f5d8e1b 100644 --- a/crates/wasi-common/cap-std-sync/src/dir.rs +++ b/crates/wasi-common/cap-std-sync/src/dir.rs @@ -15,14 +15,8 @@ impl Dir { pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self { Dir(dir) } -} -#[wiggle::async_trait] -impl WasiDir for Dir { - fn as_any(&self) -> &dyn Any { - self - } - async fn open_file( + pub fn open_file_( &self, symlink_follow: bool, path: &str, @@ -30,7 +24,7 @@ impl WasiDir for Dir { read: bool, write: bool, fdflags: FdFlags, - ) -> Result, Error> { + ) -> Result { use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt}; let mut opts = cap_std::fs::OpenOptions::new(); @@ -82,16 +76,57 @@ impl WasiDir for Dir { if fdflags.contains(wasi_common::file::FdFlags::NONBLOCK) { f.set_fd_flags(system_interface::fs::FdFlags::NONBLOCK)?; } - Ok(Box::new(File::from_cap_std(f))) + Ok(File::from_cap_std(f)) } - async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { + pub fn open_dir_(&self, symlink_follow: bool, path: &str) -> Result { let d = if symlink_follow { self.0.open_dir(Path::new(path))? } else { self.0.open_dir_nofollow(Path::new(path))? }; - Ok(Box::new(Dir::from_cap_std(d))) + Ok(Dir::from_cap_std(d)) + } + + pub fn rename_(&self, src_path: &str, dest_dir: &Self, dest_path: &str) -> Result<(), Error> { + self.0 + .rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path))?; + Ok(()) + } + pub fn hard_link_( + &self, + src_path: &str, + target_dir: &Self, + target_path: &str, + ) -> Result<(), Error> { + let src_path = Path::new(src_path); + let target_path = Path::new(target_path); + self.0.hard_link(src_path, &target_dir.0, target_path)?; + Ok(()) + } +} + +#[wiggle::async_trait] +impl WasiDir for Dir { + fn as_any(&self) -> &dyn Any { + self + } + async fn open_file( + &self, + symlink_follow: bool, + path: &str, + oflags: OFlags, + read: bool, + write: bool, + fdflags: FdFlags, + ) -> Result, Error> { + let f = self.open_file_(symlink_follow, path, oflags, read, write, fdflags)?; + Ok(Box::new(f)) + } + + async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { + let d = self.open_dir_(symlink_follow, path)?; + Ok(Box::new(d)) } async fn create_dir(&self, path: &str) -> Result<(), Error> { @@ -101,7 +136,7 @@ impl WasiDir for Dir { async fn readdir( &self, cursor: ReaddirCursor, - ) -> Result>>, Error> { + ) -> Result> + Send>, Error> { // cap_std's read_dir does not include . and .., we should prepend these. // Why does the Ok contain a tuple? We can't construct a cap_std::fs::DirEntry, and we don't // have enough info to make a ReaddirEntity yet. @@ -208,9 +243,7 @@ impl WasiDir for Dir { .as_any() .downcast_ref::() .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; - self.0 - .rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path))?; - Ok(()) + self.rename_(src_path, dest_dir, dest_path) } async fn hard_link( &self, @@ -222,10 +255,7 @@ impl WasiDir for Dir { .as_any() .downcast_ref::() .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; - let src_path = Path::new(src_path); - let target_path = Path::new(target_path); - self.0.hard_link(src_path, &target_dir.0, target_path)?; - Ok(()) + self.hard_link_(src_path, target_dir, target_path) } async fn set_times( &self, diff --git a/crates/wasi-common/src/dir.rs b/crates/wasi-common/src/dir.rs index df8850c29f..9c49c6c8aa 100644 --- a/crates/wasi-common/src/dir.rs +++ b/crates/wasi-common/src/dir.rs @@ -7,7 +7,7 @@ use std::ops::Deref; use std::path::PathBuf; #[wiggle::async_trait] -pub trait WasiDir { +pub trait WasiDir: Send + Sync { fn as_any(&self) -> &dyn Any; async fn open_file( &self, @@ -24,7 +24,7 @@ pub trait WasiDir { async fn readdir( &self, cursor: ReaddirCursor, - ) -> Result>>, Error>; + ) -> Result> + Send>, Error>; async fn symlink(&self, old_path: &str, new_path: &str) -> Result<(), Error>; async fn remove_dir(&self, path: &str) -> Result<(), Error>; async fn unlink_file(&self, path: &str) -> Result<(), Error>; diff --git a/crates/wasi-common/src/file.rs b/crates/wasi-common/src/file.rs index dede0c0dc3..c718b7ad25 100644 --- a/crates/wasi-common/src/file.rs +++ b/crates/wasi-common/src/file.rs @@ -5,7 +5,7 @@ use std::cell::{Ref, RefMut}; use std::ops::{Deref, DerefMut}; #[wiggle::async_trait] -pub trait WasiFile { +pub trait WasiFile: Send { fn as_any(&self) -> &dyn Any; async fn datasync(&self) -> Result<(), Error>; // write op async fn sync(&self) -> Result<(), Error>; // file op diff --git a/crates/wasi-common/src/pipe.rs b/crates/wasi-common/src/pipe.rs index 192fe5d10a..2f39f5cb9c 100644 --- a/crates/wasi-common/src/pipe.rs +++ b/crates/wasi-common/src/pipe.rs @@ -106,7 +106,7 @@ impl From<&str> for ReadPipe> { } #[wiggle::async_trait] -impl WasiFile for ReadPipe { +impl WasiFile for ReadPipe { fn as_any(&self) -> &dyn Any { self } @@ -265,7 +265,7 @@ impl WritePipe>> { } #[wiggle::async_trait] -impl WasiFile for WritePipe { +impl WasiFile for WritePipe { fn as_any(&self) -> &dyn Any { self } diff --git a/crates/wasi-common/tokio/Cargo.toml b/crates/wasi-common/tokio/Cargo.toml index 49e79f3d75..4e65c63d56 100644 --- a/crates/wasi-common/tokio/Cargo.toml +++ b/crates/wasi-common/tokio/Cargo.toml @@ -20,10 +20,11 @@ cap-std = "0.13.7" cap-fs-ext = "0.13.7" cap-time-ext = "0.13.7" fs-set-times = "0.3.1" -unsafe-io = "0.6.2" +unsafe-io = "0.6.5" system-interface = { version = "0.6.3", features = ["cap_std_impls"] } tracing = "0.1.19" bitflags = "1.2" +anyhow = "1" [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/crates/wasi-common/tokio/src/dir.rs b/crates/wasi-common/tokio/src/dir.rs index f3d6623b62..5ba7d985ed 100644 --- a/crates/wasi-common/tokio/src/dir.rs +++ b/crates/wasi-common/tokio/src/dir.rs @@ -1,21 +1,17 @@ -use crate::{ - asyncify, - file::{filetype_from, File}, -}; -use cap_fs_ext::{DirEntryExt, DirExt, MetadataExt, SystemTimeSpec}; +use crate::{asyncify, file::File}; use std::any::Any; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use wasi_common::{ dir::{ReaddirCursor, ReaddirEntity, WasiDir}, - file::{FdFlags, FileType, Filestat, OFlags, WasiFile}, + file::{FdFlags, Filestat, OFlags, WasiFile}, Error, ErrorExt, }; -pub struct Dir(cap_std::fs::Dir); +pub struct Dir(wasi_cap_std_sync::dir::Dir); impl Dir { pub fn from_cap_std(dir: cap_std::fs::Dir) -> Self { - Dir(dir) + Dir(wasi_cap_std_sync::dir::Dir::from_cap_std(dir)) } } @@ -33,172 +29,59 @@ impl WasiDir for Dir { write: bool, fdflags: FdFlags, ) -> Result, Error> { - use cap_fs_ext::{FollowSymlinks, OpenOptionsFollowExt}; - use wasi_common::file::FdFlags; - - let mut opts = cap_std::fs::OpenOptions::new(); - - if oflags.contains(OFlags::CREATE | OFlags::EXCLUSIVE) { - opts.create_new(true); - opts.write(true); - } else if oflags.contains(OFlags::CREATE) { - opts.create(true); - opts.write(true); - } - if oflags.contains(OFlags::TRUNCATE) { - opts.truncate(true); - } - if read { - opts.read(true); - } - if write { - opts.write(true); - } else { - // If not opened write, open read. This way the OS lets us open the file. - // If FileCaps::READ is not set, read calls will be rejected at the - // get_cap check. - opts.read(true); - } - if fdflags.contains(FdFlags::APPEND) { - opts.append(true); - } - - if symlink_follow { - opts.follow(FollowSymlinks::Yes); - } else { - opts.follow(FollowSymlinks::No); - } - // the DSYNC, SYNC, and RSYNC flags are ignored! We do not - // have support for them in cap-std yet. - // ideally OpenOptions would just support this though: - // https://github.com/bytecodealliance/cap-std/issues/146 - if fdflags.intersects(FdFlags::DSYNC | FdFlags::SYNC | FdFlags::RSYNC) { - return Err(Error::not_supported().context("SYNC family of FdFlags")); - } - - let f = asyncify(move || self.0.open_with(Path::new(path), &opts))?; - let mut f = File::from_cap_std(f); - // NONBLOCK does not have an OpenOption either, but we can patch that on with set_fd_flags: - if fdflags.contains(FdFlags::NONBLOCK) { - f.set_fdflags(FdFlags::NONBLOCK).await?; - } - Ok(Box::new(f)) + let f = asyncify(move || async move { + self.0 + .open_file_(symlink_follow, path, oflags, read, write, fdflags) + })?; + Ok(Box::new(File::from_inner(f))) } async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { - let path = unsafe { std::mem::transmute::<_, &'static str>(path) }; - let d = if symlink_follow { - asyncify(move || self.0.open_dir(Path::new(path)))? - } else { - asyncify(move || self.0.open_dir_nofollow(Path::new(path)))? - }; - Ok(Box::new(Dir::from_cap_std(d))) + let d = asyncify(move || async move { self.0.open_dir_(symlink_follow, path) })?; + Ok(Box::new(Dir(d))) } async fn create_dir(&self, path: &str) -> Result<(), Error> { - asyncify(|| self.0.create_dir(Path::new(path)))?; - Ok(()) + asyncify(|| self.0.create_dir(path)) } async fn readdir( &self, cursor: ReaddirCursor, - ) -> Result>>, Error> { - // cap_std's read_dir does not include . and .., we should prepend these. - // Why does the Ok contain a tuple? We can't construct a cap_std::fs::DirEntry, and we don't - // have enough info to make a ReaddirEntity yet. - let dir_meta = asyncify(|| self.0.dir_metadata())?; - let rd = vec![ - { - let name = ".".to_owned(); - Ok((FileType::Directory, dir_meta.ino(), name)) - }, - { - let name = "..".to_owned(); - Ok((FileType::Directory, dir_meta.ino(), name)) - }, - ] - .into_iter() - .chain( - // Now process the `DirEntry`s: - self.0.entries()?.map(|entry| { - let entry = entry?; - // XXX full_metadata blocks, but we arent in an async iterator: - let meta = entry.full_metadata()?; - let inode = meta.ino(); - let filetype = filetype_from(&meta.file_type()); - let name = entry - .file_name() - .into_string() - .map_err(|_| Error::illegal_byte_sequence().context("filename"))?; - Ok((filetype, inode, name)) - }), - ) - // Enumeration of the iterator makes it possible to define the ReaddirCursor - .enumerate() - .map(|(ix, r)| match r { - Ok((filetype, inode, name)) => Ok(ReaddirEntity { - next: ReaddirCursor::from(ix as u64 + 1), - filetype, - inode, - name, - }), - Err(e) => Err(e), - }) - .skip(u64::from(cursor) as usize); + ) -> Result> + Send>, Error> { + struct I(Box> + Send>); + impl Iterator for I { + type Item = Result; + fn next(&mut self) -> Option { + tokio::task::block_in_place(move || self.0.next()) + } + } - Ok(Box::new(rd)) + let inner = asyncify(move || self.0.readdir(cursor))?; + Ok(Box::new(I(inner))) } async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { - asyncify(|| self.0.symlink(src_path, dest_path))?; - Ok(()) + asyncify(move || self.0.symlink(src_path, dest_path)) } async fn remove_dir(&self, path: &str) -> Result<(), Error> { - asyncify(|| self.0.remove_dir(Path::new(path)))?; - Ok(()) + asyncify(move || self.0.remove_dir(path)) } async fn unlink_file(&self, path: &str) -> Result<(), Error> { - asyncify(|| self.0.remove_file_or_symlink(Path::new(path)))?; - Ok(()) + asyncify(move || self.0.unlink_file(path)) } async fn read_link(&self, path: &str) -> Result { - let link = asyncify(|| self.0.read_link(Path::new(path)))?; - Ok(link) + asyncify(move || self.0.read_link(path)) } async fn get_filestat(&self) -> Result { - let meta = asyncify(|| self.0.dir_metadata())?; - Ok(Filestat { - device_id: meta.dev(), - inode: meta.ino(), - filetype: filetype_from(&meta.file_type()), - nlink: meta.nlink(), - size: meta.len(), - atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), - mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), - ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), - }) + asyncify(|| self.0.get_filestat()) } async fn get_path_filestat( &self, path: &str, follow_symlinks: bool, ) -> Result { - let meta = if follow_symlinks { - asyncify(|| self.0.metadata(Path::new(path)))? - } else { - asyncify(|| self.0.symlink_metadata(Path::new(path)))? - }; - Ok(Filestat { - device_id: meta.dev(), - inode: meta.ino(), - filetype: filetype_from(&meta.file_type()), - nlink: meta.nlink(), - size: meta.len(), - atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), - mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), - ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), - }) + asyncify(move || self.0.get_path_filestat(path, follow_symlinks)) } async fn rename( &self, @@ -209,12 +92,8 @@ impl WasiDir for Dir { let dest_dir = dest_dir .as_any() .downcast_ref::() - .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; - asyncify(|| { - self.0 - .rename(Path::new(src_path), &dest_dir.0, Path::new(dest_path)) - })?; - Ok(()) + .ok_or(Error::badf().context("failed downcast to tokio Dir"))?; + asyncify(move || async move { self.0.rename_(src_path, &dest_dir.0, dest_path) }) } async fn hard_link( &self, @@ -225,11 +104,8 @@ impl WasiDir for Dir { let target_dir = target_dir .as_any() .downcast_ref::() - .ok_or(Error::badf().context("failed downcast to cap-std Dir"))?; - let src_path = Path::new(src_path); - let target_path = Path::new(target_path); - asyncify(|| self.0.hard_link(src_path, &target_dir.0, target_path))?; - Ok(()) + .ok_or(Error::badf().context("failed downcast to tokio Dir"))?; + asyncify(move || async move { self.0.hard_link_(src_path, &target_dir.0, target_path) }) } async fn set_times( &self, @@ -238,30 +114,7 @@ impl WasiDir for Dir { mtime: Option, follow_symlinks: bool, ) -> Result<(), Error> { - asyncify(|| { - if follow_symlinks { - self.0.set_times( - Path::new(path), - convert_systimespec(atime), - convert_systimespec(mtime), - ) - } else { - self.0.set_symlink_times( - Path::new(path), - convert_systimespec(atime), - convert_systimespec(mtime), - ) - } - })?; - Ok(()) - } -} - -fn convert_systimespec(t: Option) -> Option { - match t { - Some(wasi_common::SystemTimeSpec::Absolute(t)) => Some(SystemTimeSpec::Absolute(t)), - Some(wasi_common::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), - None => None, + asyncify(move || self.0.set_times(path, atime, mtime, follow_symlinks)) } } diff --git a/crates/wasi-common/tokio/src/file.rs b/crates/wasi-common/tokio/src/file.rs index 63da0e2076..ed76b827e6 100644 --- a/crates/wasi-common/tokio/src/file.rs +++ b/crates/wasi-common/tokio/src/file.rs @@ -1,325 +1,176 @@ use crate::asyncify; -use cap_fs_ext::MetadataExt; -use fs_set_times::{SetTimes, SystemTimeSpec}; use std::any::Any; -use std::convert::TryInto; use std::io; -use system_interface::fs::{FileIoExt, GetSetFdFlags}; -use system_interface::io::ReadReady; use wasi_common::{ file::{Advice, FdFlags, FileType, Filestat, WasiFile}, - Error, ErrorExt, + Error, }; -mod internal { - use std::sync::{Mutex, MutexGuard}; - #[cfg(not(windows))] - use unsafe_io::os::posish::{AsRawFd, RawFd}; - #[cfg(windows)] - use unsafe_io::os::windows::{AsRawHandleOrSocket, RawHandleOrSocket}; - use unsafe_io::OwnsRaw; - - // This internal type wraps tokio's File so that we can impl the - // `AsUnsafeFile` trait. We impl this on an internal type, rather than on - // super::File, because we don't want consumers of this library to be able - // to use our `AsUnsafeFile`. - // Mutex is required because this type requires internal mutation for the - // tokio AsyncWriteExt methods to work, and must be Send. - pub(super) struct Internal(Mutex); - impl Internal { - pub fn new(f: tokio::fs::File) -> Self { - Internal(Mutex::new(f)) - } - pub fn inner(&self) -> MutexGuard { - self.0.lock().unwrap() - } - } - - #[cfg(not(windows))] - impl AsRawFd for Internal { - fn as_raw_fd(&self) -> RawFd { - self.inner().as_raw_fd() - } - } - - #[cfg(windows)] - impl AsRawHandleOrSocket for Internal { - fn as_raw_handle_or_socket(&self) -> RawHandleOrSocket { - self.inner().as_raw_handle_or_socket() - } - } - - // Safety: `Internal` owns its handle. - unsafe impl OwnsRaw for Internal {} -} - -pub struct File(internal::Internal); +pub struct File(wasi_cap_std_sync::file::File); impl File { + pub(crate) fn from_inner(file: wasi_cap_std_sync::file::File) -> Self { + File(file) + } pub fn from_cap_std(file: cap_std::fs::File) -> Self { - File(internal::Internal::new(tokio::fs::File::from_std( - file.into_std(), - ))) - } - - async fn metadata(&self) -> Result { - use unsafe_io::AsUnsafeFile; - asyncify(|| Ok(cap_std::fs::Metadata::from_file(&self.0.as_file_view())?)) + Self::from_inner(wasi_cap_std_sync::file::File::from_cap_std(file)) } } -#[wiggle::async_trait] -impl WasiFile for File { - fn as_any(&self) -> &dyn Any { - self - } - async fn datasync(&self) -> Result<(), Error> { - self.0.inner().sync_data().await?; - Ok(()) - } - async fn sync(&self) -> Result<(), Error> { - self.0.inner().sync_all().await?; - Ok(()) - } - async fn get_filetype(&self) -> Result { - let meta = self.metadata().await?; - Ok(filetype_from(&meta.file_type())) - } - async fn get_fdflags(&self) -> Result { - let fdflags = asyncify(|| self.0.get_fd_flags())?; - Ok(from_sysif_fdflags(fdflags)) - } - async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { - if fdflags.intersects( - wasi_common::file::FdFlags::DSYNC - | wasi_common::file::FdFlags::SYNC - | wasi_common::file::FdFlags::RSYNC, - ) { - return Err(Error::invalid_argument().context("cannot set DSYNC, SYNC, or RSYNC flag")); - } - asyncify(move || self.0.set_fd_flags(to_sysif_fdflags(fdflags)))?; - Ok(()) - } - async fn get_filestat(&self) -> Result { - let meta = self.metadata().await?; - Ok(Filestat { - device_id: meta.dev(), - inode: meta.ino(), - filetype: filetype_from(&meta.file_type()), - nlink: meta.nlink(), - size: meta.len(), - atim: meta.accessed().map(|t| Some(t.into_std())).unwrap_or(None), - mtim: meta.modified().map(|t| Some(t.into_std())).unwrap_or(None), - ctim: meta.created().map(|t| Some(t.into_std())).unwrap_or(None), - }) - } - async fn set_filestat_size(&self, size: u64) -> Result<(), Error> { - self.0.inner().set_len(size).await?; - Ok(()) - } - async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { - asyncify(move || self.0.advise(offset, len, convert_advice(advice)))?; - Ok(()) - } - async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { - asyncify(move || self.0.allocate(offset, len))?; - Ok(()) - } - async fn set_times( - &self, - atime: Option, - mtime: Option, - ) -> Result<(), Error> { - asyncify(|| { - self.0 - .set_times(convert_systimespec(atime), convert_systimespec(mtime)) - })?; - Ok(()) - } - async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { - use std::ops::DerefMut; - use tokio::io::AsyncReadExt; - let mut nbytes: usize = 0; - for b in bufs.iter_mut() { - let n = self.0.inner().read(b.deref_mut()).await?; - nbytes += n; - if n < b.len() { - break; +pub struct Stdin(wasi_cap_std_sync::stdio::Stdin); + +pub fn stdin() -> Stdin { + Stdin(wasi_cap_std_sync::stdio::stdin()) +} + +pub struct Stdout(wasi_cap_std_sync::stdio::Stdout); + +pub fn stdout() -> Stdout { + Stdout(wasi_cap_std_sync::stdio::stdout()) +} + +pub struct Stderr(wasi_cap_std_sync::stdio::Stderr); + +pub fn stderr() -> Stderr { + Stderr(wasi_cap_std_sync::stdio::stderr()) +} + +macro_rules! wasi_file_impl { + ($ty:ty) => { + #[wiggle::async_trait] + impl WasiFile for $ty { + fn as_any(&self) -> &dyn Any { + self + } + async fn datasync(&self) -> Result<(), Error> { + asyncify(|| self.0.datasync()) + } + async fn sync(&self) -> Result<(), Error> { + asyncify(|| self.0.sync()) + } + async fn get_filetype(&self) -> Result { + asyncify(|| self.0.get_filetype()) + } + async fn get_fdflags(&self) -> Result { + asyncify(|| self.0.get_fdflags()) + } + async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { + asyncify(|| self.0.set_fdflags(fdflags)) + } + async fn get_filestat(&self) -> Result { + asyncify(|| self.0.get_filestat()) + } + async fn set_filestat_size(&self, size: u64) -> Result<(), Error> { + asyncify(move || self.0.set_filestat_size(size)) + } + async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { + asyncify(move || self.0.advise(offset, len, advice)) + } + async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { + asyncify(move || self.0.allocate(offset, len)) + } + async fn read_vectored<'a>( + &self, + bufs: &mut [io::IoSliceMut<'a>], + ) -> Result { + asyncify(move || self.0.read_vectored(bufs)) + } + async fn read_vectored_at<'a>( + &self, + bufs: &mut [io::IoSliceMut<'a>], + offset: u64, + ) -> Result { + asyncify(move || self.0.read_vectored_at(bufs, offset)) + } + async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { + asyncify(move || self.0.write_vectored(bufs)) + } + async fn write_vectored_at<'a>( + &self, + bufs: &[io::IoSlice<'a>], + offset: u64, + ) -> Result { + asyncify(move || self.0.write_vectored_at(bufs, offset)) + } + async fn seek(&self, pos: std::io::SeekFrom) -> Result { + asyncify(move || self.0.seek(pos)) + } + async fn peek(&self, buf: &mut [u8]) -> Result { + asyncify(move || self.0.peek(buf)) + } + async fn set_times( + &self, + atime: Option, + mtime: Option, + ) -> Result<(), Error> { + asyncify(move || self.0.set_times(atime, mtime)) + } + async fn num_ready_bytes(&self) -> Result { + asyncify(|| self.0.num_ready_bytes()) + } + + #[cfg(not(windows))] + async fn readable(&mut self) -> Result<(), Error> { + // The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object. + // AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce + // mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this + // async method to ensure this is the only Future which can access the RawFd during the + // lifetime of the AsyncFd. + use tokio::io::{unix::AsyncFd, Interest}; + use unsafe_io::os::posish::AsRawFd; + let rawfd = self.0.as_raw_fd(); + match AsyncFd::with_interest(rawfd, Interest::READABLE) { + Ok(asyncfd) => { + let _ = asyncfd.readable().await?; + Ok(()) + } + Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { + // if e is EPERM, this file isnt supported by epoll because it is immediately + // available for reading: + Ok(()) + } + Err(e) => Err(e.into()), + } + } + #[cfg(windows)] + async fn readable(&mut self) -> Result<(), Error> { + // Windows uses a rawfd based scheduler :( + Err(Error::badf()) + } + + #[cfg(not(windows))] + async fn writable(&mut self) -> Result<(), Error> { + // The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object. + // AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce + // mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this + // async method to ensure this is the only Future which can access the RawFd during the + // lifetime of the AsyncFd. + use tokio::io::{unix::AsyncFd, Interest}; + use unsafe_io::os::posish::AsRawFd; + let rawfd = self.0.as_raw_fd(); + match AsyncFd::with_interest(rawfd, Interest::WRITABLE) { + Ok(asyncfd) => { + let _ = asyncfd.writable().await?; + Ok(()) + } + Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { + // if e is EPERM, this file isnt supported by epoll because it is immediately + // available for writing: + Ok(()) + } + Err(e) => Err(e.into()), + } + } + #[cfg(windows)] + async fn writable(&mut self) -> Result<(), Error> { + // Windows uses a rawfd based scheduler :( + Err(Error::badf()) } } - Ok(nbytes.try_into()?) - } - async fn read_vectored_at<'a>( - &self, - bufs: &mut [io::IoSliceMut<'a>], - offset: u64, - ) -> Result { - let n = asyncify(move || self.0.read_vectored_at(bufs, offset))?; - Ok(n.try_into()?) - } - async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { - use tokio::io::AsyncWriteExt; - let mut n: usize = 0; - n += self.0.inner().write_vectored(bufs).await?; - Ok(n.try_into()?) - } - async fn write_vectored_at<'a>( - &self, - bufs: &[io::IoSlice<'a>], - offset: u64, - ) -> Result { - let n = asyncify(move || self.0.write_vectored_at(bufs, offset))?; - Ok(n.try_into()?) - } - async fn seek(&self, pos: std::io::SeekFrom) -> Result { - use tokio::io::AsyncSeekExt; - Ok(self.0.inner().seek(pos).await?) - } - async fn peek(&self, buf: &mut [u8]) -> Result { - let n = asyncify(move || self.0.peek(buf))?; - Ok(n.try_into()?) - } - async fn num_ready_bytes(&self) -> Result { - use unsafe_io::AsUnsafeFile; - asyncify(|| self.0.as_file_view().num_ready_bytes()) - } - #[cfg(not(windows))] - async fn readable(&mut self) -> Result<(), Error> { - // The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object. - // AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce - // mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this - // async method to ensure this is the only Future which can access the RawFd during the - // lifetime of the AsyncFd. - use tokio::io::{unix::AsyncFd, Interest}; - use unsafe_io::os::posish::AsRawFd; - let rawfd = self.0.as_raw_fd(); - match AsyncFd::with_interest(rawfd, Interest::READABLE) { - Ok(asyncfd) => { - let _ = asyncfd.readable().await?; - Ok(()) - } - Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { - // if e is EPERM, this file isnt supported by epoll because it is immediately - // available for reading: - Ok(()) - } - Err(e) => Err(e.into()), - } - } - #[cfg(windows)] - async fn readable(&mut self) -> Result<(), Error> { - // Windows uses a rawfd based scheduler :( - Err(Error::badf()) - } - - #[cfg(not(windows))] - async fn writable(&mut self) -> Result<(), Error> { - // The Inner impls OwnsRaw, which asserts exclusive use of the handle by the owned object. - // AsyncFd needs to wrap an owned `impl std::os::unix::io::AsRawFd`. Rather than introduce - // mutability to let it own the `Inner`, we are depending on the `&mut self` bound on this - // async method to ensure this is the only Future which can access the RawFd during the - // lifetime of the AsyncFd. - use tokio::io::{unix::AsyncFd, Interest}; - use unsafe_io::os::posish::AsRawFd; - let rawfd = self.0.as_raw_fd(); - match AsyncFd::with_interest(rawfd, Interest::WRITABLE) { - Ok(asyncfd) => { - let _ = asyncfd.writable().await?; - Ok(()) - } - Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { - // if e is EPERM, this file isnt supported by epoll because it is immediately - // available for writing: - Ok(()) - } - Err(e) => Err(e.into()), - } - } - #[cfg(windows)] - async fn writable(&mut self) -> Result<(), Error> { - // Windows uses a rawfd based scheduler :( - Err(Error::badf()) - } + }; } -pub fn filetype_from(ft: &cap_std::fs::FileType) -> FileType { - use cap_fs_ext::FileTypeExt; - if ft.is_dir() { - FileType::Directory - } else if ft.is_symlink() { - FileType::SymbolicLink - } else if ft.is_socket() { - if ft.is_block_device() { - FileType::SocketDgram - } else { - FileType::SocketStream - } - } else if ft.is_block_device() { - FileType::BlockDevice - } else if ft.is_char_device() { - FileType::CharacterDevice - } else if ft.is_file() { - FileType::RegularFile - } else { - FileType::Unknown - } -} - -pub fn convert_systimespec(t: Option) -> Option { - match t { - Some(wasi_common::SystemTimeSpec::Absolute(t)) => { - Some(SystemTimeSpec::Absolute(t.into_std())) - } - Some(wasi_common::SystemTimeSpec::SymbolicNow) => Some(SystemTimeSpec::SymbolicNow), - None => None, - } -} - -pub fn to_sysif_fdflags(f: wasi_common::file::FdFlags) -> system_interface::fs::FdFlags { - let mut out = system_interface::fs::FdFlags::empty(); - if f.contains(wasi_common::file::FdFlags::APPEND) { - out |= system_interface::fs::FdFlags::APPEND; - } - if f.contains(wasi_common::file::FdFlags::DSYNC) { - out |= system_interface::fs::FdFlags::DSYNC; - } - if f.contains(wasi_common::file::FdFlags::NONBLOCK) { - out |= system_interface::fs::FdFlags::NONBLOCK; - } - if f.contains(wasi_common::file::FdFlags::RSYNC) { - out |= system_interface::fs::FdFlags::RSYNC; - } - if f.contains(wasi_common::file::FdFlags::SYNC) { - out |= system_interface::fs::FdFlags::SYNC; - } - out -} -pub fn from_sysif_fdflags(f: system_interface::fs::FdFlags) -> wasi_common::file::FdFlags { - let mut out = wasi_common::file::FdFlags::empty(); - if f.contains(system_interface::fs::FdFlags::APPEND) { - out |= wasi_common::file::FdFlags::APPEND; - } - if f.contains(system_interface::fs::FdFlags::DSYNC) { - out |= wasi_common::file::FdFlags::DSYNC; - } - if f.contains(system_interface::fs::FdFlags::NONBLOCK) { - out |= wasi_common::file::FdFlags::NONBLOCK; - } - if f.contains(system_interface::fs::FdFlags::RSYNC) { - out |= wasi_common::file::FdFlags::RSYNC; - } - if f.contains(system_interface::fs::FdFlags::SYNC) { - out |= wasi_common::file::FdFlags::SYNC; - } - out -} -pub fn convert_advice(advice: Advice) -> system_interface::fs::Advice { - match advice { - Advice::Normal => system_interface::fs::Advice::Normal, - Advice::Sequential => system_interface::fs::Advice::Sequential, - Advice::Random => system_interface::fs::Advice::Random, - Advice::WillNeed => system_interface::fs::Advice::WillNeed, - Advice::DontNeed => system_interface::fs::Advice::DontNeed, - Advice::NoReuse => system_interface::fs::Advice::NoReuse, - } -} +wasi_file_impl!(File); +wasi_file_impl!(Stdin); +wasi_file_impl!(Stdout); +wasi_file_impl!(Stderr); diff --git a/crates/wasi-common/tokio/src/lib.rs b/crates/wasi-common/tokio/src/lib.rs index 455e97e6b3..bbe09dfe79 100644 --- a/crates/wasi-common/tokio/src/lib.rs +++ b/crates/wasi-common/tokio/src/lib.rs @@ -4,6 +4,7 @@ pub mod sched; pub mod stdio; use std::cell::RefCell; +use std::future::Future; use std::path::Path; use std::rc::Rc; pub use wasi_cap_std_sync::{clocks_ctx, random_ctx}; @@ -70,15 +71,14 @@ impl WasiCtxBuilder { pub fn stderr(self, f: Box) -> Self { WasiCtxBuilder(self.0.stderr(f)) } - // XXX our crate needs its own stdios pub fn inherit_stdin(self) -> Self { - self.stdin(Box::new(wasi_cap_std_sync::stdio::stdin())) + self.stdin(Box::new(crate::stdio::stdin())) } pub fn inherit_stdout(self) -> Self { - self.stdout(Box::new(wasi_cap_std_sync::stdio::stdout())) + self.stdout(Box::new(crate::stdio::stdout())) } pub fn inherit_stderr(self) -> Self { - self.stderr(Box::new(wasi_cap_std_sync::stdio::stderr())) + self.stderr(Box::new(crate::stdio::stderr())) } pub fn inherit_stdio(self) -> Self { self.inherit_stdin().inherit_stdout().inherit_stderr() @@ -96,10 +96,14 @@ impl WasiCtxBuilder { } } -pub(crate) fn asyncify<'a, F, T>(f: F) -> Result +// This function takes the "async" code which is in fact blocking +// but always returns Poll::Ready, and executes it in a dummy executor +// on a blocking thread in the tokio thread pool. +pub(crate) fn asyncify<'a, F, Fut, T>(f: F) -> Result where - F: FnOnce() -> Result + Send + 'a, + F: FnOnce() -> Fut + Send + 'a, + Fut: Future>, T: Send + 'static, { - tokio::task::block_in_place(f).map_err(Into::into) + tokio::task::block_in_place(move || unsafe { wiggle::run_in_dummy_executor(f()) }) } diff --git a/crates/wasi-common/tokio/src/sched/unix.rs b/crates/wasi-common/tokio/src/sched/unix.rs index 614a5f16de..e3028cf611 100644 --- a/crates/wasi-common/tokio/src/sched/unix.rs +++ b/crates/wasi-common/tokio/src/sched/unix.rs @@ -78,7 +78,10 @@ pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { } } if let Some(Some(remaining_duration)) = duration { - tokio::time::timeout(remaining_duration, futures).await??; + match tokio::time::timeout(remaining_duration, futures).await { + Ok(r) => r?, + Err(_deadline_elapsed) => {} + } } else { futures.await?; } diff --git a/crates/wasi-common/tokio/src/sched/windows.rs b/crates/wasi-common/tokio/src/sched/windows.rs index a3c15f3c2f..623841d02c 100644 --- a/crates/wasi-common/tokio/src/sched/windows.rs +++ b/crates/wasi-common/tokio/src/sched/windows.rs @@ -9,31 +9,54 @@ use wasi_common::{ file::WasiFile, sched::{ subscription::{RwEventFlags, Subscription}, - Poll, + Poll, WasiSched, }, Error, ErrorExt, }; -pub async fn poll_oneoff<'a>(poll: &'_ Poll<'a>) -> Result<(), Error> { + +pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { if poll.is_empty() { return Ok(()); } let mut ready = false; - let timeout = poll.earliest_clock_deadline(); + let waitmode = if let Some(t) = poll.earliest_clock_deadline() { + if let Some(duration) = t.duration_until() { + WaitMode::Timeout(duration) + } else { + WaitMode::Immediate + } + } else { + if ready { + WaitMode::Immediate + } else { + WaitMode::Infinite + } + }; let mut stdin_read_subs = Vec::new(); - let mut immediate_subs = Vec::new(); + let mut immediate_reads = Vec::new(); + let mut immediate_writes = Vec::new(); for s in poll.rw_subscriptions() { match s { - Subscription::Read(r) if r.file.as_any().is::() => { - stdin_read_subs.push(r); - } - Subscription::Read(rw) | Subscription::Write(rw) => { - if wasi_file_raw_handle(rw.file.deref()).is_some() { - immediate_subs.push(s); + Subscription::Read(r) => { + if r.file.as_any().is::() { + stdin_read_subs.push(r); + } else if wasi_file_raw_handle(r.file.deref()).is_some() { + immediate_reads.push(r); } else { - return Err(Error::invalid_argument() - .context("read/write subscription fd downcast failed")); + return Err( + Error::invalid_argument().context("read subscription fd downcast failed") + ); + } + } + Subscription::Write(w) => { + if wasi_file_raw_handle(w.file.deref()).is_some() { + immediate_writes.push(w); + } else { + return Err( + Error::invalid_argument().context("write subscription fd downcast failed") + ); } } Subscription::MonotonicClock { .. } => unreachable!(), @@ -41,19 +64,6 @@ pub async fn poll_oneoff<'a>(poll: &'_ Poll<'a>) -> Result<(), Error> { } if !stdin_read_subs.is_empty() { - let waitmode = if let Some(t) = timeout { - if let Some(duration) = t.duration_until() { - WaitMode::Timeout(duration) - } else { - WaitMode::Immediate - } - } else { - if ready { - WaitMode::Immediate - } else { - WaitMode::Infinite - } - }; let state = STDIN_POLL .lock() .map_err(|_| Error::trap("failed to take lock of STDIN_POLL"))? @@ -79,37 +89,27 @@ pub async fn poll_oneoff<'a>(poll: &'_ Poll<'a>) -> Result<(), Error> { } } } - for sub in immediate_subs { - match sub { - Subscription::Read(r) => { - // XXX This doesnt strictly preserve the behavior in the earlier - // implementation, which would always do complete(0) for reads from - // stdout/err. - match r.file.num_ready_bytes().await { - Ok(ready_bytes) => { - r.complete(ready_bytes, RwEventFlags::empty()); - ready = true; - } - Err(e) => { - r.error(e); - ready = true; - } - } - } - Subscription::Write(w) => { - // Everything is always ready for writing, apparently? - w.complete(0, RwEventFlags::empty()); + for r in immediate_reads { + match r.file.num_ready_bytes().await { + Ok(ready_bytes) => { + r.complete(ready_bytes, RwEventFlags::empty()); + ready = true; + } + Err(e) => { + r.error(e); ready = true; } - Subscription::MonotonicClock { .. } => unreachable!(), } } + for w in immediate_writes { + // Everything is always ready for writing, apparently? + w.complete(0, RwEventFlags::empty()); + ready = true; + } if !ready { - if let Some(t) = timeout { - if let Some(duration) = t.duration_until() { - thread::sleep(duration); - } + if let WaitMode::Timeout(duration) = waitmode { + thread::sleep(duration); } } @@ -154,6 +154,7 @@ enum PollState { Error(std::io::Error), } +#[derive(Copy, Clone)] enum WaitMode { Timeout(Duration), Infinite, diff --git a/crates/wasi-common/tokio/src/stdio.rs b/crates/wasi-common/tokio/src/stdio.rs index 1dd7431605..dd23c0555a 100644 --- a/crates/wasi-common/tokio/src/stdio.rs +++ b/crates/wasi-common/tokio/src/stdio.rs @@ -1,295 +1 @@ -use crate::file::convert_systimespec; -use fs_set_times::SetTimes; -use std::any::Any; -use std::convert::TryInto; -use std::io; -use std::io::{Read, Write}; - -use unsafe_io::AsUnsafeFile; -use wasi_common::{ - file::{Advice, FdFlags, FileType, Filestat, WasiFile}, - Error, ErrorExt, -}; - -mod internal { - #[cfg(unix)] - use std::os::unix::io::{AsRawFd, RawFd}; - #[cfg(windows)] - use std::os::windows::io::{AsRawHandle, RawHandle}; - use unsafe_io::OwnsRaw; - - pub(super) struct TokioStdin(tokio::io::Stdin); - impl TokioStdin { - pub fn new() -> Self { - TokioStdin(tokio::io::stdin()) - } - } - - #[cfg(windows)] - impl AsRawHandle for TokioStdin { - fn as_raw_handle(&self) -> RawHandle { - self.0.as_raw_handle() - } - } - #[cfg(unix)] - impl AsRawFd for TokioStdin { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } - } - unsafe impl OwnsRaw for TokioStdin {} - - pub(super) struct TokioStdout(tokio::io::Stdout); - impl TokioStdout { - pub fn new() -> Self { - TokioStdout(tokio::io::stdout()) - } - } - - #[cfg(windows)] - impl AsRawHandle for TokioStdout { - fn as_raw_handle(&self) -> RawHandle { - self.0.as_raw_handle() - } - } - #[cfg(unix)] - impl AsRawFd for TokioStdout { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } - } - unsafe impl OwnsRaw for TokioStdout {} - - pub(super) struct TokioStderr(tokio::io::Stderr); - impl TokioStderr { - pub fn new() -> Self { - TokioStderr(tokio::io::stderr()) - } - } - #[cfg(windows)] - impl AsRawHandle for TokioStderr { - fn as_raw_handle(&self) -> RawHandle { - self.0.as_raw_handle() - } - } - #[cfg(unix)] - impl AsRawFd for TokioStderr { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } - } - unsafe impl OwnsRaw for TokioStderr {} -} - -pub struct Stdin(internal::TokioStdin); - -pub fn stdin() -> Stdin { - Stdin(internal::TokioStdin::new()) -} - -#[wiggle::async_trait] -impl WasiFile for Stdin { - fn as_any(&self) -> &dyn Any { - self - } - async fn datasync(&self) -> Result<(), Error> { - Ok(()) - } - async fn sync(&self) -> Result<(), Error> { - Ok(()) - } - async fn get_filetype(&self) -> Result { - Ok(FileType::Unknown) - } - async fn get_fdflags(&self) -> Result { - Ok(FdFlags::empty()) - } - async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { - Err(Error::badf()) - } - async fn get_filestat(&self) -> Result { - let meta = self.0.as_file_view().metadata()?; - Ok(Filestat { - device_id: 0, - inode: 0, - filetype: self.get_filetype().await?, - nlink: 0, - size: meta.len(), - atim: meta.accessed().ok(), - mtim: meta.modified().ok(), - ctim: meta.created().ok(), - }) - } - async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { - Err(Error::badf()) - } - async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { - Err(Error::badf()) - } - async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> { - Err(Error::badf()) - } - async fn read_vectored<'a>(&self, bufs: &mut [io::IoSliceMut<'a>]) -> Result { - let n = self.0.as_file_view().read_vectored(bufs)?; - Ok(n.try_into().map_err(|_| Error::range())?) - } - async fn read_vectored_at<'a>( - &self, - _bufs: &mut [io::IoSliceMut<'a>], - _offset: u64, - ) -> Result { - Err(Error::seek_pipe()) - } - async fn write_vectored<'a>(&self, _bufs: &[io::IoSlice<'a>]) -> Result { - Err(Error::badf()) - } - async fn write_vectored_at<'a>( - &self, - _bufs: &[io::IoSlice<'a>], - _offset: u64, - ) -> Result { - Err(Error::badf()) - } - async fn seek(&self, _pos: std::io::SeekFrom) -> Result { - Err(Error::seek_pipe()) - } - async fn peek(&self, _buf: &mut [u8]) -> Result { - Err(Error::seek_pipe()) - } - async fn set_times( - &self, - atime: Option, - mtime: Option, - ) -> Result<(), Error> { - self.0 - .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; - Ok(()) - } - - #[cfg(not(windows))] - async fn num_ready_bytes(&self) -> Result { - Ok(posish::io::fionread(&self.0)?) - } - #[cfg(windows)] - async fn num_ready_bytes(&self) -> Result { - // conservative but correct is the best we can do - Ok(0) - } - - async fn readable(&mut self) -> Result<(), Error> { - Err(Error::badf()) - } - async fn writable(&mut self) -> Result<(), Error> { - Err(Error::badf()) - } -} - -macro_rules! wasi_file_write_impl { - ($ty:ty) => { - #[wiggle::async_trait] - impl WasiFile for $ty { - fn as_any(&self) -> &dyn Any { - self - } - async fn datasync(&self) -> Result<(), Error> { - Ok(()) - } - async fn sync(&self) -> Result<(), Error> { - Ok(()) - } - async fn get_filetype(&self) -> Result { - Ok(FileType::Unknown) - } - async fn get_fdflags(&self) -> Result { - Ok(FdFlags::APPEND) - } - async fn set_fdflags(&mut self, _fdflags: FdFlags) -> Result<(), Error> { - Err(Error::badf()) - } - async fn get_filestat(&self) -> Result { - let meta = self.0.as_file_view().metadata()?; - Ok(Filestat { - device_id: 0, - inode: 0, - filetype: self.get_filetype().await?, - nlink: 0, - size: meta.len(), - atim: meta.accessed().ok(), - mtim: meta.modified().ok(), - ctim: meta.created().ok(), - }) - } - async fn set_filestat_size(&self, _size: u64) -> Result<(), Error> { - Err(Error::badf()) - } - async fn advise(&self, _offset: u64, _len: u64, _advice: Advice) -> Result<(), Error> { - Err(Error::badf()) - } - async fn allocate(&self, _offset: u64, _len: u64) -> Result<(), Error> { - Err(Error::badf()) - } - async fn read_vectored<'a>( - &self, - _bufs: &mut [io::IoSliceMut<'a>], - ) -> Result { - Err(Error::badf()) - } - async fn read_vectored_at<'a>( - &self, - _bufs: &mut [io::IoSliceMut<'a>], - _offset: u64, - ) -> Result { - Err(Error::badf()) - } - async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { - let n = self.0.as_file_view().write_vectored(bufs)?; - Ok(n.try_into().map_err(|c| Error::range().context(c))?) - } - async fn write_vectored_at<'a>( - &self, - _bufs: &[io::IoSlice<'a>], - _offset: u64, - ) -> Result { - Err(Error::seek_pipe()) - } - async fn seek(&self, _pos: std::io::SeekFrom) -> Result { - Err(Error::seek_pipe()) - } - async fn peek(&self, _buf: &mut [u8]) -> Result { - Err(Error::badf()) - } - async fn set_times( - &self, - atime: Option, - mtime: Option, - ) -> Result<(), Error> { - self.0 - .set_times(convert_systimespec(atime), convert_systimespec(mtime))?; - Ok(()) - } - async fn num_ready_bytes(&self) -> Result { - Ok(0) - } - async fn readable(&mut self) -> Result<(), Error> { - Err(Error::badf()) - } - async fn writable(&mut self) -> Result<(), Error> { - Err(Error::badf()) - } - } - }; -} - -pub struct Stdout(internal::TokioStdout); - -pub fn stdout() -> Stdout { - Stdout(internal::TokioStdout::new()) -} -wasi_file_write_impl!(Stdout); - -pub struct Stderr(internal::TokioStderr); - -pub fn stderr() -> Stderr { - Stderr(internal::TokioStderr::new()) -} -wasi_file_write_impl!(Stderr); +pub use crate::file::{stderr, stdin, stdout, Stderr, Stdin, Stdout}; From e0f3423161d1cdc0fcfd118ff2396a0cb2af3848 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 4 May 2021 14:26:00 -0700 Subject: [PATCH 44/72] support windows --- crates/wasi-common/src/clocks.rs | 4 ++-- crates/wasi-common/tokio/src/file.rs | 10 ++++++++++ crates/wasi-common/tokio/src/sched/windows.rs | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/crates/wasi-common/src/clocks.rs b/crates/wasi-common/src/clocks.rs index babf0acff4..679759caf9 100644 --- a/crates/wasi-common/src/clocks.rs +++ b/crates/wasi-common/src/clocks.rs @@ -5,12 +5,12 @@ pub enum SystemTimeSpec { Absolute(SystemTime), } -pub trait WasiSystemClock { +pub trait WasiSystemClock: Send + Sync { fn resolution(&self) -> Duration; fn now(&self, precision: Duration) -> SystemTime; } -pub trait WasiMonotonicClock { +pub trait WasiMonotonicClock: Send + Sync { fn resolution(&self) -> Duration; fn now(&self, precision: Duration) -> Instant; } diff --git a/crates/wasi-common/tokio/src/file.rs b/crates/wasi-common/tokio/src/file.rs index ed76b827e6..82c8d965ac 100644 --- a/crates/wasi-common/tokio/src/file.rs +++ b/crates/wasi-common/tokio/src/file.rs @@ -1,6 +1,8 @@ use crate::asyncify; use std::any::Any; use std::io; +#[cfg(windows)] +use std::os::windows::io::{AsRawHandle, RawHandle}; use wasi_common::{ file::{Advice, FdFlags, FileType, Filestat, WasiFile}, Error, @@ -135,6 +137,7 @@ macro_rules! wasi_file_impl { #[cfg(windows)] async fn readable(&mut self) -> Result<(), Error> { // Windows uses a rawfd based scheduler :( + use wasi_common::ErrorExt; Err(Error::badf()) } @@ -164,9 +167,16 @@ macro_rules! wasi_file_impl { #[cfg(windows)] async fn writable(&mut self) -> Result<(), Error> { // Windows uses a rawfd based scheduler :( + use wasi_common::ErrorExt; Err(Error::badf()) } } + #[cfg(windows)] + impl AsRawHandle for $ty { + fn as_raw_handle(&self) -> RawHandle { + self.0.as_raw_handle() + } + } }; } diff --git a/crates/wasi-common/tokio/src/sched/windows.rs b/crates/wasi-common/tokio/src/sched/windows.rs index 623841d02c..7228d4e395 100644 --- a/crates/wasi-common/tokio/src/sched/windows.rs +++ b/crates/wasi-common/tokio/src/sched/windows.rs @@ -1,3 +1,4 @@ +use crate::asyncify; use anyhow::Context; use std::ops::Deref; use std::os::windows::io::{AsRawHandle, RawHandle}; @@ -9,12 +10,16 @@ use wasi_common::{ file::WasiFile, sched::{ subscription::{RwEventFlags, Subscription}, - Poll, WasiSched, + Poll, }, Error, ErrorExt, }; pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { + asyncify(move || poll_oneoff_(poll)) +} + +async fn poll_oneoff_<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { if poll.is_empty() { return Ok(()); } From 0faf3b248e4ed1c56811571e444753831594a47b Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 5 May 2021 11:00:59 -0700 Subject: [PATCH 45/72] wasmtime-wasi: keep exporting sync at the top level --- crates/wasi/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/wasi/src/lib.rs b/crates/wasi/src/lib.rs index f0ac46811c..3edaa34505 100644 --- a/crates/wasi/src/lib.rs +++ b/crates/wasi/src/lib.rs @@ -18,6 +18,11 @@ pub mod sync { super::define_wasi!(block_on); } +/// Sync mode is the "default" of this crate, so we also export it at the top +/// level. +#[cfg(feature = "sync")] +pub use sync::*; + /// Re-export the wasi-tokio crate here. This saves consumers of this library from having /// to keep additional dependencies in sync. #[cfg(feature = "tokio")] From f8d1e574287433f907689d3b237236163625ec2d Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 5 May 2021 11:17:30 -0700 Subject: [PATCH 46/72] publish wasi-tokio --- scripts/publish.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/publish.rs b/scripts/publish.rs index 39a7f04e8c..e121cedc3a 100644 --- a/scripts/publish.rs +++ b/scripts/publish.rs @@ -49,6 +49,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ // wasi-common "wasi-common", "wasi-cap-std-sync", + "wasi-tokio", // wasmtime "lightbeam", "wasmtime-fiber", From 3e8ea090c63a29eaef6c9e50fd87346a692e0a89 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 5 May 2021 11:24:27 -0700 Subject: [PATCH 47/72] ci debugging --- crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs index 3246b5e505..2e6dfb5727 100644 --- a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs +++ b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs @@ -125,7 +125,7 @@ unsafe fn test_fd_readwrite(readable_fd: wasi::Fd, writable_fd: wasi::Fd, error_ }, ]; let out = poll_oneoff_impl(&r#in).unwrap(); - assert_eq!(out.len(), 2, "should return 2 events"); + assert_eq!(out.len(), 2, "should return 2 events, got: {:?}", out); assert_eq!( out[0].userdata, 1, "the event.userdata should contain fd userdata specified by the user" From 909d691b5551890b33faae4fea21a5d931e64ea2 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 5 May 2021 11:59:46 -0700 Subject: [PATCH 48/72] run wasi-tokio test early, turn off fail-fast so we see all platforms --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9b3dfe2d70..3d6d2c9c93 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -263,6 +263,9 @@ jobs: env: RUST_BACKTRACE: 1 + # DEBUG: run these tests first + - run: cargo test -p wasi-tokio + # Build and test all features except for lightbeam - run: | cargo test \ From 9e04c5333c08cc72457fd8f5ac7a9d200bf97c54 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 5 May 2021 12:55:07 -0700 Subject: [PATCH 49/72] poll oneoff tests: what if we read a non-empty file? --- .../wasi-tests/src/bin/poll_oneoff_files.rs | 23 +++++++++++++++- crates/wasi-common/src/sched/subscription.rs | 1 + crates/wasi-common/tokio/tests/poll_oneoff.rs | 26 +++++++++++++------ 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs index 2e6dfb5727..d457a80544 100644 --- a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs +++ b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_files.rs @@ -150,16 +150,37 @@ unsafe fn test_fd_readwrite(readable_fd: wasi::Fd, writable_fd: wasi::Fd, error_ unsafe fn test_fd_readwrite_valid_fd(dir_fd: wasi::Fd) { // Create a file in the scratch directory. - let readable_fd = wasi::path_open( + let nonempty_file = wasi::path_open( dir_fd, 0, "readable_file", wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_WRITE, + 0, + 0, + ) + .expect("create writable file"); + // Write to file + let contents = &[1u8]; + let ciovec = wasi::Ciovec { + buf: contents.as_ptr() as *const _, + buf_len: contents.len(), + }; + wasi::fd_write(nonempty_file, &[ciovec]).expect("write"); + wasi::fd_close(nonempty_file).expect("close"); + + // Now open the file for reading + let readable_fd = wasi::path_open( + dir_fd, + 0, + "readable_file", + 0, wasi::RIGHTS_FD_READ | wasi::RIGHTS_POLL_FD_READWRITE, 0, 0, ) .expect("opening a readable file"); + assert_gt!( readable_fd, libc::STDERR_FILENO as wasi::Fd, diff --git a/crates/wasi-common/src/sched/subscription.rs b/crates/wasi-common/src/sched/subscription.rs index d49c646cfe..cd861f6df0 100644 --- a/crates/wasi-common/src/sched/subscription.rs +++ b/crates/wasi-common/src/sched/subscription.rs @@ -62,6 +62,7 @@ pub enum Subscription<'a> { MonotonicClock(MonotonicClockSubscription<'a>), } +#[derive(Debug)] pub enum SubscriptionResult { Read(Result<(u64, RwEventFlags), Error>), Write(Result<(u64, RwEventFlags), Error>), diff --git a/crates/wasi-common/tokio/tests/poll_oneoff.rs b/crates/wasi-common/tokio/tests/poll_oneoff.rs index 6bd42c39ce..8993218786 100644 --- a/crates/wasi-common/tokio/tests/poll_oneoff.rs +++ b/crates/wasi-common/tokio/tests/poll_oneoff.rs @@ -2,7 +2,7 @@ use anyhow::{Context, Error}; use wasi_common::{ file::{FdFlags, OFlags}, sched::{Poll, RwEventFlags, SubscriptionResult, Userdata}, - WasiDir, + WasiDir, WasiFile, }; use wasi_tokio::{sched::poll_oneoff, Dir}; @@ -13,24 +13,34 @@ async fn empty_file_readable() -> Result<(), Error> { let d = workspace.open_dir("d").context("open dir")?; let d = Dir::from_cap_std(d); - let mut readable_f = d - .open_file(false, "f", OFlags::CREATE, true, false, FdFlags::empty()) + let mut f = d + .open_file(false, "f", OFlags::CREATE, false, true, FdFlags::empty()) .await - .context("create readable file")?; + .context("create writable file f")?; + let to_write: Vec = vec![0]; + f.write_vectored(&vec![std::io::IoSlice::new(&to_write)]) + .await + .context("write to f")?; + drop(f); + + let mut f = d + .open_file(false, "f", OFlags::empty(), true, false, FdFlags::empty()) + .await + .context("open f as readable")?; let mut poll = Poll::new(); - poll.subscribe_read(&mut *readable_f, Userdata::from(123)); + poll.subscribe_read(&mut *f, Userdata::from(123)); poll_oneoff(&mut poll).await?; let events = poll.results(); - assert_eq!(events.len(), 1); + assert_eq!(events.len(), 1, "expected 1 event, got: {:?}", events); match events[0] { - (SubscriptionResult::Read(Ok((0, flags))), ud) => { + (SubscriptionResult::Read(Ok((1, flags))), ud) => { assert_eq!(flags, RwEventFlags::empty()); assert_eq!(ud, Userdata::from(123)); } - _ => panic!(""), + _ => panic!("expected (Read(Ok(1, empty), 123), got: {:?}", events[0]), } Ok(()) From 6616c1eaf127683579bb4d8a4e683617bb3c19b0 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 5 May 2021 13:32:05 -0700 Subject: [PATCH 50/72] fix warnings --- .github/workflows/main.yml | 1 + crates/wasi-common/tokio/tests/poll_oneoff.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3d6d2c9c93..75f542081d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -208,6 +208,7 @@ jobs: name: Test runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: build: [stable, beta, nightly, windows, macos] include: diff --git a/crates/wasi-common/tokio/tests/poll_oneoff.rs b/crates/wasi-common/tokio/tests/poll_oneoff.rs index 8993218786..10ac82b9be 100644 --- a/crates/wasi-common/tokio/tests/poll_oneoff.rs +++ b/crates/wasi-common/tokio/tests/poll_oneoff.rs @@ -2,7 +2,7 @@ use anyhow::{Context, Error}; use wasi_common::{ file::{FdFlags, OFlags}, sched::{Poll, RwEventFlags, SubscriptionResult, Userdata}, - WasiDir, WasiFile, + WasiDir, }; use wasi_tokio::{sched::poll_oneoff, Dir}; @@ -13,7 +13,7 @@ async fn empty_file_readable() -> Result<(), Error> { let d = workspace.open_dir("d").context("open dir")?; let d = Dir::from_cap_std(d); - let mut f = d + let f = d .open_file(false, "f", OFlags::CREATE, false, true, FdFlags::empty()) .await .context("create writable file f")?; From 148afd3949fef668ca4318aba346759bde3dc07c Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 5 May 2021 14:57:33 -0700 Subject: [PATCH 51/72] fix yaml syntax --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 75f542081d..108fcf6f04 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -208,7 +208,7 @@ jobs: name: Test runs-on: ${{ matrix.os }} strategy: - fail-fast: false + fail-fast: false matrix: build: [stable, beta, nightly, windows, macos] include: From 2a0eb391c28eefb2f7d878bc28800964ce0a8471 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Wed, 5 May 2021 17:02:46 -0700 Subject: [PATCH 52/72] get rid of debugging changes to ci --- .github/workflows/main.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 108fcf6f04..9b3dfe2d70 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -208,7 +208,6 @@ jobs: name: Test runs-on: ${{ matrix.os }} strategy: - fail-fast: false matrix: build: [stable, beta, nightly, windows, macos] include: @@ -264,9 +263,6 @@ jobs: env: RUST_BACKTRACE: 1 - # DEBUG: run these tests first - - run: cargo test -p wasi-tokio - # Build and test all features except for lightbeam - run: | cargo test \ From f4d851126d516116268444829b9019df86515d95 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 10:53:25 -0700 Subject: [PATCH 53/72] tests dont need fuel --- crates/test-programs/tests/wasm_tests/runtime/tokio.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs index 587ee27f40..88bdb380a4 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs @@ -15,7 +15,6 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any .block_on(async move { let mut config = Config::new(); config.async_support(true); - config.consume_fuel(true); Wasi::add_to_config(&mut config); let engine = Engine::new(&config)?; let store = Store::new(&engine); @@ -58,7 +57,6 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any // cap-std-sync does not yet support the sync family of fdflags builder = builder.env("NO_FDFLAGS_SYNC_SUPPORT", "1")?; - store.out_of_fuel_async_yield(u32::MAX, 10000); Wasi::set_context(&store, builder.build()?) .map_err(|_| anyhow::anyhow!("wasi set_context failed"))?; From 208013e34e75f119a6ab88e7b6ddb17849f45e0c Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 11:12:53 -0700 Subject: [PATCH 54/72] de-duplicate code --- .../tests/wasm_tests/runtime/cap_std_sync.rs | 73 ++++------------ .../tests/wasm_tests/runtime/mod.rs | 32 +++++++ .../tests/wasm_tests/runtime/tokio.rs | 83 +++++-------------- 3 files changed, 68 insertions(+), 120 deletions(-) diff --git a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs index be3a35ac36..0d89518df1 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs @@ -5,6 +5,17 @@ use wasmtime::{Linker, Module, Store}; use wasmtime_wasi::sync::{Wasi, WasiCtxBuilder}; pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> anyhow::Result<()> { + run(data, bin_name, workspace, false) +} +pub fn instantiate_inherit_stdio( + data: &[u8], + bin_name: &str, + workspace: Option<&Path>, +) -> anyhow::Result<()> { + run(data, bin_name, workspace, true) +} + +fn run(data: &[u8], bin_name: &str, workspace: Option<&Path>, stdio: bool) -> anyhow::Result<()> { let stdout = WritePipe::new_in_memory(); let stderr = WritePipe::new_in_memory(); @@ -15,6 +26,10 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any // Additionally register any preopened directories if we have them. let mut builder = WasiCtxBuilder::new(); + if stdio { + builder = builder.inherit_stdio(); + } + builder = builder .arg(bin_name)? .arg(".")? @@ -26,24 +41,8 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?; builder = builder.preopened_dir(preopen_dir, ".")?; } - - #[cfg(windows)] - { - builder = builder - .env("ERRNO_MODE_WINDOWS", "1")? - .env("NO_DANGLING_FILESYSTEM", "1")? - .env("NO_FD_ALLOCATE", "1")? - .env("NO_RENAME_DIR_TO_EMPTY_DIR", "1")? - } - #[cfg(all(unix, not(target_os = "macos")))] - { - builder = builder.env("ERRNO_MODE_UNIX", "1")?; - } - #[cfg(target_os = "macos")] - { - builder = builder - .env("ERRNO_MODE_MACOS", "1")? - .env("NO_FD_ALLOCATE", "1")?; + for (var, val) in super::test_suite_environment() { + builder = builder.env(var, val)?; } // cap-std-sync does not yet support the sync family of fdflags @@ -82,41 +81,3 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any } } } - -pub fn instantiate_inherit_stdio( - data: &[u8], - bin_name: &str, - workspace: Option<&Path>, -) -> anyhow::Result<()> { - let r = { - let store = Store::default(); - - // Create our wasi context. - // Additionally register any preopened directories if we have them. - let mut builder = WasiCtxBuilder::new(); - - builder = builder.arg(bin_name)?.arg(".")?.inherit_stdio(); - - if let Some(workspace) = workspace { - println!("preopen: {:?}", workspace); - let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?; - builder = builder.preopened_dir(preopen_dir, ".")?; - } - - let snapshot1 = Wasi::new(&store, builder.build()?); - - let mut linker = Linker::new(&store); - - snapshot1.add_to_linker(&mut linker)?; - - let module = Module::new(store.engine(), &data).context("failed to create wasm module")?; - let instance = linker.instantiate(&module)?; - let start = instance.get_typed_func::<(), ()>("_start")?; - start.call(()).map_err(anyhow::Error::from) - }; - - match r { - Ok(()) => Ok(()), - Err(trap) => Err(trap.context(format!("error while testing Wasm module '{}'", bin_name,))), - } -} diff --git a/crates/test-programs/tests/wasm_tests/runtime/mod.rs b/crates/test-programs/tests/wasm_tests/runtime/mod.rs index caabcf7b30..0ef4f5d725 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/mod.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/mod.rs @@ -1,2 +1,34 @@ pub mod cap_std_sync; pub mod tokio; + +// Configure the test suite environment. +// Test programs use these environment variables to determine what behavior +// is expected: different errnos are expected on windows, mac, and other unixes, +// and other filesystem operations are supported or not. +pub fn test_suite_environment() -> &'static [(&str, &str)] { + #[cfg(windows)] + { + &[ + ("ERRNO_MODE_WINDOWS", "1"), + // Windows does not support dangling links or symlinks in the filesystem. + ("NO_DANGLING_FILESYSTEM", "1"), + // Windows does not support fd_allocate. + ("NO_FD_ALLOCATE", "1"), + // Windows does not support renaming a directory to an empty directory - + // empty directory must be deleted. + ("NO_RENAME_DIR_TO_EMPTY_DIR", "1"), + ] + } + #[cfg(all(unix, not(target_os = "macos")))] + { + &[("ERRNO_MODE_UNIX", "1")] + } + #[cfg(target_os = "macos")] + { + &[ + ("ERRNO_MODE_MACOS", "1"), + // MacOS does not support fd_allocate + ("NO_FD_ALLOCATE", "1"), + ] + } +} diff --git a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs index 88bdb380a4..ba5fcf16db 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs @@ -5,6 +5,17 @@ use wasmtime::{Config, Engine, Linker, Module, Store}; use wasmtime_wasi::tokio::{Wasi, WasiCtxBuilder}; pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> anyhow::Result<()> { + run(data, bin_name, workspace, false) +} +pub fn instantiate_inherit_stdio( + data: &[u8], + bin_name: &str, + workspace: Option<&Path>, +) -> anyhow::Result<()> { + run(data, bin_name, workspace, true) +} + +fn run(data: &[u8], bin_name: &str, workspace: Option<&Path>, stdio: bool) -> anyhow::Result<()> { let stdout = WritePipe::new_in_memory(); let stdout_ = stdout.clone(); let stderr = WritePipe::new_in_memory(); @@ -20,9 +31,12 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any let store = Store::new(&engine); // Create our wasi context. - // Additionally register any preopened directories if we have them. let mut builder = WasiCtxBuilder::new(); + if stdio { + builder = builder.inherit_stdio(); + } + builder = builder .arg(bin_name)? .arg(".")? @@ -35,26 +49,12 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any builder = builder.preopened_dir(preopen_dir, ".")?; } - #[cfg(windows)] - { - builder = builder - .env("ERRNO_MODE_WINDOWS", "1")? - .env("NO_DANGLING_FILESYSTEM", "1")? - .env("NO_FD_ALLOCATE", "1")? - .env("NO_RENAME_DIR_TO_EMPTY_DIR", "1")? - } - #[cfg(all(unix, not(target_os = "macos")))] - { - builder = builder.env("ERRNO_MODE_UNIX", "1")?; - } - #[cfg(target_os = "macos")] - { - builder = builder - .env("ERRNO_MODE_MACOS", "1")? - .env("NO_FD_ALLOCATE", "1")?; + for (var, val) in super::test_suite_environment() { + builder = builder.env(var, val)?; } - // cap-std-sync does not yet support the sync family of fdflags + // tokio does not yet support the sync family of fdflags, because cap-std-sync + // does not. builder = builder.env("NO_FDFLAGS_SYNC_SUPPORT", "1")?; Wasi::set_context(&store, builder.build()?) @@ -89,48 +89,3 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any } } } - -pub fn instantiate_inherit_stdio( - data: &[u8], - bin_name: &str, - workspace: Option<&Path>, -) -> anyhow::Result<()> { - let r = tokio::runtime::Runtime::new() - .expect("create runtime") - .block_on(async { - let mut config = Config::new(); - config.async_support(true); - config.consume_fuel(true); - Wasi::add_to_config(&mut config); - let engine = Engine::new(&config)?; - let store = Store::new(&engine); - - // Create our wasi context. - // Additionally register any preopened directories if we have them. - let mut builder = WasiCtxBuilder::new(); - - builder = builder.arg(bin_name)?.arg(".")?.inherit_stdio(); - - if let Some(workspace) = workspace { - println!("preopen: {:?}", workspace); - let preopen_dir = unsafe { cap_std::fs::Dir::open_ambient_dir(workspace) }?; - builder = builder.preopened_dir(preopen_dir, ".")?; - } - - store.out_of_fuel_async_yield(u32::MAX, 10000); - Wasi::set_context(&store, builder.build()?) - .map_err(|_| anyhow::anyhow!("wasi set_context failed"))?; - - let module = - Module::new(store.engine(), &data).context("failed to create wasm module")?; - let linker = Linker::new(&store); - let instance = linker.instantiate_async(&module).await?; - let start = instance.get_typed_func::<(), ()>("_start")?; - start.call_async(()).await.map_err(anyhow::Error::from) - }); - - match r { - Ok(()) => Ok(()), - Err(trap) => Err(trap.context(format!("error while testing Wasm module '{}'", bin_name,))), - } -} From f3ffd74566ba0e0357a3a70ca6f499f662a7dfd2 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 11:19:25 -0700 Subject: [PATCH 55/72] dont depend on wiggle for just the one func --- crates/wasi-common/cap-std-sync/Cargo.toml | 2 +- crates/wasi-common/cap-std-sync/src/dir.rs | 2 +- crates/wasi-common/cap-std-sync/src/file.rs | 2 +- crates/wasi-common/cap-std-sync/src/sched/unix.rs | 2 +- crates/wasi-common/cap-std-sync/src/sched/windows.rs | 2 +- crates/wasi-common/cap-std-sync/src/stdio.rs | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/wasi-common/cap-std-sync/Cargo.toml b/crates/wasi-common/cap-std-sync/Cargo.toml index aa85bf9d55..46b2dd6976 100644 --- a/crates/wasi-common/cap-std-sync/Cargo.toml +++ b/crates/wasi-common/cap-std-sync/Cargo.toml @@ -13,7 +13,7 @@ include = ["src/**/*", "LICENSE" ] [dependencies] wasi-common = { path = "../", version = "0.26.0" } -wiggle = { path = "../../wiggle", version = "0.26.0" } +async_trait = "0.1" anyhow = "1.0" cap-std = "0.13.7" cap-fs-ext = "0.13.7" diff --git a/crates/wasi-common/cap-std-sync/src/dir.rs b/crates/wasi-common/cap-std-sync/src/dir.rs index c52f5d8e1b..de924e3c4e 100644 --- a/crates/wasi-common/cap-std-sync/src/dir.rs +++ b/crates/wasi-common/cap-std-sync/src/dir.rs @@ -106,7 +106,7 @@ impl Dir { } } -#[wiggle::async_trait] +#[async_trait::async_trait(?Send)] impl WasiDir for Dir { fn as_any(&self) -> &dyn Any { self diff --git a/crates/wasi-common/cap-std-sync/src/file.rs b/crates/wasi-common/cap-std-sync/src/file.rs index 751c5f21b1..0105d61583 100644 --- a/crates/wasi-common/cap-std-sync/src/file.rs +++ b/crates/wasi-common/cap-std-sync/src/file.rs @@ -20,7 +20,7 @@ impl File { } } -#[wiggle::async_trait] +#[async_trait::async_trait(?Send)] impl WasiFile for File { fn as_any(&self) -> &dyn Any { self diff --git a/crates/wasi-common/cap-std-sync/src/sched/unix.rs b/crates/wasi-common/cap-std-sync/src/sched/unix.rs index 94a8614a60..1c1227bd6a 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/unix.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/unix.rs @@ -20,7 +20,7 @@ impl SyncSched { } } -#[wiggle::async_trait] +#[async_trait::async_trait(?Send)] impl WasiSched for SyncSched { async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { if poll.is_empty() { diff --git a/crates/wasi-common/cap-std-sync/src/sched/windows.rs b/crates/wasi-common/cap-std-sync/src/sched/windows.rs index a98e558a7b..3d5fdd37ad 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/windows.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/windows.rs @@ -21,7 +21,7 @@ impl SyncSched { } } -#[wiggle::async_trait] +#[async_trait::async_trait(?Send)] impl WasiSched for SyncSched { async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { if poll.is_empty() { diff --git a/crates/wasi-common/cap-std-sync/src/stdio.rs b/crates/wasi-common/cap-std-sync/src/stdio.rs index d86d181399..c6afa8f9b4 100644 --- a/crates/wasi-common/cap-std-sync/src/stdio.rs +++ b/crates/wasi-common/cap-std-sync/src/stdio.rs @@ -22,7 +22,7 @@ pub fn stdin() -> Stdin { Stdin(std::io::stdin()) } -#[wiggle::async_trait] +#[async_trait::async_trait(?Send)] impl WasiFile for Stdin { fn as_any(&self) -> &dyn Any { self @@ -125,7 +125,7 @@ impl AsRawFd for Stdin { macro_rules! wasi_file_write_impl { ($ty:ty) => { - #[wiggle::async_trait] + #[async_trait::async_trait(?Send)] impl WasiFile for $ty { fn as_any(&self) -> &dyn Any { self From 76be1959c4fab304662fca000e7b56fdaca97fb1 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 11:20:38 -0700 Subject: [PATCH 56/72] explain! --- crates/wasi-common/tokio/src/sched/unix.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/wasi-common/tokio/src/sched/unix.rs b/crates/wasi-common/tokio/src/sched/unix.rs index e3028cf611..35e0234f77 100644 --- a/crates/wasi-common/tokio/src/sched/unix.rs +++ b/crates/wasi-common/tokio/src/sched/unix.rs @@ -28,7 +28,8 @@ impl<'a, T> Future for FirstReady<'a, T> { match f.as_mut().poll(cx) { FPoll::Ready(r) => match result { // First ready gets to set the result. But, continue the loop so all futures - // get the opportunity to become ready. + // which are ready simultaneously (often on first poll) get to report their + // readiness. FPoll::Pending => { result = FPoll::Ready(r); } From e50f1b24a92dbe334945ce3ae084def84aaf2818 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 11:24:48 -0700 Subject: [PATCH 57/72] better name and comment --- crates/wasi-common/tokio/src/dir.rs | 33 +++++++++-------- crates/wasi-common/tokio/src/file.rs | 36 +++++++++---------- crates/wasi-common/tokio/src/lib.rs | 13 ++++--- crates/wasi-common/tokio/src/sched/windows.rs | 4 +-- 4 files changed, 48 insertions(+), 38 deletions(-) diff --git a/crates/wasi-common/tokio/src/dir.rs b/crates/wasi-common/tokio/src/dir.rs index 5ba7d985ed..a70b44e182 100644 --- a/crates/wasi-common/tokio/src/dir.rs +++ b/crates/wasi-common/tokio/src/dir.rs @@ -1,4 +1,4 @@ -use crate::{asyncify, file::File}; +use crate::{block_on_dummy_executor, file::File}; use std::any::Any; use std::path::PathBuf; use wasi_common::{ @@ -29,7 +29,7 @@ impl WasiDir for Dir { write: bool, fdflags: FdFlags, ) -> Result, Error> { - let f = asyncify(move || async move { + let f = block_on_dummy_executor(move || async move { self.0 .open_file_(symlink_follow, path, oflags, read, write, fdflags) })?; @@ -37,12 +37,13 @@ impl WasiDir for Dir { } async fn open_dir(&self, symlink_follow: bool, path: &str) -> Result, Error> { - let d = asyncify(move || async move { self.0.open_dir_(symlink_follow, path) })?; + let d = + block_on_dummy_executor(move || async move { self.0.open_dir_(symlink_follow, path) })?; Ok(Box::new(Dir(d))) } async fn create_dir(&self, path: &str) -> Result<(), Error> { - asyncify(|| self.0.create_dir(path)) + block_on_dummy_executor(|| self.0.create_dir(path)) } async fn readdir( &self, @@ -56,32 +57,32 @@ impl WasiDir for Dir { } } - let inner = asyncify(move || self.0.readdir(cursor))?; + let inner = block_on_dummy_executor(move || self.0.readdir(cursor))?; Ok(Box::new(I(inner))) } async fn symlink(&self, src_path: &str, dest_path: &str) -> Result<(), Error> { - asyncify(move || self.0.symlink(src_path, dest_path)) + block_on_dummy_executor(move || self.0.symlink(src_path, dest_path)) } async fn remove_dir(&self, path: &str) -> Result<(), Error> { - asyncify(move || self.0.remove_dir(path)) + block_on_dummy_executor(move || self.0.remove_dir(path)) } async fn unlink_file(&self, path: &str) -> Result<(), Error> { - asyncify(move || self.0.unlink_file(path)) + block_on_dummy_executor(move || self.0.unlink_file(path)) } async fn read_link(&self, path: &str) -> Result { - asyncify(move || self.0.read_link(path)) + block_on_dummy_executor(move || self.0.read_link(path)) } async fn get_filestat(&self) -> Result { - asyncify(|| self.0.get_filestat()) + block_on_dummy_executor(|| self.0.get_filestat()) } async fn get_path_filestat( &self, path: &str, follow_symlinks: bool, ) -> Result { - asyncify(move || self.0.get_path_filestat(path, follow_symlinks)) + block_on_dummy_executor(move || self.0.get_path_filestat(path, follow_symlinks)) } async fn rename( &self, @@ -93,7 +94,9 @@ impl WasiDir for Dir { .as_any() .downcast_ref::() .ok_or(Error::badf().context("failed downcast to tokio Dir"))?; - asyncify(move || async move { self.0.rename_(src_path, &dest_dir.0, dest_path) }) + block_on_dummy_executor( + move || async move { self.0.rename_(src_path, &dest_dir.0, dest_path) }, + ) } async fn hard_link( &self, @@ -105,7 +108,9 @@ impl WasiDir for Dir { .as_any() .downcast_ref::() .ok_or(Error::badf().context("failed downcast to tokio Dir"))?; - asyncify(move || async move { self.0.hard_link_(src_path, &target_dir.0, target_path) }) + block_on_dummy_executor(move || async move { + self.0.hard_link_(src_path, &target_dir.0, target_path) + }) } async fn set_times( &self, @@ -114,7 +119,7 @@ impl WasiDir for Dir { mtime: Option, follow_symlinks: bool, ) -> Result<(), Error> { - asyncify(move || self.0.set_times(path, atime, mtime, follow_symlinks)) + block_on_dummy_executor(move || self.0.set_times(path, atime, mtime, follow_symlinks)) } } diff --git a/crates/wasi-common/tokio/src/file.rs b/crates/wasi-common/tokio/src/file.rs index 82c8d965ac..5907aa7f53 100644 --- a/crates/wasi-common/tokio/src/file.rs +++ b/crates/wasi-common/tokio/src/file.rs @@ -1,4 +1,4 @@ -use crate::asyncify; +use crate::block_on_dummy_executor; use std::any::Any; use std::io; #[cfg(windows)] @@ -45,70 +45,70 @@ macro_rules! wasi_file_impl { self } async fn datasync(&self) -> Result<(), Error> { - asyncify(|| self.0.datasync()) + block_on_dummy_executor(|| self.0.datasync()) } async fn sync(&self) -> Result<(), Error> { - asyncify(|| self.0.sync()) + block_on_dummy_executor(|| self.0.sync()) } async fn get_filetype(&self) -> Result { - asyncify(|| self.0.get_filetype()) + block_on_dummy_executor(|| self.0.get_filetype()) } async fn get_fdflags(&self) -> Result { - asyncify(|| self.0.get_fdflags()) + block_on_dummy_executor(|| self.0.get_fdflags()) } async fn set_fdflags(&mut self, fdflags: FdFlags) -> Result<(), Error> { - asyncify(|| self.0.set_fdflags(fdflags)) + block_on_dummy_executor(|| self.0.set_fdflags(fdflags)) } async fn get_filestat(&self) -> Result { - asyncify(|| self.0.get_filestat()) + block_on_dummy_executor(|| self.0.get_filestat()) } async fn set_filestat_size(&self, size: u64) -> Result<(), Error> { - asyncify(move || self.0.set_filestat_size(size)) + block_on_dummy_executor(move || self.0.set_filestat_size(size)) } async fn advise(&self, offset: u64, len: u64, advice: Advice) -> Result<(), Error> { - asyncify(move || self.0.advise(offset, len, advice)) + block_on_dummy_executor(move || self.0.advise(offset, len, advice)) } async fn allocate(&self, offset: u64, len: u64) -> Result<(), Error> { - asyncify(move || self.0.allocate(offset, len)) + block_on_dummy_executor(move || self.0.allocate(offset, len)) } async fn read_vectored<'a>( &self, bufs: &mut [io::IoSliceMut<'a>], ) -> Result { - asyncify(move || self.0.read_vectored(bufs)) + block_on_dummy_executor(move || self.0.read_vectored(bufs)) } async fn read_vectored_at<'a>( &self, bufs: &mut [io::IoSliceMut<'a>], offset: u64, ) -> Result { - asyncify(move || self.0.read_vectored_at(bufs, offset)) + block_on_dummy_executor(move || self.0.read_vectored_at(bufs, offset)) } async fn write_vectored<'a>(&self, bufs: &[io::IoSlice<'a>]) -> Result { - asyncify(move || self.0.write_vectored(bufs)) + block_on_dummy_executor(move || self.0.write_vectored(bufs)) } async fn write_vectored_at<'a>( &self, bufs: &[io::IoSlice<'a>], offset: u64, ) -> Result { - asyncify(move || self.0.write_vectored_at(bufs, offset)) + block_on_dummy_executor(move || self.0.write_vectored_at(bufs, offset)) } async fn seek(&self, pos: std::io::SeekFrom) -> Result { - asyncify(move || self.0.seek(pos)) + block_on_dummy_executor(move || self.0.seek(pos)) } async fn peek(&self, buf: &mut [u8]) -> Result { - asyncify(move || self.0.peek(buf)) + block_on_dummy_executor(move || self.0.peek(buf)) } async fn set_times( &self, atime: Option, mtime: Option, ) -> Result<(), Error> { - asyncify(move || self.0.set_times(atime, mtime)) + block_on_dummy_executor(move || self.0.set_times(atime, mtime)) } async fn num_ready_bytes(&self) -> Result { - asyncify(|| self.0.num_ready_bytes()) + block_on_dummy_executor(|| self.0.num_ready_bytes()) } #[cfg(not(windows))] diff --git a/crates/wasi-common/tokio/src/lib.rs b/crates/wasi-common/tokio/src/lib.rs index bbe09dfe79..865b99445a 100644 --- a/crates/wasi-common/tokio/src/lib.rs +++ b/crates/wasi-common/tokio/src/lib.rs @@ -96,10 +96,15 @@ impl WasiCtxBuilder { } } -// This function takes the "async" code which is in fact blocking -// but always returns Poll::Ready, and executes it in a dummy executor -// on a blocking thread in the tokio thread pool. -pub(crate) fn asyncify<'a, F, Fut, T>(f: F) -> Result +// Much of this crate is implemented in terms of `async` methods from the +// wasi-cap-std-sync crate. These methods may be async in signature, however, +// they are synchronous in implementation (always Poll::Ready on first poll) +// and perform blocking syscalls. +// +// This function takes this blocking code and executes it using a dummy executor +// to assert its immediate readiness. We tell tokio this is a blocking operation +// with the block_in_place function. +pub(crate) fn block_on_dummy_executor<'a, F, Fut, T>(f: F) -> Result where F: FnOnce() -> Fut + Send + 'a, Fut: Future>, diff --git a/crates/wasi-common/tokio/src/sched/windows.rs b/crates/wasi-common/tokio/src/sched/windows.rs index 7228d4e395..f484056e8d 100644 --- a/crates/wasi-common/tokio/src/sched/windows.rs +++ b/crates/wasi-common/tokio/src/sched/windows.rs @@ -1,4 +1,4 @@ -use crate::asyncify; +use crate::block_on_dummy_executor; use anyhow::Context; use std::ops::Deref; use std::os::windows::io::{AsRawHandle, RawHandle}; @@ -16,7 +16,7 @@ use wasi_common::{ }; pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { - asyncify(move || poll_oneoff_(poll)) + block_on_dummy_executor(move || poll_oneoff_(poll)) } async fn poll_oneoff_<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { From b0335d3ddf1d87568e9175df5c9d32f967506879 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 14:27:28 -0700 Subject: [PATCH 58/72] fixes to example --- examples/tokio/main.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/tokio/main.rs b/examples/tokio/main.rs index 03e83b4ac0..b8b0548953 100644 --- a/examples/tokio/main.rs +++ b/examples/tokio/main.rs @@ -79,6 +79,10 @@ impl Inputs { fn run_wasm(inputs: Inputs) -> impl Future> { use std::pin::Pin; use std::task::{Context, Poll}; + // IMPORTANT: The current wasmtime API is very challenging to use safely + // on an async runtime. This RFC describes a redesign of the API that will + // resolve these safety issues: + // https://github.com/alexcrichton/rfcs-2/blob/new-api/accepted/new-api.md // This is a "marker type future" which simply wraps some other future and // the only purpose it serves is to forward the implementation of `Future` @@ -151,10 +155,8 @@ async fn _run_wasm(inputs: Inputs) -> Result<(), Error> { // Instantiate let instance = linker.instantiate_async(&inputs.env.module).await?; instance - .get_export("_start") + .get_typed_func("_start") .ok_or_else(|| anyhow!("wasm is a wasi command with export _start"))? - .into_func() - .ok_or_else(|| anyhow!("_start is a func"))? .call_async(&[]) .await?; From 3d9b98f1df060343fac6c12d764faec5b20ea9ef Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 14:34:30 -0700 Subject: [PATCH 59/72] fix example --- Cargo.lock | 2 +- crates/wasi-common/cap-std-sync/Cargo.toml | 2 +- examples/tokio/main.rs | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ba3fe8586..2f98977231 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3135,6 +3135,7 @@ name = "wasi-cap-std-sync" version = "0.26.0" dependencies = [ "anyhow", + "async-trait", "bitflags", "cap-fs-ext", "cap-rand", @@ -3148,7 +3149,6 @@ dependencies = [ "tracing", "unsafe-io", "wasi-common", - "wiggle", "winapi", ] diff --git a/crates/wasi-common/cap-std-sync/Cargo.toml b/crates/wasi-common/cap-std-sync/Cargo.toml index 46b2dd6976..eab62d0721 100644 --- a/crates/wasi-common/cap-std-sync/Cargo.toml +++ b/crates/wasi-common/cap-std-sync/Cargo.toml @@ -13,7 +13,7 @@ include = ["src/**/*", "LICENSE" ] [dependencies] wasi-common = { path = "../", version = "0.26.0" } -async_trait = "0.1" +async-trait = "0.1" anyhow = "1.0" cap-std = "0.13.7" cap-fs-ext = "0.13.7" diff --git a/examples/tokio/main.rs b/examples/tokio/main.rs index b8b0548953..491fe754db 100644 --- a/examples/tokio/main.rs +++ b/examples/tokio/main.rs @@ -155,9 +155,8 @@ async fn _run_wasm(inputs: Inputs) -> Result<(), Error> { // Instantiate let instance = linker.instantiate_async(&inputs.env.module).await?; instance - .get_typed_func("_start") - .ok_or_else(|| anyhow!("wasm is a wasi command with export _start"))? - .call_async(&[]) + .get_typed_func::<(), ()>("_start")? + .call_async(()) .await?; Ok(()) From ff8bdc390b4f16c3e05637a462893de7e7e188f3 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 15:45:54 -0700 Subject: [PATCH 60/72] reuse cap-std-syncs windows scheduler without copypaste --- crates/wasi-common/cap-std-sync/src/sched.rs | 35 ++- .../cap-std-sync/src/sched/unix.rs | 167 ++++++------- .../cap-std-sync/src/sched/windows.rs | 201 ++++++++-------- crates/wasi-common/tokio/src/sched/windows.rs | 219 +----------------- 4 files changed, 212 insertions(+), 410 deletions(-) diff --git a/crates/wasi-common/cap-std-sync/src/sched.rs b/crates/wasi-common/cap-std-sync/src/sched.rs index 43af68a952..cbda527109 100644 --- a/crates/wasi-common/cap-std-sync/src/sched.rs +++ b/crates/wasi-common/cap-std-sync/src/sched.rs @@ -1,15 +1,40 @@ #[cfg(unix)] -mod unix; +pub mod unix; #[cfg(unix)] -pub use unix::*; +pub use unix::poll_oneoff; #[cfg(windows)] -mod windows; +pub mod windows; #[cfg(windows)] -pub use windows::*; +pub use windows::poll_oneoff; -use wasi_common::sched::WasiSched; +use std::thread; +use std::time::Duration; +use wasi_common::{ + sched::{Poll, WasiSched}, + Error, +}; +pub struct SyncSched {} +impl SyncSched { + pub fn new() -> Self { + Self {} + } +} +#[async_trait::async_trait(?Send)] +impl WasiSched for SyncSched { + async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { + poll_oneoff(poll).await + } + async fn sched_yield(&self) -> Result<(), Error> { + thread::yield_now(); + Ok(()) + } + async fn sleep(&self, duration: Duration) -> Result<(), Error> { + std::thread::sleep(duration); + Ok(()) + } +} pub fn sched_ctx() -> Box { Box::new(SyncSched::new()) } diff --git a/crates/wasi-common/cap-std-sync/src/sched/unix.rs b/crates/wasi-common/cap-std-sync/src/sched/unix.rs index 1c1227bd6a..7b232a4ed9 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/unix.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/unix.rs @@ -5,112 +5,93 @@ use wasi_common::{ file::WasiFile, sched::{ subscription::{RwEventFlags, Subscription}, - Poll, WasiSched, + Poll, }, Error, ErrorExt, }; use poll::{PollFd, PollFlags}; -pub struct SyncSched; - -impl SyncSched { - pub fn new() -> Self { - SyncSched +pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { + if poll.is_empty() { + return Ok(()); } -} + let mut pollfds = Vec::new(); + for s in poll.rw_subscriptions() { + match s { + Subscription::Read(f) => { + let raw_fd = wasi_file_raw_fd(f.file).ok_or( + Error::invalid_argument().context("read subscription fd downcast failed"), + )?; + pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLIN) }); + } -#[async_trait::async_trait(?Send)] -impl WasiSched for SyncSched { - async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { - if poll.is_empty() { - return Ok(()); + Subscription::Write(f) => { + let raw_fd = wasi_file_raw_fd(f.file).ok_or( + Error::invalid_argument().context("write subscription fd downcast failed"), + )?; + pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLOUT) }); + } + Subscription::MonotonicClock { .. } => unreachable!(), } - let mut pollfds = Vec::new(); - for s in poll.rw_subscriptions() { - match s { - Subscription::Read(f) => { - let raw_fd = wasi_file_raw_fd(f.file).ok_or( - Error::invalid_argument().context("read subscription fd downcast failed"), - )?; - pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLIN) }); - } + } - Subscription::Write(f) => { - let raw_fd = wasi_file_raw_fd(f.file).ok_or( - Error::invalid_argument().context("write subscription fd downcast failed"), - )?; - pollfds.push(unsafe { PollFd::new(raw_fd, PollFlags::POLLOUT) }); - } - Subscription::MonotonicClock { .. } => unreachable!(), - } - } - - let ready = loop { - let poll_timeout = if let Some(t) = poll.earliest_clock_deadline() { - let duration = t.duration_until().unwrap_or(Duration::from_secs(0)); - (duration.as_millis() + 1) // XXX try always rounding up? - .try_into() - .map_err(|_| Error::overflow().context("poll timeout"))? - } else { - libc::c_int::max_value() - }; - tracing::debug!( - poll_timeout = tracing::field::debug(poll_timeout), - poll_fds = tracing::field::debug(&pollfds), - "poll" - ); - match poll::poll(&mut pollfds, poll_timeout) { - Ok(ready) => break ready, - Err(_) => { - let last_err = std::io::Error::last_os_error(); - if last_err.raw_os_error().unwrap() == libc::EINTR { - continue; - } else { - return Err(last_err.into()); - } - } - } - }; - if ready > 0 { - for (rwsub, pollfd) in poll.rw_subscriptions().zip(pollfds.into_iter()) { - if let Some(revents) = pollfd.revents() { - let (nbytes, rwsub) = match rwsub { - Subscription::Read(sub) => { - let ready = sub.file.num_ready_bytes().await?; - (std::cmp::max(ready, 1), sub) - } - Subscription::Write(sub) => (0, sub), - _ => unreachable!(), - }; - if revents.contains(PollFlags::POLLNVAL) { - rwsub.error(Error::badf()); - } else if revents.contains(PollFlags::POLLERR) { - rwsub.error(Error::io()); - } else if revents.contains(PollFlags::POLLHUP) { - rwsub.complete(nbytes, RwEventFlags::HANGUP); - } else { - rwsub.complete(nbytes, RwEventFlags::empty()); - }; - } - } + let ready = loop { + let poll_timeout = if let Some(t) = poll.earliest_clock_deadline() { + let duration = t.duration_until().unwrap_or(Duration::from_secs(0)); + (duration.as_millis() + 1) // XXX try always rounding up? + .try_into() + .map_err(|_| Error::overflow().context("poll timeout"))? } else { - poll.earliest_clock_deadline() - .expect("timed out") - .result() - .expect("timer deadline is past") - .unwrap() + libc::c_int::max_value() + }; + tracing::debug!( + poll_timeout = tracing::field::debug(poll_timeout), + poll_fds = tracing::field::debug(&pollfds), + "poll" + ); + match poll::poll(&mut pollfds, poll_timeout) { + Ok(ready) => break ready, + Err(_) => { + let last_err = std::io::Error::last_os_error(); + if last_err.raw_os_error().unwrap() == libc::EINTR { + continue; + } else { + return Err(last_err.into()); + } + } } - Ok(()) - } - async fn sched_yield(&self) -> Result<(), Error> { - std::thread::yield_now(); - Ok(()) - } - async fn sleep(&self, duration: Duration) -> Result<(), Error> { - std::thread::sleep(duration); - Ok(()) + }; + if ready > 0 { + for (rwsub, pollfd) in poll.rw_subscriptions().zip(pollfds.into_iter()) { + if let Some(revents) = pollfd.revents() { + let (nbytes, rwsub) = match rwsub { + Subscription::Read(sub) => { + let ready = sub.file.num_ready_bytes().await?; + (std::cmp::max(ready, 1), sub) + } + Subscription::Write(sub) => (0, sub), + _ => unreachable!(), + }; + if revents.contains(PollFlags::POLLNVAL) { + rwsub.error(Error::badf()); + } else if revents.contains(PollFlags::POLLERR) { + rwsub.error(Error::io()); + } else if revents.contains(PollFlags::POLLHUP) { + rwsub.complete(nbytes, RwEventFlags::HANGUP); + } else { + rwsub.complete(nbytes, RwEventFlags::empty()); + }; + } + } + } else { + poll.earliest_clock_deadline() + .expect("timed out") + .result() + .expect("timer deadline is past") + .unwrap() } + Ok(()) } fn wasi_file_raw_fd(f: &dyn WasiFile) -> Option { diff --git a/crates/wasi-common/cap-std-sync/src/sched/windows.rs b/crates/wasi-common/cap-std-sync/src/sched/windows.rs index 3d5fdd37ad..135ec7b4a0 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/windows.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/windows.rs @@ -1,3 +1,13 @@ +// The windows scheduler is unmaintained and due for a rewrite. +// +// Rather than use a polling mechanism for file read/write readiness, +// it checks readiness just once, before sleeping for any timer subscriptions. +// Checking stdin readiness uses a worker thread which, once started, lives for the +// lifetime of the process. +// +// We suspect there are bugs in this scheduler, however, we have not +// taken the time to improve it. See bug #2880. + use anyhow::Context; use std::ops::Deref; use std::os::windows::io::{AsRawHandle, RawHandle}; @@ -9,130 +19,121 @@ use wasi_common::{ file::WasiFile, sched::{ subscription::{RwEventFlags, Subscription}, - Poll, WasiSched, + Poll, }, Error, ErrorExt, }; -pub struct SyncSched {} -impl SyncSched { - pub fn new() -> Self { - Self {} - } +pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { + poll_oneoff_(poll, wasi_file_raw_handle).await } -#[async_trait::async_trait(?Send)] -impl WasiSched for SyncSched { - async fn poll_oneoff<'a>(&self, poll: &mut Poll<'a>) -> Result<(), Error> { - if poll.is_empty() { - return Ok(()); - } +// For reuse by wasi-tokio, which has a different WasiFile -> RawHandle translator. +pub async fn poll_oneoff_<'a>( + poll: &mut Poll<'a>, + file_to_handle: impl Fn(&dyn WasiFile) -> Option, +) -> Result<(), Error> { + if poll.is_empty() { + return Ok(()); + } - let mut ready = false; - let waitmode = if let Some(t) = poll.earliest_clock_deadline() { - if let Some(duration) = t.duration_until() { - WaitMode::Timeout(duration) - } else { - WaitMode::Immediate - } + let mut ready = false; + let waitmode = if let Some(t) = poll.earliest_clock_deadline() { + if let Some(duration) = t.duration_until() { + WaitMode::Timeout(duration) } else { - if ready { - WaitMode::Immediate - } else { - WaitMode::Infinite - } - }; - - let mut stdin_read_subs = Vec::new(); - let mut immediate_reads = Vec::new(); - let mut immediate_writes = Vec::new(); - for s in poll.rw_subscriptions() { - match s { - Subscription::Read(r) => { - if r.file.as_any().is::() { - stdin_read_subs.push(r); - } else if wasi_file_raw_handle(r.file.deref()).is_some() { - immediate_reads.push(r); - } else { - return Err(Error::invalid_argument() - .context("read subscription fd downcast failed")); - } - } - Subscription::Write(w) => { - if wasi_file_raw_handle(w.file.deref()).is_some() { - immediate_writes.push(w); - } else { - return Err(Error::invalid_argument() - .context("write subscription fd downcast failed")); - } - } - Subscription::MonotonicClock { .. } => unreachable!(), - } + WaitMode::Immediate } + } else { + if ready { + WaitMode::Immediate + } else { + WaitMode::Infinite + } + }; - if !stdin_read_subs.is_empty() { - let state = STDIN_POLL - .lock() - .map_err(|_| Error::trap("failed to take lock of STDIN_POLL"))? - .poll(waitmode)?; - for readsub in stdin_read_subs.into_iter() { - match state { - PollState::Ready => { - readsub.complete(1, RwEventFlags::empty()); - ready = true; - } - PollState::NotReady | PollState::TimedOut => {} - PollState::Error(ref e) => { - // Unfortunately, we need to deliver the Error to each of the - // subscriptions, but there is no Clone on std::io::Error. So, we convert it to the - // kind, and then back to std::io::Error, and finally to anyhow::Error. - // When its time to turn this into an errno elsewhere, the error kind will - // be inspected. - let ekind = e.kind(); - let ioerror = std::io::Error::from(ekind); - readsub.error(ioerror.into()); - ready = true; - } + let mut stdin_read_subs = Vec::new(); + let mut immediate_reads = Vec::new(); + let mut immediate_writes = Vec::new(); + for s in poll.rw_subscriptions() { + match s { + Subscription::Read(r) => { + if r.file.as_any().is::() { + stdin_read_subs.push(r); + } else if file_to_handle(r.file.deref()).is_some() { + immediate_reads.push(r); + } else { + return Err( + Error::invalid_argument().context("read subscription fd downcast failed") + ); } } + Subscription::Write(w) => { + if wasi_file_raw_handle(w.file.deref()).is_some() { + immediate_writes.push(w); + } else { + return Err( + Error::invalid_argument().context("write subscription fd downcast failed") + ); + } + } + Subscription::MonotonicClock { .. } => unreachable!(), } - for r in immediate_reads { - match r.file.num_ready_bytes().await { - Ok(ready_bytes) => { - r.complete(ready_bytes, RwEventFlags::empty()); + } + + if !stdin_read_subs.is_empty() { + let state = STDIN_POLL + .lock() + .map_err(|_| Error::trap("failed to take lock of STDIN_POLL"))? + .poll(waitmode)?; + for readsub in stdin_read_subs.into_iter() { + match state { + PollState::Ready => { + readsub.complete(1, RwEventFlags::empty()); ready = true; } - Err(e) => { - r.error(e); + PollState::NotReady | PollState::TimedOut => {} + PollState::Error(ref e) => { + // Unfortunately, we need to deliver the Error to each of the + // subscriptions, but there is no Clone on std::io::Error. So, we convert it to the + // kind, and then back to std::io::Error, and finally to anyhow::Error. + // When its time to turn this into an errno elsewhere, the error kind will + // be inspected. + let ekind = e.kind(); + let ioerror = std::io::Error::from(ekind); + readsub.error(ioerror.into()); ready = true; } } } - for w in immediate_writes { - // Everything is always ready for writing, apparently? - w.complete(0, RwEventFlags::empty()); - ready = true; - } - - if !ready { - if let WaitMode::Timeout(duration) = waitmode { - thread::sleep(duration); + } + for r in immediate_reads { + match r.file.num_ready_bytes().await { + Ok(ready_bytes) => { + r.complete(ready_bytes, RwEventFlags::empty()); + ready = true; + } + Err(e) => { + r.error(e); + ready = true; } } + } + for w in immediate_writes { + // Everything is always ready for writing, apparently? + w.complete(0, RwEventFlags::empty()); + ready = true; + } - Ok(()) - } - async fn sched_yield(&self) -> Result<(), Error> { - thread::yield_now(); - Ok(()) - } - async fn sleep(&self, duration: Duration) -> Result<(), Error> { - std::thread::sleep(duration); - Ok(()) + if !ready { + if let WaitMode::Timeout(duration) = waitmode { + thread::sleep(duration); + } } + + Ok(()) } - -fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option { +pub fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option { let a = f.as_any(); if a.is::() { Some( diff --git a/crates/wasi-common/tokio/src/sched/windows.rs b/crates/wasi-common/tokio/src/sched/windows.rs index f484056e8d..d962675f45 100644 --- a/crates/wasi-common/tokio/src/sched/windows.rs +++ b/crates/wasi-common/tokio/src/sched/windows.rs @@ -1,124 +1,14 @@ use crate::block_on_dummy_executor; -use anyhow::Context; -use std::ops::Deref; use std::os::windows::io::{AsRawHandle, RawHandle}; -use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError}; -use std::sync::Mutex; -use std::thread; -use std::time::Duration; -use wasi_common::{ - file::WasiFile, - sched::{ - subscription::{RwEventFlags, Subscription}, - Poll, - }, - Error, ErrorExt, -}; +use wasi_cap_std_sync::sched::windows::poll_oneoff_; +use wasi_common::{file::WasiFile, sched::Poll, Error}; pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { - block_on_dummy_executor(move || poll_oneoff_(poll)) -} - -async fn poll_oneoff_<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { - if poll.is_empty() { - return Ok(()); - } - - let mut ready = false; - let waitmode = if let Some(t) = poll.earliest_clock_deadline() { - if let Some(duration) = t.duration_until() { - WaitMode::Timeout(duration) - } else { - WaitMode::Immediate - } - } else { - if ready { - WaitMode::Immediate - } else { - WaitMode::Infinite - } - }; - - let mut stdin_read_subs = Vec::new(); - let mut immediate_reads = Vec::new(); - let mut immediate_writes = Vec::new(); - for s in poll.rw_subscriptions() { - match s { - Subscription::Read(r) => { - if r.file.as_any().is::() { - stdin_read_subs.push(r); - } else if wasi_file_raw_handle(r.file.deref()).is_some() { - immediate_reads.push(r); - } else { - return Err( - Error::invalid_argument().context("read subscription fd downcast failed") - ); - } - } - Subscription::Write(w) => { - if wasi_file_raw_handle(w.file.deref()).is_some() { - immediate_writes.push(w); - } else { - return Err( - Error::invalid_argument().context("write subscription fd downcast failed") - ); - } - } - Subscription::MonotonicClock { .. } => unreachable!(), - } - } - - if !stdin_read_subs.is_empty() { - let state = STDIN_POLL - .lock() - .map_err(|_| Error::trap("failed to take lock of STDIN_POLL"))? - .poll(waitmode)?; - for readsub in stdin_read_subs.into_iter() { - match state { - PollState::Ready => { - readsub.complete(1, RwEventFlags::empty()); - ready = true; - } - PollState::NotReady | PollState::TimedOut => {} - PollState::Error(ref e) => { - // Unfortunately, we need to deliver the Error to each of the - // subscriptions, but there is no Clone on std::io::Error. So, we convert it to the - // kind, and then back to std::io::Error, and finally to anyhow::Error. - // When its time to turn this into an errno elsewhere, the error kind will - // be inspected. - let ekind = e.kind(); - let ioerror = std::io::Error::from(ekind); - readsub.error(ioerror.into()); - ready = true; - } - } - } - } - for r in immediate_reads { - match r.file.num_ready_bytes().await { - Ok(ready_bytes) => { - r.complete(ready_bytes, RwEventFlags::empty()); - ready = true; - } - Err(e) => { - r.error(e); - ready = true; - } - } - } - for w in immediate_writes { - // Everything is always ready for writing, apparently? - w.complete(0, RwEventFlags::empty()); - ready = true; - } - - if !ready { - if let WaitMode::Timeout(duration) = waitmode { - thread::sleep(duration); - } - } - - Ok(()) + // Tokio doesn't provide us the AsyncFd primitive on Windows, so instead + // we use the blocking poll_oneoff implementation from the wasi-cap-std-crate. + // We provide a function specific to this crate's WasiFile types for downcasting + // to a RawHandle. + block_on_dummy_executor(move || poll_oneoff_(poll, wasi_file_raw_handle)) } fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option { @@ -151,98 +41,3 @@ fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option { None } } - -enum PollState { - Ready, - NotReady, // Not ready, but did not wait - TimedOut, // Not ready, waited until timeout - Error(std::io::Error), -} - -#[derive(Copy, Clone)] -enum WaitMode { - Timeout(Duration), - Infinite, - Immediate, -} - -struct StdinPoll { - request_tx: Sender<()>, - notify_rx: Receiver, -} - -lazy_static::lazy_static! { - static ref STDIN_POLL: Mutex = StdinPoll::new(); -} - -impl StdinPoll { - pub fn new() -> Mutex { - let (request_tx, request_rx) = mpsc::channel(); - let (notify_tx, notify_rx) = mpsc::channel(); - thread::spawn(move || Self::event_loop(request_rx, notify_tx)); - Mutex::new(StdinPoll { - request_tx, - notify_rx, - }) - } - - // This function should not be used directly. - // Correctness of this function crucially depends on the fact that - // mpsc::Receiver is !Sync. - fn poll(&self, wait_mode: WaitMode) -> Result { - match self.notify_rx.try_recv() { - // Clean up possibly unread result from previous poll. - Ok(_) | Err(TryRecvError::Empty) => {} - Err(TryRecvError::Disconnected) => { - return Err(Error::trap("StdinPoll notify_rx channel closed")) - } - } - - // Notify the worker thread to poll stdin - self.request_tx - .send(()) - .context("request_tx channel closed")?; - - // Wait for the worker thread to send a readiness notification - match wait_mode { - WaitMode::Timeout(timeout) => match self.notify_rx.recv_timeout(timeout) { - Ok(r) => Ok(r), - Err(RecvTimeoutError::Timeout) => Ok(PollState::TimedOut), - Err(RecvTimeoutError::Disconnected) => { - Err(Error::trap("StdinPoll notify_rx channel closed")) - } - }, - WaitMode::Infinite => self - .notify_rx - .recv() - .context("StdinPoll notify_rx channel closed"), - WaitMode::Immediate => match self.notify_rx.try_recv() { - Ok(r) => Ok(r), - Err(TryRecvError::Empty) => Ok(PollState::NotReady), - Err(TryRecvError::Disconnected) => { - Err(Error::trap("StdinPoll notify_rx channel closed")) - } - }, - } - } - - fn event_loop(request_rx: Receiver<()>, notify_tx: Sender) -> ! { - use std::io::BufRead; - loop { - // Wait on a request: - request_rx.recv().expect("request_rx channel"); - // Wait for data to appear in stdin. If fill_buf returns any slice, it means - // that either: - // (a) there is some data in stdin, if non-empty, - // (b) EOF was recieved, if its empty - // Linux returns `POLLIN` in both cases, so we imitate this behavior. - let resp = match std::io::stdin().lock().fill_buf() { - Ok(_) => PollState::Ready, - Err(e) => PollState::Error(e), - }; - // Notify about data in stdin. If the read on this channel has timed out, the - // next poller will have to clean the channel. - notify_tx.send(resp).expect("notify_tx channel"); - } - } -} From add115ba00cf86aa21be15abc24b9be20b1556ce Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 15:53:23 -0700 Subject: [PATCH 61/72] fix --- crates/test-programs/tests/wasm_tests/runtime/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/test-programs/tests/wasm_tests/runtime/mod.rs b/crates/test-programs/tests/wasm_tests/runtime/mod.rs index 0ef4f5d725..9a4fe8202a 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/mod.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/mod.rs @@ -5,7 +5,7 @@ pub mod tokio; // Test programs use these environment variables to determine what behavior // is expected: different errnos are expected on windows, mac, and other unixes, // and other filesystem operations are supported or not. -pub fn test_suite_environment() -> &'static [(&str, &str)] { +pub fn test_suite_environment() -> &'static [(&'static str, &'static str)] { #[cfg(windows)] { &[ From b19d86268c35408fed0b23aa8a523e329e15a04e Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 16:19:33 -0700 Subject: [PATCH 62/72] fix test harness stdio --- .../tests/wasm_tests/runtime/cap_std_sync.rs | 19 ++++++++++++------- .../tests/wasm_tests/runtime/tokio.rs | 19 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs index 0d89518df1..32aa3cbedb 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs @@ -15,7 +15,12 @@ pub fn instantiate_inherit_stdio( run(data, bin_name, workspace, true) } -fn run(data: &[u8], bin_name: &str, workspace: Option<&Path>, stdio: bool) -> anyhow::Result<()> { +fn run( + data: &[u8], + bin_name: &str, + workspace: Option<&Path>, + inherit_stdio: bool, +) -> anyhow::Result<()> { let stdout = WritePipe::new_in_memory(); let stderr = WritePipe::new_in_memory(); @@ -26,15 +31,15 @@ fn run(data: &[u8], bin_name: &str, workspace: Option<&Path>, stdio: bool) -> an // Additionally register any preopened directories if we have them. let mut builder = WasiCtxBuilder::new(); - if stdio { + if inherit_stdio { builder = builder.inherit_stdio(); + } else { + builder = builder + .stdout(Box::new(stdout.clone())) + .stderr(Box::new(stderr.clone())); } - builder = builder - .arg(bin_name)? - .arg(".")? - .stdout(Box::new(stdout.clone())) - .stderr(Box::new(stderr.clone())); + builder = builder.arg(bin_name)?.arg(".")?; if let Some(workspace) = workspace { println!("preopen: {:?}", workspace); diff --git a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs index ba5fcf16db..28577015bc 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/tokio.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/tokio.rs @@ -15,7 +15,12 @@ pub fn instantiate_inherit_stdio( run(data, bin_name, workspace, true) } -fn run(data: &[u8], bin_name: &str, workspace: Option<&Path>, stdio: bool) -> anyhow::Result<()> { +fn run( + data: &[u8], + bin_name: &str, + workspace: Option<&Path>, + inherit_stdio: bool, +) -> anyhow::Result<()> { let stdout = WritePipe::new_in_memory(); let stdout_ = stdout.clone(); let stderr = WritePipe::new_in_memory(); @@ -33,15 +38,15 @@ fn run(data: &[u8], bin_name: &str, workspace: Option<&Path>, stdio: bool) -> an // Create our wasi context. let mut builder = WasiCtxBuilder::new(); - if stdio { + if inherit_stdio { builder = builder.inherit_stdio(); + } else { + builder = builder + .stdout(Box::new(stdout_.clone())) + .stderr(Box::new(stderr_.clone())); } - builder = builder - .arg(bin_name)? - .arg(".")? - .stdout(Box::new(stdout_)) - .stderr(Box::new(stderr_)); + builder = builder.arg(bin_name)?.arg(".")?; if let Some(workspace) = workspace { println!("preopen: {:?}", workspace); From 35a9d4e3c97361c638ce8358890bdf2ab25c0db9 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 16:21:43 -0700 Subject: [PATCH 63/72] less general mechanism for examples cargo feature --- crates/misc/run-examples/src/main.rs | 18 +++++------------- examples/tokio/CARGO_FEATURES | 1 - 2 files changed, 5 insertions(+), 14 deletions(-) delete mode 100644 examples/tokio/CARGO_FEATURES diff --git a/crates/misc/run-examples/src/main.rs b/crates/misc/run-examples/src/main.rs index 0bff7d5c22..362f9e1774 100644 --- a/crates/misc/run-examples/src/main.rs +++ b/crates/misc/run-examples/src/main.rs @@ -13,11 +13,7 @@ fn main() -> anyhow::Result<()> { continue; } - examples.insert(( - path.clone(), - path.file_stem().unwrap().to_str().unwrap().to_owned(), - dir, - )); + examples.insert((path.file_stem().unwrap().to_str().unwrap().to_owned(), dir)); } println!("======== Building libwasmtime.a ==========="); @@ -25,7 +21,7 @@ fn main() -> anyhow::Result<()> { .args(&["build"]) .current_dir("crates/c-api"))?; - for (example_path, example, is_dir) in examples { + for (example, is_dir) in examples { if example == "README" { continue; } @@ -51,13 +47,9 @@ fn main() -> anyhow::Result<()> { println!("======== Rust example `{}` ============", example); let mut cargo_cmd = Command::new("cargo"); cargo_cmd.arg("run").arg("--example").arg(&example); - if is_dir { - let mut features_path = std::path::PathBuf::from(example_path); - features_path.push("CARGO_FEATURES"); - if features_path.exists() { - let features = std::fs::read_to_string(features_path)?; - cargo_cmd.arg("--features").arg(features); - } + + if example.contains("tokio") { + cargo_cmd.arg("--features").arg("wasmtime-wasi/tokio"); } run(&mut cargo_cmd)?; diff --git a/examples/tokio/CARGO_FEATURES b/examples/tokio/CARGO_FEATURES deleted file mode 100644 index c1c1ff5101..0000000000 --- a/examples/tokio/CARGO_FEATURES +++ /dev/null @@ -1 +0,0 @@ -wasmtime-wasi/tokio From e9f410d1dbd2678bedcd746c7714b716a64e1356 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Thu, 6 May 2021 16:25:12 -0700 Subject: [PATCH 64/72] run_in_dummy_executor isnt unsafe, its just a bad idea --- crates/wasi-common/tokio/src/lib.rs | 2 +- crates/wiggle/src/lib.rs | 2 +- crates/wiggle/wasmtime/macro/src/lib.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/wasi-common/tokio/src/lib.rs b/crates/wasi-common/tokio/src/lib.rs index 865b99445a..e7dc7f42e6 100644 --- a/crates/wasi-common/tokio/src/lib.rs +++ b/crates/wasi-common/tokio/src/lib.rs @@ -110,5 +110,5 @@ where Fut: Future>, T: Send + 'static, { - tokio::task::block_in_place(move || unsafe { wiggle::run_in_dummy_executor(f()) }) + tokio::task::block_in_place(move || wiggle::run_in_dummy_executor(f())) } diff --git a/crates/wiggle/src/lib.rs b/crates/wiggle/src/lib.rs index 602a8c9d17..f05a65cf5e 100644 --- a/crates/wiggle/src/lib.rs +++ b/crates/wiggle/src/lib.rs @@ -954,7 +954,7 @@ impl From for Trap { } } -pub unsafe fn run_in_dummy_executor(future: F) -> F::Output { +pub fn run_in_dummy_executor(future: F) -> F::Output { use std::pin::Pin; use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; diff --git a/crates/wiggle/wasmtime/macro/src/lib.rs b/crates/wiggle/wasmtime/macro/src/lib.rs index 67c91817c5..01f8a8db48 100644 --- a/crates/wiggle/wasmtime/macro/src/lib.rs +++ b/crates/wiggle/wasmtime/macro/src/lib.rs @@ -320,7 +320,7 @@ fn generate_func( let #name_ident = wasmtime::Func::wrap( store, move |caller: wasmtime::Caller #(, #arg_decls)*| -> Result<#ret_ty, wasmtime::Trap> { - unsafe { #rt::run_in_dummy_executor(Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*)) } + #rt::run_in_dummy_executor(Self::#fn_ident(&caller, &mut my_ctx.borrow_mut() #(, #arg_names)*)) } ); }); @@ -372,7 +372,7 @@ fn generate_func( .store() .get::>>() .ok_or_else(|| wasmtime::Trap::new("context is missing in the store"))?; - unsafe { #rt::run_in_dummy_executor(Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*)) } + #rt::run_in_dummy_executor(Self::#fn_ident(&caller, &mut ctx.borrow_mut() #(, #arg_names)*)) }, ); } From 9b0927293675d29854051538aa10987e76b4b3f4 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 7 May 2021 12:19:51 -0700 Subject: [PATCH 65/72] poll_oneoff: bound tests for time, rather than instant completion --- crates/wasi-common/tokio/tests/poll_oneoff.rs | 87 ++++++++++++++++++- 1 file changed, 85 insertions(+), 2 deletions(-) diff --git a/crates/wasi-common/tokio/tests/poll_oneoff.rs b/crates/wasi-common/tokio/tests/poll_oneoff.rs index 10ac82b9be..818f7e86c0 100644 --- a/crates/wasi-common/tokio/tests/poll_oneoff.rs +++ b/crates/wasi-common/tokio/tests/poll_oneoff.rs @@ -1,13 +1,17 @@ use anyhow::{Context, Error}; +use cap_std::time::Duration; +use std::collections::HashMap; use wasi_common::{ file::{FdFlags, OFlags}, sched::{Poll, RwEventFlags, SubscriptionResult, Userdata}, - WasiDir, + WasiDir, WasiFile, }; -use wasi_tokio::{sched::poll_oneoff, Dir}; +use wasi_tokio::{clocks_ctx, sched::poll_oneoff, Dir}; #[tokio::test(flavor = "multi_thread")] async fn empty_file_readable() -> Result<(), Error> { + let clocks = clocks_ctx(); + let workspace = unsafe { cap_tempfile::tempdir().expect("create tempdir") }; workspace.create_dir("d").context("create dir")?; let d = workspace.open_dir("d").context("open dir")?; @@ -30,6 +34,17 @@ async fn empty_file_readable() -> Result<(), Error> { let mut poll = Poll::new(); poll.subscribe_read(&mut *f, Userdata::from(123)); + // Timeout bounds time in poll_oneoff + poll.subscribe_monotonic_clock( + &*clocks.monotonic, + clocks + .monotonic + .now(clocks.monotonic.resolution()) + .checked_add(Duration::from_millis(1)) + .unwrap(), + clocks.monotonic.resolution(), + Userdata::from(0), + ); poll_oneoff(&mut poll).await?; let events = poll.results(); @@ -48,6 +63,8 @@ async fn empty_file_readable() -> Result<(), Error> { #[tokio::test(flavor = "multi_thread")] async fn empty_file_writable() -> Result<(), Error> { + let clocks = clocks_ctx(); + let workspace = unsafe { cap_tempfile::tempdir().expect("create tempdir") }; workspace.create_dir("d").context("create dir")?; let d = workspace.open_dir("d").context("open dir")?; @@ -60,6 +77,17 @@ async fn empty_file_writable() -> Result<(), Error> { let mut poll = Poll::new(); poll.subscribe_write(&mut *writable_f, Userdata::from(123)); + // Timeout bounds time in poll_oneoff + poll.subscribe_monotonic_clock( + &*clocks.monotonic, + clocks + .monotonic + .now(clocks.monotonic.resolution()) + .checked_add(Duration::from_millis(1)) + .unwrap(), + clocks.monotonic.resolution(), + Userdata::from(0), + ); poll_oneoff(&mut poll).await?; let events = poll.results(); @@ -75,3 +103,58 @@ async fn empty_file_writable() -> Result<(), Error> { Ok(()) } + +#[tokio::test(flavor = "multi_thread")] +async fn stdio_readable() -> Result<(), Error> { + let clocks = clocks_ctx(); + + let deadline = clocks + .monotonic + .now(clocks.monotonic.resolution()) + .checked_add(Duration::from_millis(1)) + .unwrap(); + + let mut waiting_on: HashMap> = vec![ + ( + 1, + Box::new(wasi_tokio::stdio::stdout()) as Box, + ), + (2, Box::new(wasi_tokio::stdio::stderr())), + ] + .into_iter() + .collect(); + + while !waiting_on.is_empty() { + let mut poll = Poll::new(); + + for (ix, file) in waiting_on.iter_mut() { + poll.subscribe_write(&mut **file, Userdata::from(*ix)); + } + // Timeout bounds time in poll_oneoff + poll.subscribe_monotonic_clock( + &*clocks.monotonic, + deadline, + clocks.monotonic.resolution(), + Userdata::from(999), + ); + poll_oneoff(&mut poll).await?; + let events = poll.results(); + + for e in events { + match e { + (SubscriptionResult::Write(Ok(_)), ud) => { + let _ = waiting_on.remove(&u64::from(ud)); + } + (SubscriptionResult::Write(Err(_)), ud) => { + panic!("error on ix {}", u64::from(ud)) + } + (SubscriptionResult::Read { .. }, _) => unreachable!(), + (SubscriptionResult::MonotonicClock { .. }, _) => { + panic!("timed out before stdin and stdout ready for reading") + } + } + } + } + + Ok(()) +} From ee8a8a2a90d614f41693cca96d1f4575b1f1e0e4 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 7 May 2021 14:27:23 -0700 Subject: [PATCH 66/72] poll_oneoff_stdio test: loosen up contract permit both readable events to be delivered in very short interval, rather than simultaneously. --- .../wasi-tests/src/bin/poll_oneoff_stdio.rs | 98 ++++++++++--------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs index 19f60d076c..3134c10cf8 100644 --- a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs +++ b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::mem::MaybeUninit; use wasi_tests::{assert_errno, STDERR_FD, STDIN_FD, STDOUT_FD}; @@ -46,7 +47,7 @@ unsafe fn test_stdin_read() { let out = poll_oneoff_impl(&r#in).unwrap(); // The result should be either a timeout, or that stdin is ready for reading. // Both are valid behaviors that depend on the test environment. - assert!(out.len() >= 1, "should return at least 1 event"); + assert!(out.len() >= 1, "stdin read should return at least 1 event"); for event in out { if event.r#type == wasi::EVENTTYPE_CLOCK { assert_errno!(event.error, wasi::ERRNO_SUCCESS); @@ -66,55 +67,60 @@ unsafe fn test_stdin_read() { } } +fn writable_subs(h: &HashMap) -> Vec { + h.iter() + .map(|(ud, fd)| wasi::Subscription { + userdata: *ud, + u: wasi::SubscriptionU { + tag: wasi::EVENTTYPE_FD_WRITE, + u: wasi::SubscriptionUU { + fd_write: wasi::SubscriptionFdReadwrite { + file_descriptor: *fd, + }, + }, + }, + }) + .collect() +} + unsafe fn test_stdout_stderr_write() { - let stdout_readwrite = wasi::SubscriptionFdReadwrite { - file_descriptor: STDOUT_FD, - }; - let stderr_readwrite = wasi::SubscriptionFdReadwrite { - file_descriptor: STDERR_FD, - }; - let r#in = [ - wasi::Subscription { - userdata: 1, - u: wasi::SubscriptionU { - tag: wasi::EVENTTYPE_FD_WRITE, - u: wasi::SubscriptionUU { - fd_write: stdout_readwrite, + let mut writable: HashMap = + vec![(1, STDOUT_FD), (2, STDERR_FD)].into_iter().collect(); + + let clock = wasi::Subscription { + userdata: CLOCK_ID, + u: wasi::SubscriptionU { + tag: wasi::EVENTTYPE_CLOCK, + u: wasi::SubscriptionUU { + clock: wasi::SubscriptionClock { + id: wasi::CLOCKID_MONOTONIC, + timeout: 5_000_000u64, // 5 milliseconds + precision: 0, + flags: 0, }, }, }, - wasi::Subscription { - userdata: 2, - u: wasi::SubscriptionU { - tag: wasi::EVENTTYPE_FD_WRITE, - u: wasi::SubscriptionUU { - fd_write: stderr_readwrite, - }, - }, - }, - ]; - let out = poll_oneoff_impl(&r#in).unwrap(); - assert_eq!(out.len(), 2, "should return 2 events"); - assert_eq!( - out[0].userdata, 1, - "the event.userdata should contain fd userdata specified by the user" - ); - assert_errno!(out[0].error, wasi::ERRNO_SUCCESS); - assert_eq!( - out[0].r#type, - wasi::EVENTTYPE_FD_WRITE, - "the event.type should equal FD_WRITE" - ); - assert_eq!( - out[1].userdata, 2, - "the event.userdata should contain fd userdata specified by the user" - ); - assert_errno!(out[1].error, wasi::ERRNO_SUCCESS); - assert_eq!( - out[1].r#type, - wasi::EVENTTYPE_FD_WRITE, - "the event.type should equal FD_WRITE" - ); + }; + while !writable.is_empty() { + let mut subs = writable_subs(&writable); + subs.push(clock.clone()); + let out = poll_oneoff_impl(&subs).unwrap(); + for event in out { + match event.userdata { + CLOCK_ID => { + panic!("timed out with the following pending subs: {:?}", writable) + } + ud => { + if let Some(_) = writable.remove(&ud) { + assert_eq!(event.r#type, wasi::EVENTTYPE_FD_WRITE); + assert_errno!(event.error, wasi::ERRNO_SUCCESS); + } else { + panic!("Unknown userdata {}, pending sub: {:?}", ud, writable) + } + } + } + } + } } unsafe fn test_poll_oneoff() { From 68fdadde26fd229fe5f9669071da0a4d5cc9c1cb Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 7 May 2021 15:19:02 -0700 Subject: [PATCH 67/72] tokio poll_oneoff test: CI needs more than 1ms to complete it --- crates/wasi-common/tokio/tests/poll_oneoff.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/wasi-common/tokio/tests/poll_oneoff.rs b/crates/wasi-common/tokio/tests/poll_oneoff.rs index 818f7e86c0..bf932325fe 100644 --- a/crates/wasi-common/tokio/tests/poll_oneoff.rs +++ b/crates/wasi-common/tokio/tests/poll_oneoff.rs @@ -40,7 +40,7 @@ async fn empty_file_readable() -> Result<(), Error> { clocks .monotonic .now(clocks.monotonic.resolution()) - .checked_add(Duration::from_millis(1)) + .checked_add(Duration::from_millis(5)) .unwrap(), clocks.monotonic.resolution(), Userdata::from(0), @@ -83,7 +83,7 @@ async fn empty_file_writable() -> Result<(), Error> { clocks .monotonic .now(clocks.monotonic.resolution()) - .checked_add(Duration::from_millis(1)) + .checked_add(Duration::from_millis(5)) .unwrap(), clocks.monotonic.resolution(), Userdata::from(0), @@ -111,7 +111,7 @@ async fn stdio_readable() -> Result<(), Error> { let deadline = clocks .monotonic .now(clocks.monotonic.resolution()) - .checked_add(Duration::from_millis(1)) + .checked_add(Duration::from_millis(5)) .unwrap(); let mut waiting_on: HashMap> = vec![ From b450094dad25def1100141dbf40ab042c1ee9ad5 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 7 May 2021 15:19:17 -0700 Subject: [PATCH 68/72] debug --- crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs index 3134c10cf8..7caf508c32 100644 --- a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs +++ b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs @@ -68,6 +68,7 @@ unsafe fn test_stdin_read() { } fn writable_subs(h: &HashMap) -> Vec { + println!("writable subs: {:?}", h); h.iter() .map(|(ud, fd)| wasi::Subscription { userdata: *ud, From 86bd56f6d1318e5374cc07d0ed3033810df0c33c Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 7 May 2021 15:42:03 -0700 Subject: [PATCH 69/72] turn off fail-fast again --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9b3dfe2d70..71dc0a3b3c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -208,6 +208,7 @@ jobs: name: Test runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: build: [stable, beta, nightly, windows, macos] include: @@ -382,6 +383,7 @@ jobs: name: Build wasmtime runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: include: - build: x86_64-linux From 548b6c5311cb7dafdcd7375df0d025f1befe74e6 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 7 May 2021 15:51:33 -0700 Subject: [PATCH 70/72] windows fixes --- crates/wasi-common/cap-std-sync/src/sched/windows.rs | 12 +++++++++--- crates/wasi-common/tokio/src/sched/windows.rs | 6 +++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/wasi-common/cap-std-sync/src/sched/windows.rs b/crates/wasi-common/cap-std-sync/src/sched/windows.rs index 135ec7b4a0..41f3f1dda3 100644 --- a/crates/wasi-common/cap-std-sync/src/sched/windows.rs +++ b/crates/wasi-common/cap-std-sync/src/sched/windows.rs @@ -25,12 +25,13 @@ use wasi_common::{ }; pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { - poll_oneoff_(poll, wasi_file_raw_handle).await + poll_oneoff_(poll, wasi_file_is_stdin, wasi_file_raw_handle).await } // For reuse by wasi-tokio, which has a different WasiFile -> RawHandle translator. pub async fn poll_oneoff_<'a>( poll: &mut Poll<'a>, + file_is_stdin: impl Fn(&dyn WasiFile) -> bool, file_to_handle: impl Fn(&dyn WasiFile) -> Option, ) -> Result<(), Error> { if poll.is_empty() { @@ -58,7 +59,7 @@ pub async fn poll_oneoff_<'a>( for s in poll.rw_subscriptions() { match s { Subscription::Read(r) => { - if r.file.as_any().is::() { + if file_is_stdin(r.file.deref()) { stdin_read_subs.push(r); } else if file_to_handle(r.file.deref()).is_some() { immediate_reads.push(r); @@ -69,7 +70,7 @@ pub async fn poll_oneoff_<'a>( } } Subscription::Write(w) => { - if wasi_file_raw_handle(w.file.deref()).is_some() { + if file_to_handle(w.file.deref()).is_some() { immediate_writes.push(w); } else { return Err( @@ -133,6 +134,11 @@ pub async fn poll_oneoff_<'a>( Ok(()) } + +pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool { + f.as_any().is::() +} + pub fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option { let a = f.as_any(); if a.is::() { diff --git a/crates/wasi-common/tokio/src/sched/windows.rs b/crates/wasi-common/tokio/src/sched/windows.rs index d962675f45..90b33aeb47 100644 --- a/crates/wasi-common/tokio/src/sched/windows.rs +++ b/crates/wasi-common/tokio/src/sched/windows.rs @@ -8,7 +8,11 @@ pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> { // we use the blocking poll_oneoff implementation from the wasi-cap-std-crate. // We provide a function specific to this crate's WasiFile types for downcasting // to a RawHandle. - block_on_dummy_executor(move || poll_oneoff_(poll, wasi_file_raw_handle)) + block_on_dummy_executor(move || poll_oneoff_(poll, wasi_file_is_stdin, wasi_file_raw_handle)) +} + +pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool { + f.as_any().is::() } fn wasi_file_raw_handle(f: &dyn WasiFile) -> Option { From 74e9b385df2b0a71de12077c97882da4e7061504 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 7 May 2021 16:07:15 -0700 Subject: [PATCH 71/72] lets try 10ms, macos ci timed out with 5ms --- crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs index 7caf508c32..b5c3bad73e 100644 --- a/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs +++ b/crates/test-programs/wasi-tests/src/bin/poll_oneoff_stdio.rs @@ -95,7 +95,7 @@ unsafe fn test_stdout_stderr_write() { u: wasi::SubscriptionUU { clock: wasi::SubscriptionClock { id: wasi::CLOCKID_MONOTONIC, - timeout: 5_000_000u64, // 5 milliseconds + timeout: 10_000_000u64, // 10 milliseconds precision: 0, flags: 0, }, From bae1a5693dff7f217631336578dac8e99a490fe1 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Fri, 7 May 2021 16:19:50 -0700 Subject: [PATCH 72/72] 10ms here, and let it timeout in addition to ready? --- crates/wasi-common/tokio/tests/poll_oneoff.rs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/crates/wasi-common/tokio/tests/poll_oneoff.rs b/crates/wasi-common/tokio/tests/poll_oneoff.rs index bf932325fe..fa04f4604e 100644 --- a/crates/wasi-common/tokio/tests/poll_oneoff.rs +++ b/crates/wasi-common/tokio/tests/poll_oneoff.rs @@ -40,7 +40,7 @@ async fn empty_file_readable() -> Result<(), Error> { clocks .monotonic .now(clocks.monotonic.resolution()) - .checked_add(Duration::from_millis(5)) + .checked_add(Duration::from_millis(10)) .unwrap(), clocks.monotonic.resolution(), Userdata::from(0), @@ -49,11 +49,10 @@ async fn empty_file_readable() -> Result<(), Error> { let events = poll.results(); - assert_eq!(events.len(), 1, "expected 1 event, got: {:?}", events); - match events[0] { + match events.get(0).expect("at least one event") { (SubscriptionResult::Read(Ok((1, flags))), ud) => { - assert_eq!(flags, RwEventFlags::empty()); - assert_eq!(ud, Userdata::from(123)); + assert_eq!(*flags, RwEventFlags::empty()); + assert_eq!(*ud, Userdata::from(123)); } _ => panic!("expected (Read(Ok(1, empty), 123), got: {:?}", events[0]), } @@ -83,7 +82,7 @@ async fn empty_file_writable() -> Result<(), Error> { clocks .monotonic .now(clocks.monotonic.resolution()) - .checked_add(Duration::from_millis(5)) + .checked_add(Duration::from_millis(10)) .unwrap(), clocks.monotonic.resolution(), Userdata::from(0), @@ -92,11 +91,10 @@ async fn empty_file_writable() -> Result<(), Error> { let events = poll.results(); - assert_eq!(events.len(), 1); - match events[0] { + match events.get(0).expect("at least one event") { (SubscriptionResult::Write(Ok((0, flags))), ud) => { - assert_eq!(flags, RwEventFlags::empty()); - assert_eq!(ud, Userdata::from(123)); + assert_eq!(*flags, RwEventFlags::empty()); + assert_eq!(*ud, Userdata::from(123)); } _ => panic!(""), } @@ -111,7 +109,7 @@ async fn stdio_readable() -> Result<(), Error> { let deadline = clocks .monotonic .now(clocks.monotonic.resolution()) - .checked_add(Duration::from_millis(5)) + .checked_add(Duration::from_millis(10)) .unwrap(); let mut waiting_on: HashMap> = vec![