1 //! Handling for standard in using a worker task. 2 //! 3 //! Standard input is a global singleton resource for the entire program which 4 //! needs special care. Currently this implementation adheres to a few 5 //! constraints which make this nontrivial to implement. 6 //! 7 //! * Any number of guest wasm programs can read stdin. While this doesn't make 8 //! a ton of sense semantically they shouldn't block forever. Instead it's a 9 //! race to see who actually reads which parts of stdin. 10 //! 11 //! * Data from stdin isn't actually read unless requested. This is done to try 12 //! to be a good neighbor to others running in the process. Under the 13 //! assumption that most programs have one "thing" which reads stdin the 14 //! actual consumption of bytes is delayed until the wasm guest is dynamically 15 //! chosen to be that "thing". Before that data from stdin is not consumed to 16 //! avoid taking it from other components in the process. 17 //! 18 //! * Tokio's documentation indicates that "interactive stdin" is best done with 19 //! a helper thread to avoid blocking shutdown of the event loop. That's 20 //! respected here where all stdin reading happens on a blocking helper thread 21 //! that, at this time, is never shut down. 22 //! 23 //! This module is one that's likely to change over time though as new systems 24 //! are encountered along with preexisting bugs. 25 26 use crate::cli::{IsTerminal, StdinStream}; 27 use bytes::{Bytes, BytesMut}; 28 use std::io::Read; 29 use std::mem; 30 use std::pin::Pin; 31 use std::sync::{Condvar, Mutex, OnceLock}; 32 use std::task::{Context, Poll}; 33 use tokio::io::{self, AsyncRead, ReadBuf}; 34 use tokio::sync::Notify; 35 use tokio::sync::futures::Notified; 36 use wasmtime_wasi_io::{ 37 poll::Pollable, 38 streams::{InputStream, StreamError}, 39 }; 40 41 // Implementation for tokio::io::Stdin 42 impl IsTerminal for tokio::io::Stdin { 43 fn is_terminal(&self) -> bool { 44 std::io::stdin().is_terminal() 45 } 46 } 47 impl StdinStream for tokio::io::Stdin { 48 fn p2_stream(&self) -> Box<dyn InputStream> { 49 Box::new(WasiStdin) 50 } 51 fn async_stream(&self) -> Box<dyn AsyncRead + Send + Sync> { 52 Box::new(WasiStdinAsyncRead::Ready) 53 } 54 } 55 56 // Implementation for std::io::Stdin 57 impl IsTerminal for std::io::Stdin { 58 fn is_terminal(&self) -> bool { 59 std::io::IsTerminal::is_terminal(self) 60 } 61 } 62 impl StdinStream for std::io::Stdin { 63 fn p2_stream(&self) -> Box<dyn InputStream> { 64 Box::new(WasiStdin) 65 } 66 fn async_stream(&self) -> Box<dyn AsyncRead + Send + Sync> { 67 Box::new(WasiStdinAsyncRead::Ready) 68 } 69 } 70 71 #[derive(Default)] 72 struct GlobalStdin { 73 state: Mutex<StdinState>, 74 read_requested: Condvar, 75 read_completed: Notify, 76 } 77 78 #[derive(Default, Debug)] 79 enum StdinState { 80 #[default] 81 ReadNotRequested, 82 ReadRequested, 83 Data(BytesMut), 84 Error(std::io::Error), 85 Closed, 86 } 87 88 impl GlobalStdin { 89 fn get() -> &'static GlobalStdin { 90 static STDIN: OnceLock<GlobalStdin> = OnceLock::new(); 91 STDIN.get_or_init(|| create()) 92 } 93 } 94 95 fn create() -> GlobalStdin { 96 std::thread::spawn(|| { 97 let state = GlobalStdin::get(); 98 loop { 99 // Wait for a read to be requested, but don't hold the lock across 100 // the blocking read. 101 let mut lock = state.state.lock().unwrap(); 102 lock = state 103 .read_requested 104 .wait_while(lock, |state| !matches!(state, StdinState::ReadRequested)) 105 .unwrap(); 106 drop(lock); 107 108 let mut bytes = BytesMut::zeroed(1024); 109 let (new_state, done) = match std::io::stdin().read(&mut bytes) { 110 Ok(0) => (StdinState::Closed, true), 111 Ok(nbytes) => { 112 bytes.truncate(nbytes); 113 (StdinState::Data(bytes), false) 114 } 115 Err(e) => (StdinState::Error(e), true), 116 }; 117 118 // After the blocking read completes the state should not have been 119 // tampered with. 120 debug_assert!(matches!( 121 *state.state.lock().unwrap(), 122 StdinState::ReadRequested 123 )); 124 *state.state.lock().unwrap() = new_state; 125 state.read_completed.notify_waiters(); 126 if done { 127 break; 128 } 129 } 130 }); 131 132 GlobalStdin::default() 133 } 134 135 struct WasiStdin; 136 137 #[async_trait::async_trait] 138 impl InputStream for WasiStdin { 139 fn read(&mut self, size: usize) -> Result<Bytes, StreamError> { 140 let g = GlobalStdin::get(); 141 let mut locked = g.state.lock().unwrap(); 142 match mem::replace(&mut *locked, StdinState::ReadRequested) { 143 StdinState::ReadNotRequested => { 144 g.read_requested.notify_one(); 145 Ok(Bytes::new()) 146 } 147 StdinState::ReadRequested => Ok(Bytes::new()), 148 StdinState::Data(mut data) => { 149 let size = data.len().min(size); 150 let bytes = data.split_to(size); 151 *locked = if data.is_empty() { 152 StdinState::ReadNotRequested 153 } else { 154 StdinState::Data(data) 155 }; 156 Ok(bytes.freeze()) 157 } 158 StdinState::Error(e) => { 159 *locked = StdinState::Closed; 160 Err(StreamError::LastOperationFailed(e.into())) 161 } 162 StdinState::Closed => { 163 *locked = StdinState::Closed; 164 Err(StreamError::Closed) 165 } 166 } 167 } 168 } 169 170 #[async_trait::async_trait] 171 impl Pollable for WasiStdin { 172 async fn ready(&mut self) { 173 let g = GlobalStdin::get(); 174 175 // Scope the synchronous `state.lock()` to this block which does not 176 // `.await` inside of it. 177 let notified = { 178 let mut locked = g.state.lock().unwrap(); 179 match *locked { 180 // If a read isn't requested yet 181 StdinState::ReadNotRequested => { 182 g.read_requested.notify_one(); 183 *locked = StdinState::ReadRequested; 184 g.read_completed.notified() 185 } 186 StdinState::ReadRequested => g.read_completed.notified(), 187 StdinState::Data(_) | StdinState::Closed | StdinState::Error(_) => return, 188 } 189 }; 190 191 notified.await; 192 } 193 } 194 195 enum WasiStdinAsyncRead { 196 Ready, 197 Waiting(Notified<'static>), 198 } 199 200 impl AsyncRead for WasiStdinAsyncRead { 201 fn poll_read( 202 mut self: Pin<&mut Self>, 203 cx: &mut Context<'_>, 204 buf: &mut ReadBuf<'_>, 205 ) -> Poll<io::Result<()>> { 206 let g = GlobalStdin::get(); 207 208 // Perform everything below in a `loop` to handle the case that a read 209 // was stolen by another thread, for example, or perhaps a spurious 210 // notification to `Notified`. 211 loop { 212 // If we were previously blocked on reading a "ready" notification, 213 // wait for that notification to complete. 214 if let Some(notified) = self.as_mut().notified_future() { 215 match notified.poll(cx) { 216 Poll::Ready(()) => self.set(WasiStdinAsyncRead::Ready), 217 Poll::Pending => break Poll::Pending, 218 } 219 } 220 221 assert!(matches!(*self, WasiStdinAsyncRead::Ready)); 222 223 // Once we're in the "ready" state then take a look at the global 224 // state of stdin. 225 let mut locked = g.state.lock().unwrap(); 226 match mem::replace(&mut *locked, StdinState::ReadRequested) { 227 // If data is available then drain what we can into `buf`. 228 StdinState::Data(mut data) => { 229 let size = data.len().min(buf.remaining()); 230 let bytes = data.split_to(size); 231 *locked = if data.is_empty() { 232 StdinState::ReadNotRequested 233 } else { 234 StdinState::Data(data) 235 }; 236 buf.put_slice(&bytes); 237 break Poll::Ready(Ok(())); 238 } 239 240 // If stdin failed to be read then we fail with that error and 241 // transition to "closed" 242 StdinState::Error(e) => { 243 *locked = StdinState::Closed; 244 break Poll::Ready(Err(e)); 245 } 246 247 // If stdin is closed, keep it closed. 248 StdinState::Closed => { 249 *locked = StdinState::Closed; 250 break Poll::Ready(Ok(())); 251 } 252 253 // For these states we indicate that a read is requested, if it 254 // wasn't previously requested, and then we transition to 255 // `Waiting` below by falling through outside this `match`. 256 StdinState::ReadNotRequested => { 257 g.read_requested.notify_one(); 258 } 259 StdinState::ReadRequested => {} 260 } 261 262 self.set(WasiStdinAsyncRead::Waiting(g.read_completed.notified())); 263 264 // Intentionally drop the lock after the `notified()` future 265 // creation just above as to work correctly this needs to happen 266 // within the lock. 267 drop(locked); 268 } 269 } 270 } 271 272 impl WasiStdinAsyncRead { 273 fn notified_future(self: Pin<&mut Self>) -> Option<Pin<&mut Notified<'static>>> { 274 // SAFETY: this is a pin-projection from `self` to the field `Notified` 275 // internally. Given that `self` is pinned it should be safe to acquire 276 // a pinned version of the internal field. 277 unsafe { 278 match self.get_unchecked_mut() { 279 WasiStdinAsyncRead::Ready => None, 280 WasiStdinAsyncRead::Waiting(notified) => Some(Pin::new_unchecked(notified)), 281 } 282 } 283 } 284 } 285