Skip to main content

visa_rs/
async_tokio.rs

1use crate::{async_io::AsyncInstrument, Error, Instrument, JobID};
2use std::{
3    io,
4    pin::Pin,
5    task::{Context, Poll},
6};
7use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
8
9/// Adapts a VISA instrument to Tokio's [`AsyncRead`] and [`AsyncWrite`] traits.
10///
11/// `write().await` and `write_all().await` start VISA transfers as bytes are accepted;
12/// they do not wait for completion or automatically flush. `flush().await` waits for
13/// completion and reports write errors; it is not needed to start the transfer.
14/// Flush before reading a response or dropping/converting the adapter.
15///
16/// Dropping a Tokio read, write, or flush future does not cancel an already started
17/// transfer. Dropping or converting the adapter requests cancellation of pending I/O.
18pub struct InstrumentTokioAdapter {
19    // VISA retains buffer pointers until completion; never lend it the caller's buffer.
20    instr: AsyncInstrument,
21    read_current: Option<JobID>,
22    write_current: Option<JobID>,
23    // Undelivered bytes and cursor. Recycle the allocation once drained.
24    read_buf: Vec<u8>,
25    read_pos: usize,
26    // Recycled allocation for the next write.
27    write_buf: Vec<u8>,
28}
29
30impl TryFrom<Instrument> for InstrumentTokioAdapter {
31    type Error = Error;
32    fn try_from(value: Instrument) -> Result<Self, Self::Error> {
33        Ok(Self::new(AsyncInstrument::new(value)?))
34    }
35}
36
37impl From<AsyncInstrument> for InstrumentTokioAdapter {
38    fn from(value: AsyncInstrument) -> Self {
39        Self::new(value)
40    }
41}
42
43impl From<InstrumentTokioAdapter> for AsyncInstrument {
44    fn from(mut value: InstrumentTokioAdapter) -> Self {
45        value.cancel_in_flight();
46        let value = std::mem::ManuallyDrop::new(value);
47        // SAFETY: `value` is never dropped, so reading each field out of it moves it
48        // exactly once. Both jobs are already cancelled, which is all `Drop` does.
49        let instr = unsafe { std::ptr::read(&value.instr) };
50        drop(unsafe { std::ptr::read(&value.read_buf) });
51        drop(unsafe { std::ptr::read(&value.write_buf) });
52        instr
53    }
54}
55
56impl From<InstrumentTokioAdapter> for Instrument {
57    fn from(value: InstrumentTokioAdapter) -> Self {
58        let async_instr: AsyncInstrument = value.into();
59        async_instr.into()
60    }
61}
62
63impl InstrumentTokioAdapter {
64    pub fn new(instr: AsyncInstrument) -> Self {
65        Self {
66            instr,
67            read_current: None,
68            write_current: None,
69            read_buf: Vec::new(),
70            read_pos: 0,
71            write_buf: Vec::new(),
72        }
73    }
74
75    fn map_vs_err(err: Error) -> io::Error {
76        io::Error::other(err)
77    }
78
79    fn cancel_in_flight(&mut self) {
80        if let Some(job_id) = self.read_current.take() {
81            self.instr.cancel_job(job_id);
82        }
83        if let Some(job_id) = self.write_current.take() {
84            self.instr.cancel_job(job_id);
85        }
86    }
87
88    // Deliver what fits, retaining the remainder for the next poll_read.
89    fn deliver(&mut self, buf: &mut ReadBuf<'_>) {
90        let available = &self.read_buf[self.read_pos..];
91        let n = available.len().min(buf.remaining());
92        buf.put_slice(&available[..n]);
93        self.read_pos += n;
94        if self.read_pos == self.read_buf.len() {
95            self.read_buf.clear();
96            self.read_pos = 0;
97        }
98    }
99
100    fn poll_current_write(&mut self, cx: &Context<'_>) -> Poll<io::Result<()>> {
101        let Some(job_id) = self.write_current else {
102            return Poll::Ready(Ok(()));
103        };
104        match self.instr.poll_job(job_id, cx) {
105            Poll::Ready((result, mut buf)) => {
106                self.write_current = None;
107                let expected = buf.len();
108                buf.clear();
109                self.write_buf = buf;
110                let completion = result.map_err(|e| {
111                    log::error!("tokio async write completion error: {}", e);
112                    Self::map_vs_err(e)
113                })?;
114                // These bytes have already been acknowledged to the caller. A short
115                // completion must not silently discard the untransferred suffix.
116                if completion.count != expected {
117                    let kind = if completion.count == 0 {
118                        io::ErrorKind::WriteZero
119                    } else if completion.count > expected {
120                        io::ErrorKind::InvalidData
121                    } else {
122                        io::ErrorKind::Other
123                    };
124                    return Poll::Ready(Err(io::Error::new(
125                        kind,
126                        format!(
127                            "VISA wrote {} of {expected} accepted bytes",
128                            completion.count
129                        ),
130                    )));
131                }
132                Poll::Ready(Ok(()))
133            }
134            Poll::Pending => Poll::Pending,
135        }
136    }
137}
138
139impl Drop for InstrumentTokioAdapter {
140    fn drop(&mut self) {
141        self.cancel_in_flight();
142    }
143}
144
145impl AsyncRead for InstrumentTokioAdapter {
146    fn poll_read(
147        self: Pin<&mut Self>,
148        cx: &mut Context<'_>,
149        buf: &mut ReadBuf<'_>,
150    ) -> Poll<io::Result<()>> {
151        let me = self.get_mut();
152        if buf.remaining() == 0 {
153            return Poll::Ready(Ok(()));
154        }
155        if me.read_current.is_none() {
156            // Leftovers from a previous transfer come first.
157            if me.read_pos < me.read_buf.len() {
158                me.deliver(buf);
159                return Poll::Ready(Ok(()));
160            }
161            let mut owned = std::mem::take(&mut me.read_buf);
162            owned.clear();
163            owned.resize(
164                crate::instrument::async_transfer_count(buf.remaining()) as usize,
165                0,
166            );
167            me.read_pos = 0;
168            match me.instr.start_read_id(owned, cx.waker()) {
169                Ok(job_id) => me.read_current = Some(job_id),
170                Err(e) => return Poll::Ready(Err(Self::map_vs_err(e))),
171            }
172        }
173        let job_id = me.read_current.expect("set immediately above");
174        match me.instr.poll_job(job_id, cx) {
175            Poll::Ready((Ok(c), mut owned)) => {
176                me.read_current = None;
177                owned.truncate(c.count.min(owned.len()));
178                me.read_buf = owned;
179                me.read_pos = 0;
180                me.deliver(buf);
181                Poll::Ready(Ok(()))
182            }
183            Poll::Ready((Err(e), mut owned)) => {
184                me.read_current = None;
185                owned.clear();
186                me.read_buf = owned;
187                me.read_pos = 0;
188                log::error!("tokio async read completion error: {}", e);
189                Poll::Ready(Err(Self::map_vs_err(e)))
190            }
191            Poll::Pending => Poll::Pending,
192        }
193    }
194}
195
196impl AsyncWrite for InstrumentTokioAdapter {
197    /// Starts writing accepted bytes. While the previous transfer is pending,
198    /// no bytes from this call are accepted. Completion errors are deferred until
199    /// the next write or flush.
200    fn poll_write(
201        self: Pin<&mut Self>,
202        cx: &mut Context<'_>,
203        buf: &[u8],
204    ) -> Poll<io::Result<usize>> {
205        let me = self.get_mut();
206        match me.poll_current_write(cx) {
207            Poll::Pending => {
208                if !buf.is_empty() {
209                    log::warn!(
210                        "tokio write waiting for previous VISA job; {} input bytes not accepted yet",
211                        buf.len()
212                    );
213                }
214                return Poll::Pending;
215            }
216            Poll::Ready(result) => result?,
217        }
218        if buf.is_empty() {
219            return Poll::Ready(Ok(0));
220        }
221        // VISA's count is 32-bit. A larger slice is accepted in multiple writes.
222        let n = crate::instrument::async_transfer_count(buf.len()) as usize;
223        let mut owned = std::mem::take(&mut me.write_buf);
224        owned.clear();
225        owned.extend_from_slice(&buf[..n]);
226        match me.instr.start_write_id(owned, cx.waker()) {
227            Ok(job_id) => me.write_current = Some(job_id),
228            Err(e) => return Poll::Ready(Err(Self::map_vs_err(e))),
229        }
230        Poll::Ready(Ok(n))
231    }
232
233    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
234        use crate::flags::FlushMode;
235        let me = self.get_mut();
236        // A queued write has to reach the device before flushing means anything.
237        std::task::ready!(me.poll_current_write(cx))?;
238        me.instr
239            .instrument()
240            .visa_flush(FlushMode::WRITE_BUF | FlushMode::IO_OUT_BUF)
241            .map_err(Self::map_vs_err)?;
242        Poll::Ready(Ok(()))
243    }
244
245    /// Flushes pending output without closing the session.
246    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
247        self.poll_flush(cx)
248    }
249}