Refactor poll_oneoff and return stdin if immediately readable.

This commit is contained in:
Marcin Mielniczuk
2019-12-08 20:37:14 +01:00
parent 4695c95374
commit 98e84ae487
3 changed files with 95 additions and 66 deletions

View File

@@ -158,6 +158,7 @@ impl Error {
Self::Winx(err) => crate::sys::host_impl::errno_from_win(*err), Self::Winx(err) => crate::sys::host_impl::errno_from_win(*err),
} }
} }
pub const ESUCCESS: Self = Error::Wasi(WasiError::ESUCCESS); pub const ESUCCESS: Self = Error::Wasi(WasiError::ESUCCESS);
pub const E2BIG: Self = Error::Wasi(WasiError::E2BIG); pub const E2BIG: Self = Error::Wasi(WasiError::E2BIG);
pub const EACCES: Self = Error::Wasi(WasiError::EACCES); pub const EACCES: Self = Error::Wasi(WasiError::EACCES);
@@ -246,3 +247,18 @@ fn errno_from_ioerror(e: &std::io::Error) -> wasi::__wasi_errno_t {
} }
} }
} }
pub(crate) type Result<T> = std::result::Result<T, Error>;
pub(crate) trait WasiErrno {
fn as_wasi_errno(&self) -> wasi::__wasi_errno_t;
}
impl<T> WasiErrno for Result<T> {
fn as_wasi_errno(&self) -> wasi::__wasi_errno_t {
self.as_ref()
.err()
.unwrap_or(&Error::ESUCCESS)
.as_wasi_errno()
}
}

View File

@@ -41,5 +41,5 @@ pub mod wasi32;
pub use ctx::{WasiCtx, WasiCtxBuilder}; pub use ctx::{WasiCtx, WasiCtxBuilder};
pub use sys::preopen_dir; pub use sys::preopen_dir;
pub type Error = error::Error; pub use error::Error;
pub(crate) type Result<T> = std::result::Result<T, Error>; pub(crate) use error::Result;

View File

