Path: blob/main/crates/wasi-common/src/sync/sched/windows.rs
1693 views
// The windows scheduler is unmaintained and due for a rewrite.1//2// Rather than use a polling mechanism for file read/write readiness,3// it checks readiness just once, before sleeping for any timer subscriptions.4// Checking stdin readiness uses a worker thread which, once started, lives for the5// lifetime of the process.6//7// We suspect there are bugs in this scheduler, however, we have not8// taken the time to improve it. See bug #2880.910use crate::sched::subscription::{RwEventFlags, Subscription};11use crate::{Error, ErrorExt, file::WasiFile, sched::Poll};12use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};13use std::sync::{LazyLock, Mutex};14use std::thread;15use std::time::Duration;1617pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> {18poll_oneoff_(poll, wasi_file_is_stdin).await19}2021pub async fn poll_oneoff_<'a>(22poll: &mut Poll<'a>,23file_is_stdin: impl Fn(&dyn WasiFile) -> bool,24) -> Result<(), Error> {25if poll.is_empty() {26return Ok(());27}2829let mut ready = false;30let waitmode = if let Some(t) = poll.earliest_clock_deadline() {31if let Some(duration) = t.duration_until() {32WaitMode::Timeout(duration)33} else {34WaitMode::Immediate35}36} else {37if ready {38WaitMode::Immediate39} else {40WaitMode::Infinite41}42};4344let mut stdin_read_subs = Vec::new();45let mut immediate_reads = Vec::new();46let mut immediate_writes = Vec::new();47for s in poll.rw_subscriptions() {48match s {49Subscription::Read(r) => {50if file_is_stdin(r.file) {51stdin_read_subs.push(r);52} else if r.file.pollable().is_some() {53immediate_reads.push(r);54} else {55return Err(Error::invalid_argument().context("file is not pollable"));56}57}58Subscription::Write(w) => {59if w.file.pollable().is_some() {60immediate_writes.push(w);61} else {62return Err(Error::invalid_argument().context("file is not pollable"));63}64}65Subscription::MonotonicClock { .. } => unreachable!(),66}67}6869if !stdin_read_subs.is_empty() {70let state = STDIN_POLL71.lock()72.map_err(|_| Error::trap(anyhow::Error::msg("failed to take lock of STDIN_POLL")))?73.poll(waitmode)?;74for readsub in stdin_read_subs.into_iter() {75match state {76PollState::Ready => {77readsub.complete(1, RwEventFlags::empty());78ready = true;79}80PollState::NotReady | PollState::TimedOut => {}81PollState::Error(ref e) => {82// Unfortunately, we need to deliver the Error to each of the83// subscriptions, but there is no Clone on std::io::Error. So, we convert it to the84// kind, and then back to std::io::Error, and finally to anyhow::Error.85// When its time to turn this into an errno elsewhere, the error kind will86// be inspected.87let ekind = e.kind();88let ioerror = std::io::Error::from(ekind);89readsub.error(ioerror.into());90ready = true;91}92}93}94}95for r in immediate_reads {96match r.file.num_ready_bytes() {97Ok(ready_bytes) => {98r.complete(ready_bytes, RwEventFlags::empty());99ready = true;100}101Err(e) => {102r.error(e);103ready = true;104}105}106}107for w in immediate_writes {108// Everything is always ready for writing, apparently?109w.complete(0, RwEventFlags::empty());110ready = true;111}112113if !ready {114if let WaitMode::Timeout(duration) = waitmode {115thread::sleep(duration);116}117}118119Ok(())120}121122pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool {123f.as_any().is::<crate::sync::stdio::Stdin>()124}125126enum PollState {127Ready,128NotReady, // Not ready, but did not wait129TimedOut, // Not ready, waited until timeout130Error(std::io::Error),131}132133#[derive(Copy, Clone)]134enum WaitMode {135Timeout(Duration),136Infinite,137Immediate,138}139140struct StdinPoll {141request_tx: Sender<()>,142notify_rx: Receiver<PollState>,143}144145static STDIN_POLL: LazyLock<Mutex<StdinPoll>> = LazyLock::new(StdinPoll::new);146147impl StdinPoll {148pub fn new() -> Mutex<Self> {149let (request_tx, request_rx) = mpsc::channel();150let (notify_tx, notify_rx) = mpsc::channel();151thread::spawn(move || Self::event_loop(request_rx, notify_tx));152Mutex::new(StdinPoll {153request_tx,154notify_rx,155})156}157158// This function should not be used directly.159// Correctness of this function crucially depends on the fact that160// mpsc::Receiver is !Sync.161fn poll(&self, wait_mode: WaitMode) -> Result<PollState, Error> {162match self.notify_rx.try_recv() {163// Clean up possibly unread result from previous poll.164Ok(_) | Err(TryRecvError::Empty) => {}165Err(TryRecvError::Disconnected) => {166return Err(Error::trap(anyhow::Error::msg(167"StdinPoll notify_rx channel closed",168)));169}170}171172// Notify the worker thread to poll stdin173self.request_tx174.send(())175.map_err(|_| Error::trap(anyhow::Error::msg("request_tx channel closed")))?;176177// Wait for the worker thread to send a readiness notification178match wait_mode {179WaitMode::Timeout(timeout) => match self.notify_rx.recv_timeout(timeout) {180Ok(r) => Ok(r),181Err(RecvTimeoutError::Timeout) => Ok(PollState::TimedOut),182Err(RecvTimeoutError::Disconnected) => Err(Error::trap(anyhow::Error::msg(183"StdinPoll notify_rx channel closed",184))),185},186WaitMode::Infinite => self187.notify_rx188.recv()189.map_err(|_| Error::trap(anyhow::Error::msg("StdinPoll notify_rx channel closed"))),190WaitMode::Immediate => match self.notify_rx.try_recv() {191Ok(r) => Ok(r),192Err(TryRecvError::Empty) => Ok(PollState::NotReady),193Err(TryRecvError::Disconnected) => Err(Error::trap(anyhow::Error::msg(194"StdinPoll notify_rx channel closed",195))),196},197}198}199200fn event_loop(request_rx: Receiver<()>, notify_tx: Sender<PollState>) -> ! {201use std::io::BufRead;202loop {203// Wait on a request:204request_rx.recv().expect("request_rx channel");205// Wait for data to appear in stdin. If fill_buf returns any slice, it means206// that either:207// (a) there is some data in stdin, if non-empty,208// (b) EOF was received, if its empty209// Linux returns `POLLIN` in both cases, so we imitate this behavior.210let resp = match std::io::stdin().lock().fill_buf() {211Ok(_) => PollState::Ready,212Err(e) => PollState::Error(e),213};214// Notify about data in stdin. If the read on this channel has timed out, the215// next poller will have to clean the channel.216notify_tx.send(resp).expect("notify_tx channel");217}218}219}220221222