1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
use crate::handle::{Filetype, Handle};
use crate::sched::{
ClockEventData, Errno, Event, EventFdReadwrite, Eventrwflags, Eventtype, FdEventData,
};
use crate::sys::osdir::OsDir;
use crate::sys::osfile::OsFile;
use crate::sys::osother::OsOther;
use crate::sys::stdio::{Stderr, Stdin, Stdout};
use crate::sys::AsFile;
use crate::{Error, Result};
use lazy_static::lazy_static;
use std::convert::TryInto;
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use tracing::{debug, error, trace, warn};
struct StdinPoll {
request_tx: Sender<()>,
notify_rx: Receiver<PollState>,
}
enum PollState {
Ready,
NotReady, // it's not ready, but we didn't wait
TimedOut, // it's not ready and a timeout has occurred
Error(Errno),
}
enum WaitMode {
Timeout(Duration),
Infinite,
Immediate,
}
impl StdinPoll {
// This function should not be used directly
// Correctness of this function crucially depends on the fact that
// mpsc::Receiver is !Sync.
fn poll(&self, wait_mode: WaitMode) -> PollState {
// Clean up possible unread result from the previous poll
match self.notify_rx.try_recv() {
Ok(_) | Err(TryRecvError::Empty) => {}
Err(TryRecvError::Disconnected) => panic!("notify_rx channel closed"),
}
// Notify the worker thread that we want to poll stdin
self.request_tx.send(()).expect("request_tx channel closed");
// Wait for the worker thread to send a readiness notification
let pollret = match wait_mode {
WaitMode::Timeout(timeout) => {
self.notify_rx
.recv_timeout(timeout)
.unwrap_or_else(|e| match e {
RecvTimeoutError::Disconnected => panic!("notify_rx channel closed"),
RecvTimeoutError::Timeout => PollState::TimedOut,
})
}
WaitMode::Infinite => self.notify_rx.recv().expect("notify_rx channel closed"),
WaitMode::Immediate => self.notify_rx.try_recv().unwrap_or_else(|e| match e {
TryRecvError::Disconnected => panic!("notify_rx channel closed"),
TryRecvError::Empty => PollState::NotReady,
}),
};
pollret
}
fn event_loop(request_rx: Receiver<()>, notify_tx: Sender<PollState>) -> ! {
use std::io::BufRead;
loop {
// Wait for the request to poll stdin
request_rx.recv().expect("request_rx channel closed");
// Wait for data to appear in stdin.
// If `fill_buf` returns any slice, then it means that either
// (a) there some data in stdin, if it's non-empty
// (b) EOF was received, if it's empty
// Linux returns `POLLIN` in both cases, and we imitate this behavior.
let resp = match std::io::stdin().lock().fill_buf() {
Ok(_) => PollState::Ready,
Err(e) => PollState::Error(Errno::from(Error::from(e))),
};
// Notify the requestor about data in stdin. They may have already timed out,
// then the next requestor will have to clean the channel.
notify_tx.send(resp).expect("notify_tx channel closed");
}
}
}
lazy_static! {
static ref STDIN_POLL: Mutex<StdinPoll> = {
let (request_tx, request_rx) = mpsc::channel();
let (notify_tx, notify_rx) = mpsc::channel();
thread::spawn(move || StdinPoll::event_loop(request_rx, notify_tx));
Mutex::new(StdinPoll {
request_tx,
notify_rx,
})
};
}
fn make_rw_event(event: &FdEventData, nbytes: std::result::Result<u64, Errno>) -> Event {
let (nbytes, error) = match nbytes {
Ok(nbytes) => (nbytes, Errno::Success),
Err(e) => (u64::default(), e),
};
Event {
userdata: event.userdata,
type_: event.r#type,
error,
fd_readwrite: EventFdReadwrite {
nbytes,
flags: Eventrwflags::empty(),
},
}
}
fn make_timeout_event(timeout: &ClockEventData) -> Event {
Event {
userdata: timeout.userdata,
type_: Eventtype::Clock,
error: Errno::Success,
fd_readwrite: EventFdReadwrite {
nbytes: 0,
flags: Eventrwflags::empty(),
},
}
}
fn handle_timeout(timeout_event: ClockEventData, timeout: Duration, events: &mut Vec<Event>) {
thread::sleep(timeout);
handle_timeout_event(timeout_event, events);
}
fn handle_timeout_event(timeout_event: ClockEventData, events: &mut Vec<Event>) {
let new_event = make_timeout_event(&timeout_event);
events.push(new_event);
}
fn handle_rw_event(event: FdEventData, out_events: &mut Vec<Event>) {
let handle = &event.handle;
let size = if let Some(_) = handle.as_any().downcast_ref::<Stdin>() {
// We return the only universally correct lower bound, see the comment later in the function.
Ok(1)
} else if let Some(_) = handle.as_any().downcast_ref::<Stdout>() {
// On Unix, ioctl(FIONREAD) will return 0 for stdout. Emulate the same behavior on Windows.
Ok(0)
} else if let Some(_) = handle.as_any().downcast_ref::<Stderr>() {
// On Unix, ioctl(FIONREAD) will return 0 for stdout/stderr. Emulate the same behavior on Windows.
Ok(0)
} else {
if event.r#type == Eventtype::FdRead {
handle
.as_file()
.and_then(|f| f.metadata())
.map(|m| m.len())
.map_err(|ioerror| Errno::from(Error::from(ioerror)))
} else {
// 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
Ok(0)
}
};
let new_event = make_rw_event(&event, size);
out_events.push(new_event);
}
fn handle_error_event(event: FdEventData, error: Errno, out_events: &mut Vec<Event>) {
let new_event = make_rw_event(&event, Err(error));
out_events.push(new_event);
}
pub(crate) fn oneoff(
timeout: Option<ClockEventData>,
fd_events: Vec<FdEventData>,
events: &mut Vec<Event>,
) -> Result<()> {
let timeout = timeout
.map(|event| {
event
.delay
.try_into()
.map(Duration::from_nanos)
.map(|dur| (event, dur))
})
.transpose()?;
// With no events to listen, poll_oneoff just becomes a sleep.
if fd_events.is_empty() {
match timeout {
Some((event, dur)) => return Ok(handle_timeout(event, dur, events)),
// The implementation has to return Ok(()) in this case,
// cf. the comment in src/hostcalls_impl/misc.rs
None => return Ok(()),
}
}
let mut stdin_events = vec![];
let mut immediate_events = vec![];
let mut pipe_events = vec![];
for event in fd_events {
let handle = &event.handle;
if let Some(_) = handle.as_any().downcast_ref::<OsFile>() {
immediate_events.push(event);
} else if let Some(_) = handle.as_any().downcast_ref::<OsDir>() {
immediate_events.push(event);
} else if let Some(_) = handle.as_any().downcast_ref::<Stdin>() {
stdin_events.push(event);
} else if let Some(_) = handle.as_any().downcast_ref::<Stdout>() {
// stdout are always considered ready to write because there seems to
// be no way of checking if a write to stdout would block.
//
// If stdin is polled for anything else then reading, then it is also
// considered immediately ready, following the behavior on Linux.
immediate_events.push(event);
} else if let Some(_) = handle.as_any().downcast_ref::<Stderr>() {
// stderr are always considered ready to write because there seems to
// be no way of checking if a write to stdout would block.
//
// If stdin is polled for anything else then reading, then it is also
// considered immediately ready, following the behavior on Linux.
immediate_events.push(event);
} else if let Some(other) = handle.as_any().downcast_ref::<OsOther>() {
if other.get_file_type() == Filetype::SocketStream {
// We map pipe to SocketStream
pipe_events.push(event);
} else {
debug!(
"poll_oneoff: unsupported file type: {}",
other.get_file_type()
);
handle_error_event(event, Errno::Notsup, events);
}
} else {
tracing::error!("can poll FdEvent for OS resources only");
return Err(Error::Badf);
}
}
let immediate = !immediate_events.is_empty();
// Process all the events that do not require waiting.
if immediate {
trace!(" | have immediate events, will return immediately");
for event in immediate_events {
handle_rw_event(event, events);
}
}
if !stdin_events.is_empty() {
// waiting for data to arrive on stdin. This thread will not terminate.
//
// 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.
//
// One issue is that we are currently relying on the Rust libstd for interaction
// with stdin. More precisely, `io::stdin` is used via the `BufRead` trait,
// in the `fd_read` function, which always does buffering on the libstd side. [1]
// This means that even if there's still some unread data in stdin,
// the lower-level Windows system calls may return false negatives,
// claiming that stdin is empty.
//
// Theoretically, one could use `WaitForSingleObject` on the stdin handle
// to achieve (1). Unfortunately, this function doesn't seem to honor the
// requested timeout and to misbehaves after the stdin is closed.
//
// There appears to be no way of achieving (2) on Windows.
// [1]: https://github.com/rust-lang/rust/pull/12422
let waitmode = if immediate {
trace!(" | tentatively checking stdin");
WaitMode::Immediate
} else {
trace!(" | passively waiting on stdin");
match timeout {
Some((_event, dur)) => WaitMode::Timeout(dur),
None => WaitMode::Infinite,
}
};
let state = STDIN_POLL.lock().unwrap().poll(waitmode);
for event in stdin_events {
match state {
PollState::Ready => handle_rw_event(event, events),
PollState::NotReady => {} // not immediately available, so just ignore
PollState::TimedOut => handle_timeout_event(timeout.unwrap().0, events),
PollState::Error(e) => handle_error_event(event, e, events),
}
}
}
if !immediate && !pipe_events.is_empty() {
trace!(" | actively polling pipes");
match timeout {
Some((event, dur)) => {
// In the tests stdin is replaced with a dummy pipe, so for now
// we just time out. Support for pipes will be decided later on.
warn!("Polling pipes not supported on Windows, will just time out.");
handle_timeout(event, dur, events);
}
None => {
error!("Polling only pipes with no timeout not supported on Windows.");
return Err(Error::Notsup);
}
}
}
Ok(())
}