From 423ec09682b8620799352c5fe0bc4d9d68463c5c Mon Sep 17 00:00:00 2001 From: Marcin Mielniczuk Date: Sun, 10 Nov 2019 16:41:16 +0100 Subject: [PATCH] WIP implementation of poll_oneoff on Windows --- crates/test-programs/build.rs | 1 - .../src/sys/windows/hostcalls_impl/misc.rs | 152 +++++++++++++++++- 2 files changed, 150 insertions(+), 3 deletions(-) diff --git a/crates/test-programs/build.rs b/crates/test-programs/build.rs index 0c453be63926..cf4931090288 100644 --- a/crates/test-programs/build.rs +++ b/crates/test-programs/build.rs @@ -174,7 +174,6 @@ mod wasi_tests { "dangling_symlink" => true, "symlink_loop" => true, "truncation_rights" => true, - "poll_oneoff" => true, "path_link" => true, "dangling_fd" => true, _ => false, diff --git a/crates/wasi-common/src/sys/windows/hostcalls_impl/misc.rs b/crates/wasi-common/src/sys/windows/hostcalls_impl/misc.rs index 28d1408de1f9..102021c5c0c9 100644 --- a/crates/wasi-common/src/sys/windows/hostcalls_impl/misc.rs +++ b/crates/wasi-common/src/sys/windows/hostcalls_impl/misc.rs @@ -77,12 +77,160 @@ pub(crate) fn clock_time_get(clock_id: wasi::__wasi_clockid_t) -> Result bool { + use std::io::Read; + std::io::stdin().bytes().peekable().peek().is_some() +} + pub(crate) fn poll_oneoff( timeout: Option, fd_events: Vec, events: &mut Vec, -) -> Result> { - unimplemented!("poll_oneoff") +) -> Result<()> { + 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 {