@@ -81,12 +81,41 @@ fn stdin_nonempty() -> bool {
std::io::stdin().bytes().peekable().peek().is_some() std::io::stdin().bytes().peekable().peek().is_some()
} }
fn make_read_event(event: &FdEventData, nbytes: Result<u64>) -> wasi::__wasi_event_t {
use crate::error::WasiErrno;
let error = nbytes.as_wasi_errno();
let nbytes = nbytes.unwrap_or_default();
wasi::__wasi_event_t {
userdata: event.userdata,
r#type: event.r#type,
error,
u: wasi::__wasi_event_u_t {
fd_readwrite: wasi::__wasi_event_fd_readwrite_t { nbytes, flags: 0 },
},
}
}
fn make_timeout_event(event: &FdEventData, timeout: &ClockEventData) -> wasi::__wasi_event_t {
wasi::__wasi_event_t {
userdata: timeout.userdata,
r#type: wasi::__WASI_EVENTTYPE_CLOCK,
error: wasi::__WASI_ERRNO_SUCCESS,
u: wasi::__wasi_event_u_t {
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: 0,
flags: 0,
},
},
}
}
pub(crate) fn poll_oneoff( pub(crate) fn poll_oneoff(
timeout: Option<ClockEventData>, timeout: Option<ClockEventData>,
fd_events: Vec<FdEventData>, fd_events: Vec<FdEventData>,
events: &mut Vec<wasi::__wasi_event_t>, events: &mut Vec<wasi::__wasi_event_t>,
) -> Result<()> { ) -> Result<()> {
use crate::fdentry::Descriptor; use crate::fdentry::Descriptor;
use std::fs::Metadata;
if fd_events.is_empty() && timeout.is_none() { if fd_events.is_empty() && timeout.is_none() {
return Ok(()); return Ok(());
} }
@@ -100,52 +129,51 @@ pub(crate) fn poll_oneoff(
// Therefore, we only poll the stdin. // Therefore, we only poll the stdin.
let mut stdin_events = vec![]; let mut stdin_events = vec![];
let mut immediate_events = vec![]; let mut immediate_events = vec![];
let mut stdin_ready = None;
for event in fd_events { for event in fd_events {
match event.descriptor { match event.descriptor {
Descriptor::Stdin if event.r#type == wasi::__WASI_EVENTTYPE_FD_READ => { Descriptor::Stdin if event.r#type == wasi::__WASI_EVENTTYPE_FD_READ => {
// Cache the non-emptiness for better performance.
let immediate = stdin_ready.get_or_insert_with(stdin_nonempty);
if *immediate {
immediate_events.push(event)
} else {
stdin_events.push(event) stdin_events.push(event)
} }
}
_ => immediate_events.push(event), _ => immediate_events.push(event),
} }
} }
// we have at least one immediate event, so we don't need to care about stdin // Process all the events that do not require waiting.
if immediate_events.len() > 0 { if !immediate_events.is_empty() {
for event in immediate_events { for mut event in immediate_events {
let size = match event.descriptor { let size = match event.descriptor {
Descriptor::OsHandle(os_handle) Descriptor::OsHandle(os_handle) => {
if event.r#type == wasi::__WASI_EVENTTYPE_FD_READ => if event.r#type == wasi::__WASI_EVENTTYPE_FD_READ {
{ os_handle.metadata().map(|m| m.len()).map_err(Into::into)
os_handle } else {
.metadata() // The spec is unclear what nbytes should actually be for __WASI_EVENTTYPE_FD_WRITE and
.expect("FIXME return a proper error") // the implementation on Unix just returns 0 here, so it's probably fine
.len() // to do the same on Windows for now.
}
Descriptor::Stdin => panic!("Descriptor::Stdin should have been filtered out"),
// On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows.
//
// Besides, the spec is unclear what nbytes should actually be for __WASI_EVENTTYPE_FD_WRITE and
// the implementation on Unix just returns 0 here, so it's probably fine to do the same on Windows for now.
// cf. https://github.com/WebAssembly/WASI/issues/148 // cf. https://github.com/WebAssembly/WASI/issues/148
_ => 0, Ok(0)
}
}
// We return the only universally correct lower bound, see the comment later in the function.
Descriptor::Stdin => Ok(1),
// On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows.
Descriptor::Stdout | Descriptor::Stderr => Ok(0),
}; };
events.push(wasi::__wasi_event_t { let new_event = make_read_event(&event, size);
userdata: event.userdata, events.push(new_event)
r#type: event.r#type, }
error: wasi::__WASI_ERRNO_SUCCESS,
u: wasi::__wasi_event_u_t {
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: size,
flags: 0,
},
},
})
} }
} else {
assert_ne!(stdin_events.len(), 0, "stdin_events should not be empty");
// There are some stdin poll requests and there's no data available immediately
if !stdin_events.is_empty() {
// We are busy-polling the stdin with delay, unfortunately. // We are busy-polling the stdin with delay, unfortunately.
// //
// We'd like to do the following: // We'd like to do the following:
@@ -172,58 +200,43 @@ pub(crate) fn poll_oneoff(
let timeout_duration = timeout let timeout_duration = timeout
.map(|t| t.delay.try_into().map(Duration::from_nanos)) .map(|t| t.delay.try_into().map(Duration::from_nanos))
.transpose()?; .transpose()?;
let timeout = loop {
let timeout_occurred: Option<ClockEventData> = loop {
// Even though we assume that stdin is not ready, it's better to check it
// sooner than later, as we're going to wait anyway if it's the case.
if stdin_nonempty() {
break None;
}
if let Some(timeout_duration) = timeout_duration { if let Some(timeout_duration) = timeout_duration {
if poll_start.elapsed() >= timeout_duration { if poll_start.elapsed() >= timeout_duration {
break timeout; break timeout;
} }
} }
if stdin_nonempty() {
break None;
}
std::thread::sleep(poll_interval); std::thread::sleep(poll_interval);
}; };
// TODO try refactoring pushing to events match timeout_occurred {
match timeout { Some(timeout_info) => {
// timeout occurred
Some(timeout) => {
for event in stdin_events { for event in stdin_events {
events.push(wasi::__wasi_event_t { let new_event = make_timeout_event(&event, &timeout_info);
userdata: timeout.userdata, events.push(new_event);
r#type: wasi::__WASI_EVENTTYPE_CLOCK,
error: wasi::__WASI_ERRNO_SUCCESS,
u: wasi::__wasi_event_u_t {
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
nbytes: 0,
flags: 0,
},
},
});
} }
} }
// stdin is ready for reading
None => { None => {
// stdin became ready for reading
for event in stdin_events { for event in stdin_events {
assert_eq!( assert_eq!(
event.r#type, event.r#type,
wasi::__WASI_EVENTTYPE_FD_READ, wasi::__WASI_EVENTTYPE_FD_READ,
"stdin was expected to be polled for reading" "stdin was expected to be polled for reading"
); );
events.push(wasi::__wasi_event_t {
userdata: event.userdata,
r#type: event.r#type,
error: wasi::__WASI_ERRNO_SUCCESS,
// Another limitation is that `std::io::BufRead` doesn't allow us // Another limitation is that `std::io::BufRead` doesn't allow us
// to find out the number bytes available in the buffer, // to find out the number bytes available in the buffer,
// so we return the only universally correct lower bound. // so we return the only universally correct lower bound,
u: wasi::__wasi_event_u_t { // which is 1 byte.
fd_readwrite: wasi::__wasi_event_fd_readwrite_t { let new_event = make_read_event(&event, Ok(1));
nbytes: 1, events.push(new_event);
flags: 0,
},
},
});
} }
} }
} }