WIP implementation of poll_oneoff on Windows
This commit is contained in:
@@ -176,7 +176,6 @@ mod wasi_tests {
|
|||||||
"dangling_symlink" => true,
|
"dangling_symlink" => true,
|
||||||
"symlink_loop" => true,
|
"symlink_loop" => true,
|
||||||
"truncation_rights" => true,
|
"truncation_rights" => true,
|
||||||
"poll_oneoff" => true,
|
|
||||||
"path_link" => true,
|
"path_link" => true,
|
||||||
"dangling_fd" => true,
|
"dangling_fd" => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
|
|||||||
@@ -76,12 +76,160 @@ pub(crate) fn clock_time_get(clock_id: wasi::__wasi_clockid_t) -> Result<wasi::_
|
|||||||
duration.as_nanos().try_into().map_err(Into::into)
|
duration.as_nanos().try_into().map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stdin_nonempty() -> bool {
|
||||||
|
use std::io::Read;
|
||||||
|
std::io::stdin().bytes().peekable().peek().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
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<Vec<wasi::__wasi_event_t>> {
|
) -> Result<()> {
|
||||||
unimplemented!("poll_oneoff")
|
use crate::fdentry::Descriptor;
|
||||||
|
if fd_events.is_empty() && timeout.is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently WASI file support is only (a) regular files (b) directories (c) symlinks on Windows,
|
||||||
|
// which are always ready to write on Unix.
|
||||||
|
//
|
||||||
|
// We need to consider stdin/stdout/stderr separately.
|
||||||
|
// We treat stdout/stderr as always ready to write. I'm not sure if it's correct
|
||||||
|
// on Windows but I have not find any way of checking if a write to stdout would block.
|
||||||
|
// Therefore, we only poll the stdin.
|
||||||
|
let mut stdin_events = vec![];
|
||||||
|
let mut immediate_events = vec![];
|
||||||
|
|
||||||
|
for event in fd_events {
|
||||||
|
match event.descriptor {
|
||||||
|
Descriptor::Stdin if event.r#type == wasi::__WASI_EVENTTYPE_FD_READ => {
|
||||||
|
stdin_events.push(event)
|
||||||
|
}
|
||||||
|
_ => immediate_events.push(event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have at least one immediate event, so we don't need to care about stdin
|
||||||
|
if immediate_events.len() > 0 {
|
||||||
|
for event in immediate_events {
|
||||||
|
let size = match event.descriptor {
|
||||||
|
Descriptor::OsHandle(os_handle)
|
||||||
|
if event.r#type == wasi::__WASI_EVENTTYPE_FD_READ =>
|
||||||
|
{
|
||||||
|
os_handle
|
||||||
|
.metadata()
|
||||||
|
.expect("FIXME return a proper error")
|
||||||
|
.len()
|
||||||
|
}
|
||||||
|
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
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
events.push(wasi::__wasi_event_t {
|
||||||
|
userdata: event.userdata,
|
||||||
|
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");
|
||||||
|
|
||||||
|
// We are busy-polling the stdin with delay, unfortunately.
|
||||||
|
//
|
||||||
|
// We'd like to do the following:
|
||||||
|
// (1) wait in a non-blocking way for data to be available in stdin, with timeout
|
||||||
|
// (2) find out, how many bytes are there available to be read.
|
||||||
|
// For one, using `WaitForSingleObject` on the stdin handle could possibly be one way of
|
||||||
|
// achieving (1).
|
||||||
|
// I don't know of any way to achieve (2).
|
||||||
|
//
|
||||||
|
// While both of these are not as trivial on Windows as they are on Linux, there's a much
|
||||||
|
// more fundamental issue preventing us from achieving such behavior with the current
|
||||||
|
// implementation of wasi-common.
|
||||||
|
//
|
||||||
|
// Precisely, in `fd_read` we are using `io::stdin` via the `BufRead` trait, which does
|
||||||
|
// buffering on the libstd side. This means that even if there's still some unread data
|
||||||
|
// in stdin, the Windows system calls may return false negatives, indicating that stdin is empty.
|
||||||
|
// Therefore, avoiding the busy-poll here would require us to ditch libstd for the interaction
|
||||||
|
// with stdin altogether.
|
||||||
|
//
|
||||||
|
// However, polling stdin is a relatively infrequent use case, so this hopefully won't be
|
||||||
|
// a major issue.
|
||||||
|
let poll_interval = Duration::from_millis(10);
|
||||||
|
let poll_start = Instant::now();
|
||||||
|
let timeout_duration = timeout
|
||||||
|
.map(|t| t.delay.try_into().map(Duration::from_nanos))
|
||||||
|
.transpose()?;
|
||||||
|
let timeout = loop {
|
||||||
|
if let Some(timeout_duration) = timeout_duration {
|
||||||
|
if poll_start.elapsed() >= timeout_duration {
|
||||||
|
break timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stdin_nonempty() {
|
||||||
|
break None;
|
||||||
|
}
|
||||||
|
std::thread::sleep(poll_interval);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO try refactoring pushing to events
|
||||||
|
match timeout {
|
||||||
|
// timeout occurred
|
||||||
|
Some(timeout) => {
|
||||||
|
for event in stdin_events {
|
||||||
|
events.push(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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// stdin is ready for reading
|
||||||
|
None => {
|
||||||
|
for event in stdin_events {
|
||||||
|
assert_eq!(
|
||||||
|
event.r#type,
|
||||||
|
wasi::__WASI_EVENTTYPE_FD_READ,
|
||||||
|
"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
|
||||||
|
// to find out the number bytes available in the buffer,
|
||||||
|
// so we return the only universally correct lower bound.
|
||||||
|
u: wasi::__wasi_event_u_t {
|
||||||
|
fd_readwrite: wasi::__wasi_event_fd_readwrite_t {
|
||||||
|
nbytes: 1,
|
||||||
|
flags: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_monotonic_time() -> Duration {
|
fn get_monotonic_time() -> Duration {
|
||||||
|
|||||||
Reference in New Issue
Block a user