Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/wasi-common/src/sync/sched/windows.rs
1693 views
1
// The windows scheduler is unmaintained and due for a rewrite.
2
//
3
// Rather than use a polling mechanism for file read/write readiness,
4
// it checks readiness just once, before sleeping for any timer subscriptions.
5
// Checking stdin readiness uses a worker thread which, once started, lives for the
6
// lifetime of the process.
7
//
8
// We suspect there are bugs in this scheduler, however, we have not
9
// taken the time to improve it. See bug #2880.
10
11
use crate::sched::subscription::{RwEventFlags, Subscription};
12
use crate::{Error, ErrorExt, file::WasiFile, sched::Poll};
13
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
14
use std::sync::{LazyLock, Mutex};
15
use std::thread;
16
use std::time::Duration;
17
18
pub async fn poll_oneoff<'a>(poll: &mut Poll<'a>) -> Result<(), Error> {
19
poll_oneoff_(poll, wasi_file_is_stdin).await
20
}
21
22
pub async fn poll_oneoff_<'a>(
23
poll: &mut Poll<'a>,
24
file_is_stdin: impl Fn(&dyn WasiFile) -> bool,
25
) -> Result<(), Error> {
26
if poll.is_empty() {
27
return Ok(());
28
}
29
30
let mut ready = false;
31
let waitmode = if let Some(t) = poll.earliest_clock_deadline() {
32
if let Some(duration) = t.duration_until() {
33
WaitMode::Timeout(duration)
34
} else {
35
WaitMode::Immediate
36
}
37
} else {
38
if ready {
39
WaitMode::Immediate
40
} else {
41
WaitMode::Infinite
42
}
43
};
44
45
let mut stdin_read_subs = Vec::new();
46
let mut immediate_reads = Vec::new();
47
let mut immediate_writes = Vec::new();
48
for s in poll.rw_subscriptions() {
49
match s {
50
Subscription::Read(r) => {
51
if file_is_stdin(r.file) {
52
stdin_read_subs.push(r);
53
} else if r.file.pollable().is_some() {
54
immediate_reads.push(r);
55
} else {
56
return Err(Error::invalid_argument().context("file is not pollable"));
57
}
58
}
59
Subscription::Write(w) => {
60
if w.file.pollable().is_some() {
61
immediate_writes.push(w);
62
} else {
63
return Err(Error::invalid_argument().context("file is not pollable"));
64
}
65
}
66
Subscription::MonotonicClock { .. } => unreachable!(),
67
}
68
}
69
70
if !stdin_read_subs.is_empty() {
71
let state = STDIN_POLL
72
.lock()
73
.map_err(|_| Error::trap(anyhow::Error::msg("failed to take lock of STDIN_POLL")))?
74
.poll(waitmode)?;
75
for readsub in stdin_read_subs.into_iter() {
76
match state {
77
PollState::Ready => {
78
readsub.complete(1, RwEventFlags::empty());
79
ready = true;
80
}
81
PollState::NotReady | PollState::TimedOut => {}
82
PollState::Error(ref e) => {
83
// Unfortunately, we need to deliver the Error to each of the
84
// subscriptions, but there is no Clone on std::io::Error. So, we convert it to the
85
// kind, and then back to std::io::Error, and finally to anyhow::Error.
86
// When its time to turn this into an errno elsewhere, the error kind will
87
// be inspected.
88
let ekind = e.kind();
89
let ioerror = std::io::Error::from(ekind);
90
readsub.error(ioerror.into());
91
ready = true;
92
}
93
}
94
}
95
}
96
for r in immediate_reads {
97
match r.file.num_ready_bytes() {
98
Ok(ready_bytes) => {
99
r.complete(ready_bytes, RwEventFlags::empty());
100
ready = true;
101
}
102
Err(e) => {
103
r.error(e);
104
ready = true;
105
}
106
}
107
}
108
for w in immediate_writes {
109
// Everything is always ready for writing, apparently?
110
w.complete(0, RwEventFlags::empty());
111
ready = true;
112
}
113
114
if !ready {
115
if let WaitMode::Timeout(duration) = waitmode {
116
thread::sleep(duration);
117
}
118
}
119
120
Ok(())
121
}
122
123
pub fn wasi_file_is_stdin(f: &dyn WasiFile) -> bool {
124
f.as_any().is::<crate::sync::stdio::Stdin>()
125
}
126
127
enum PollState {
128
Ready,
129
NotReady, // Not ready, but did not wait
130
TimedOut, // Not ready, waited until timeout
131
Error(std::io::Error),
132
}
133
134
#[derive(Copy, Clone)]
135
enum WaitMode {
136
Timeout(Duration),
137
Infinite,
138
Immediate,
139
}
140
141
struct StdinPoll {
142
request_tx: Sender<()>,
143
notify_rx: Receiver<PollState>,
144
}
145
146
static STDIN_POLL: LazyLock<Mutex<StdinPoll>> = LazyLock::new(StdinPoll::new);
147
148
impl StdinPoll {
149
pub fn new() -> Mutex<Self> {
150
let (request_tx, request_rx) = mpsc::channel();
151
let (notify_tx, notify_rx) = mpsc::channel();
152
thread::spawn(move || Self::event_loop(request_rx, notify_tx));
153
Mutex::new(StdinPoll {
154
request_tx,
155
notify_rx,
156
})
157
}
158
159
// This function should not be used directly.
160
// Correctness of this function crucially depends on the fact that
161
// mpsc::Receiver is !Sync.
162
fn poll(&self, wait_mode: WaitMode) -> Result<PollState, Error> {
163
match self.notify_rx.try_recv() {
164
// Clean up possibly unread result from previous poll.
165
Ok(_) | Err(TryRecvError::Empty) => {}
166
Err(TryRecvError::Disconnected) => {
167
return Err(Error::trap(anyhow::Error::msg(
168
"StdinPoll notify_rx channel closed",
169
)));
170
}
171
}
172
173
// Notify the worker thread to poll stdin
174
self.request_tx
175
.send(())
176
.map_err(|_| Error::trap(anyhow::Error::msg("request_tx channel closed")))?;
177
178
// Wait for the worker thread to send a readiness notification
179
match wait_mode {
180
WaitMode::Timeout(timeout) => match self.notify_rx.recv_timeout(timeout) {
181
Ok(r) => Ok(r),
182
Err(RecvTimeoutError::Timeout) => Ok(PollState::TimedOut),
183
Err(RecvTimeoutError::Disconnected) => Err(Error::trap(anyhow::Error::msg(
184
"StdinPoll notify_rx channel closed",
185
))),
186
},
187
WaitMode::Infinite => self
188
.notify_rx
189
.recv()
190
.map_err(|_| Error::trap(anyhow::Error::msg("StdinPoll notify_rx channel closed"))),
191
WaitMode::Immediate => match self.notify_rx.try_recv() {
192
Ok(r) => Ok(r),
193
Err(TryRecvError::Empty) => Ok(PollState::NotReady),
194
Err(TryRecvError::Disconnected) => Err(Error::trap(anyhow::Error::msg(
195
"StdinPoll notify_rx channel closed",
196
))),
197
},
198
}
199
}
200
201
fn event_loop(request_rx: Receiver<()>, notify_tx: Sender<PollState>) -> ! {
202
use std::io::BufRead;
203
loop {
204
// Wait on a request:
205
request_rx.recv().expect("request_rx channel");
206
// Wait for data to appear in stdin. If fill_buf returns any slice, it means
207
// that either:
208
// (a) there is some data in stdin, if non-empty,
209
// (b) EOF was received, if its empty
210
// Linux returns `POLLIN` in both cases, so we imitate this behavior.
211
let resp = match std::io::stdin().lock().fill_buf() {
212
Ok(_) => PollState::Ready,
213
Err(e) => PollState::Error(e),
214
};
215
// Notify about data in stdin. If the read on this channel has timed out, the
216
// next poller will have to clean the channel.
217
notify_tx.send(resp).expect("notify_tx channel");
218
}
219
}
220
}
221
222