Skip to main content

wasmtime_wasi/cli/
locked_async.rs

1use crate::cli::{IsTerminal, StdinStream, StdoutStream};
2use crate::p2;
3use bytes::Bytes;
4use std::mem;
5use std::pin::Pin;
6use std::sync::Arc;
7use std::task::{Context, Poll, ready};
8use tokio::io::{self, AsyncRead, AsyncWrite};
9use tokio::sync::{Mutex, OwnedMutexGuard};
10use wasmtime_wasi_io::streams::{InputStream, OutputStream};
11
12/// An impl of [`StdinStream`] built on top of [`AsyncRead`].
13//
14// Note the usage of `tokio::sync::Mutex` here as opposed to a
15// `std::sync::Mutex`. This is intentionally done to implement the `Pollable`
16// variant of this trait. Note that in doing so we're left with the quandry of
17// how to implement methods of `InputStream` since those methods are not
18// `async`. They're currently implemented with `try_lock`, which then raises the
19// question of what to do on contention. Currently traps are returned.
20//
21// Why should it be ok to return a trap? In general concurrency/contention
22// shouldn't return a trap since it should be able to happen normally. The
23// current assumption, though, is that WASI stdin/stdout streams are special
24// enough that the contention case should never come up in practice. Currently
25// in WASI there is no actually concurrency, there's just the items in a single
26// `Store` and that store owns all of its I/O in a single Tokio task. There's no
27// means to actually spawn multiple Tokio tasks that use the same store. This
28// means at the very least that there's zero parallelism. Due to the lack of
29// multiple tasks that also means that there's no concurrency either.
30//
31// This `AsyncStdinStream` wrapper is only intended to be used by the WASI
32// bindings themselves. It's possible for the host to take this and work with it
33// on its own task, but that's niche enough it's not designed for.
34//
35// Overall that means that the guest is either calling `Pollable` or
36// `InputStream` methods. This means that there should never be contention
37// between the two at this time. This may all change in the future with WASI
38// 0.3, but perhaps we'll have a better story for stdio at that time (see the
39// doc block on the `OutputStream` impl below)
40pub struct AsyncStdinStream(Arc<Mutex<p2::pipe::AsyncReadStream>>);
41
42impl AsyncStdinStream {
43    pub fn new(s: impl AsyncRead + Send + Sync + 'static) -> Self {
44        Self(Arc::new(Mutex::new(p2::pipe::AsyncReadStream::new(s))))
45    }
46}
47
48impl StdinStream for AsyncStdinStream {
49    fn p2_stream(&self) -> Box<dyn InputStream> {
50        Box::new(Self(self.0.clone()))
51    }
52    fn async_stream(&self) -> Box<dyn AsyncRead + Send + Sync> {
53        Box::new(StdioHandle::Ready(self.0.clone()))
54    }
55}
56
57impl IsTerminal for AsyncStdinStream {
58    fn is_terminal(&self) -> bool {
59        false
60    }
61}
62
63#[async_trait::async_trait]
64impl InputStream for AsyncStdinStream {
65    fn read(&mut self, size: usize) -> Result<bytes::Bytes, p2::StreamError> {
66        match self.0.try_lock() {
67            Ok(mut stream) => stream.read(size),
68            Err(_) => Err(p2::StreamError::trap("concurrent reads are not supported")),
69        }
70    }
71    fn skip(&mut self, size: usize) -> Result<usize, p2::StreamError> {
72        match self.0.try_lock() {
73            Ok(mut stream) => stream.skip(size),
74            Err(_) => Err(p2::StreamError::trap("concurrent skips are not supported")),
75        }
76    }
77    async fn cancel(&mut self) {
78        // Cancel the inner stream if we're the last reference to it:
79        if let Some(mutex) = Arc::get_mut(&mut self.0) {
80            match mutex.try_lock() {
81                Ok(mut stream) => stream.cancel().await,
82                Err(_) => {}
83            }
84        }
85    }
86}
87
88#[async_trait::async_trait]
89impl p2::Pollable for AsyncStdinStream {
90    async fn ready(&mut self) {
91        self.0.lock().await.ready().await
92    }
93}
94
95impl AsyncRead for StdioHandle<p2::pipe::AsyncReadStream> {
96    fn poll_read(
97        mut self: Pin<&mut Self>,
98        cx: &mut Context<'_>,
99        buf: &mut io::ReadBuf<'_>,
100    ) -> Poll<io::Result<()>> {
101        match ready!(self.as_mut().poll(cx, |cx, g| {
102            ready!(g.poll_ready(cx));
103            Poll::Ready(g.read(buf.remaining()))
104        })) {
105            Some(Ok(bytes)) => {
106                buf.put_slice(&bytes);
107                Poll::Ready(Ok(()))
108            }
109            Some(Err(e)) => Poll::Ready(Err(e)),
110            // If the guard can't be acquired that means that this stream is
111            // closed, so return that we're ready without filling in data.
112            None => Poll::Ready(Ok(())),
113        }
114    }
115}
116
117/// A wrapper of [`crate::p2::pipe::AsyncWriteStream`] that implements
118/// [`StdoutStream`]. Note that the [`OutputStream`] impl for this is not
119/// correct when used for interleaved async IO.
120//
121// Note that the use of `tokio::sync::Mutex` here is intentional, in addition to
122// the `try_lock()` calls below in the implementation of `OutputStream`. For
123// more information see the documentation on `AsyncStdinStream`.
124pub struct AsyncStdoutStream(Arc<Mutex<p2::pipe::AsyncWriteStream>>);
125
126impl AsyncStdoutStream {
127    pub fn new(budget: usize, s: impl AsyncWrite + Send + Sync + 'static) -> Self {
128        Self(Arc::new(Mutex::new(p2::pipe::AsyncWriteStream::new(
129            budget, s,
130        ))))
131    }
132}
133
134impl StdoutStream for AsyncStdoutStream {
135    fn p2_stream(&self) -> Box<dyn OutputStream> {
136        Box::new(Self(self.0.clone()))
137    }
138    fn async_stream(&self) -> Box<dyn AsyncWrite + Send + Sync> {
139        Box::new(StdoutHandle {
140            handle: StdioHandle::Ready(self.0.clone()),
141            flushing: false,
142        })
143    }
144}
145
146impl IsTerminal for AsyncStdoutStream {
147    fn is_terminal(&self) -> bool {
148        false
149    }
150}
151
152// This implementation is known to be bogus. All check-writes and writes are
153// directed at the same underlying stream. The check-write/write protocol does
154// require the size returned by a check-write to be accepted by write, even if
155// other side-effects happen between those calls, and this implementation
156// permits another view (created by StdoutStream::stream()) of the same
157// underlying stream to accept a write which will invalidate a prior
158// check-write of another view.
159// Ultimately, the Std{in,out}Stream::stream() methods exist because many
160// different places in a linked component (which may itself contain many
161// modules) may need to access stdio without any coordination to keep those
162// accesses all using pointing to the same resource. So, we allow many
163// resources to be created. We have the reasonable expectation that programs
164// won't attempt to interleave async IO from these disparate uses of stdio.
165// If that expectation doesn't turn out to be true, and you find yourself at
166// this comment to correct it: sorry about that.
167#[async_trait::async_trait]
168impl OutputStream for AsyncStdoutStream {
169    fn check_write(&mut self) -> Result<usize, p2::StreamError> {
170        match self.0.try_lock() {
171            Ok(mut stream) => stream.check_write(),
172            Err(_) => Err(p2::StreamError::trap("concurrent writes are not supported")),
173        }
174    }
175    fn write(&mut self, bytes: Bytes) -> Result<(), p2::StreamError> {
176        match self.0.try_lock() {
177            Ok(mut stream) => stream.write(bytes),
178            Err(_) => Err(p2::StreamError::trap("concurrent writes not supported yet")),
179        }
180    }
181    fn flush(&mut self) -> Result<(), p2::StreamError> {
182        match self.0.try_lock() {
183            Ok(mut stream) => stream.flush(),
184            Err(_) => Err(p2::StreamError::trap(
185                "concurrent flushes not supported yet",
186            )),
187        }
188    }
189    async fn cancel(&mut self) {
190        // Cancel the inner stream if we're the last reference to it:
191        if let Some(mutex) = Arc::get_mut(&mut self.0) {
192            match mutex.try_lock() {
193                Ok(mut stream) => stream.cancel().await,
194                Err(_) => {}
195            }
196        }
197    }
198}
199
200#[async_trait::async_trait]
201impl p2::Pollable for AsyncStdoutStream {
202    async fn ready(&mut self) {
203        self.0.lock().await.ready().await
204    }
205}
206
207struct StdoutHandle {
208    handle: StdioHandle<p2::pipe::AsyncWriteStream>,
209    flushing: bool,
210}
211
212impl AsyncWrite for StdoutHandle {
213    fn poll_write(
214        mut self: Pin<&mut Self>,
215        cx: &mut Context<'_>,
216        buf: &[u8],
217    ) -> Poll<io::Result<usize>> {
218        if buf.is_empty() {
219            return Poll::Ready(Ok(0));
220        }
221        match ready!(Pin::new(&mut self.handle).poll(cx, |cx, i| {
222            // Use `check_write` to see if the stream isn't yet ready (a 0
223            // return value) and also how much it's willing to accept should it
224            // be nonzero.
225            loop {
226                let amt = match i.check_write() {
227                    Ok(amt) => amt.min(buf.len()),
228                    Err(e) => return Poll::Ready(Err(e)),
229                };
230                if amt > 0 {
231                    return Poll::Ready(i.write(Bytes::copy_from_slice(&buf[..amt])).map(|()| amt));
232                }
233                ready!(i.poll_ready(cx));
234            }
235        })) {
236            Some(result) => Poll::Ready(result),
237            None => Poll::Ready(Ok(0)),
238        }
239    }
240    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
241        // First request a flush of the underlying stream.
242        if !self.flushing {
243            match ready!(Pin::new(&mut self.handle).poll(cx, |cx, i| {
244                ready!(i.poll_ready(cx));
245                Poll::Ready(i.flush())
246            })) {
247                Some(Ok(())) => self.flushing = true,
248                Some(Err(e)) => return Poll::Ready(Err(e)),
249                None => return Poll::Ready(Ok(())),
250            }
251        }
252
253        // Next wait for the stream to become ready again, meaning that the
254        // flush has completed. Here `check_write` is called again to pick up
255        // any error which happened along the way.
256        let result = ready!(Pin::new(&mut self.handle).poll(cx, |cx, i| {
257            ready!(i.poll_ready(cx));
258            Poll::Ready(i.check_write().map(|_| ()))
259        }));
260        self.flushing = false;
261        match result {
262            Some(result) => Poll::Ready(result),
263            None => Poll::Ready(Ok(())),
264        }
265    }
266    fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
267        Poll::Ready(Ok(()))
268    }
269}
270
271/// State necessary for effectively transforming `Arc<Mutex<dyn
272/// {Input,Output}Stream>>` into `Async{Read,Write}`.
273///
274/// This is a beast and inefficient. It should get the job done in theory but
275/// one must truly ask oneself at some point "but at what cost".
276///
277/// More seriously, it's unclear if this is the best way to transform a single
278/// `AsyncRead` into a "multiple `AsyncRead`". This certainly is an attempt and
279/// the hope is that everything here is private enough that we can refactor as
280/// necessary in the future without causing much churn.
281enum StdioHandle<S> {
282    Ready(Arc<Mutex<S>>),
283    Locking(Box<dyn Future<Output = OwnedMutexGuard<S>> + Send + Sync>),
284    Locked(OwnedMutexGuard<S>),
285    Closed,
286}
287
288impl<S> StdioHandle<S>
289where
290    S: Send + Sync + 'static,
291{
292    fn poll<T>(
293        mut self: Pin<&mut Self>,
294        cx: &mut Context<'_>,
295        op: impl FnOnce(&mut Context<'_>, &mut S) -> Poll<p2::StreamResult<T>>,
296    ) -> Poll<Option<io::Result<T>>> {
297        // If we don't currently have the lock on this handle, initiate the
298        // lock acquisition.
299        if let StdioHandle::Ready(lock) = &*self {
300            self.set(StdioHandle::Locking(Box::new(lock.clone().lock_owned())));
301        }
302
303        // If we're in the process of locking this handle, wait for that to
304        // finish.
305        if let Some(lock) = self.as_mut().as_locking() {
306            let guard = ready!(lock.poll(cx));
307            self.set(StdioHandle::Locked(guard));
308        }
309
310        let mut guard = match self.as_mut().take_guard() {
311            Some(guard) => guard,
312            // If the guard can't be acquired that means that this stream is
313            // closed, so return that we're ready without filling in data.
314            None => return Poll::Ready(None),
315        };
316
317        // Perform the I/O and delegate on the result.
318        let result = match op(cx, &mut guard) {
319            Poll::Ready(result) => result,
320
321            // If the stream isn't ready yet then restore our "locked" state
322            // since we haven't finished, then return pending.
323            Poll::Pending => {
324                self.set(StdioHandle::Locked(guard));
325                return Poll::Pending;
326            }
327        };
328        match result {
329            // The I/O succeeded so relinquish the lock on this stream by
330            // transitioning back to the "Ready" state.
331            Ok(result) => {
332                self.set(StdioHandle::Ready(OwnedMutexGuard::mutex(&guard).clone()));
333                Poll::Ready(Some(Ok(result)))
334            }
335
336            // The stream is closed, and `take_guard` above already set the
337            // closed state, so return nothing indicating the closure.
338            Err(p2::StreamError::Closed) => Poll::Ready(None),
339
340            // The stream failed so propagate the error. Errors should only
341            // come from the underlying I/O object and thus should cast
342            // successfully. Additionally `take_guard` replaced our state
343            // with "closed" above which is the desired state at this point.
344            Err(p2::StreamError::LastOperationFailed(e)) => {
345                Poll::Ready(Some(Err(e.downcast().unwrap())))
346            }
347
348            // Shouldn't be possible to produce a trap here.
349            Err(p2::StreamError::Trap(_)) => unreachable!(),
350        }
351    }
352
353    fn as_locking(
354        self: Pin<&mut Self>,
355    ) -> Option<Pin<&mut dyn Future<Output = OwnedMutexGuard<S>>>> {
356        // SAFETY: this is a pin-projection from `self` into the `Locking`
357        // field.
358        unsafe {
359            match self.get_unchecked_mut() {
360                StdioHandle::Locking(future) => Some(Pin::new_unchecked(&mut **future)),
361                _ => None,
362            }
363        }
364    }
365
366    fn take_guard(self: Pin<&mut Self>) -> Option<OwnedMutexGuard<S>> {
367        if !matches!(*self, StdioHandle::Locked(_)) {
368            return None;
369        }
370        // SAFETY: the `Locked` arm is safe to move as it's an invariant of this
371        // type that it's not pinned.
372        unsafe {
373            match mem::replace(self.get_unchecked_mut(), StdioHandle::Closed) {
374                StdioHandle::Locked(guard) => Some(guard),
375                _ => unreachable!(),
376            }
377        }
378    }
379}