Skip to main content

visa_rs/async_io/
future.rs

1//! The `Future` types driving one logical transfer each.
2
3use super::{note_sync_completion, AsyncInstrument, Completed};
4use crate::instrument::async_transfer_count;
5use crate::{
6    enums::status::{CompletionCode, ErrorCode},
7    JobID, Result,
8};
9use std::{
10    future::Future,
11    pin::Pin,
12    task::{Context, Poll},
13};
14
15// Response-read chunk size: a memory/round-trip tradeoff, not a response length limit.
16const READ_CHUNK: usize = 4096;
17
18// Append a completed transfer and decide whether the logical read is finished.
19fn collect_chunk(
20    out: &mut Vec<u8>,
21    limit: Option<usize>,
22    Completed { count, completion }: Completed,
23    mut buf: Vec<u8>,
24) -> Result<bool> {
25    let count = count.min(buf.len());
26    if out.is_empty() {
27        buf.truncate(count);
28        *out = buf;
29    } else {
30        out.extend_from_slice(&buf[..count]);
31    }
32    if let Some(limit) = limit {
33        if out.len() == limit {
34            return Ok(true);
35        }
36        // A successful zero-byte completion cannot advance an exact read. Fail
37        // instead of repeatedly submitting reads that might complete inline.
38        if count == 0 {
39            return Err(ErrorCode::ErrorIo.into());
40        }
41        return Ok(false);
42    }
43    // Only response-oriented reads stop at END or the termination character.
44    Ok(completion != CompletionCode::SuccessMaxCnt)
45}
46
47/// Future returned by [`AsyncInstrument::async_read`] and
48/// [`AsyncInstrument::async_read_exact`].
49///
50/// Dropping it before completion requests cancellation.
51#[must_use = "futures do nothing unless you `.await` or poll them"]
52pub struct AsyncRead<'a> {
53    ss: &'a AsyncInstrument,
54    // None: read until the device ends the response. Some(n): read exactly n bytes.
55    limit: Option<usize>,
56    timeout: std::time::Duration,
57    out: Vec<u8>,
58    id: Option<JobID>,
59    prepared: bool,
60    done: bool,
61}
62
63impl<'a> AsyncRead<'a> {
64    pub(super) fn new(
65        ss: &'a AsyncInstrument,
66        limit: Option<usize>,
67        timeout: std::time::Duration,
68    ) -> Self {
69        Self {
70            ss,
71            limit,
72            timeout,
73            out: Vec::new(),
74            id: None,
75            prepared: false,
76            done: false,
77        }
78    }
79
80    fn chunk_len(&self) -> usize {
81        match self.limit {
82            // Request the remaining length up to VISA's count limit; do not split
83            // known-length reads into the smaller response-oriented READ_CHUNK.
84            Some(n) => async_transfer_count(n.saturating_sub(self.out.len())) as usize,
85            // Unknown length: a fixed chunk, looped until the device ends the response.
86            None => READ_CHUNK,
87        }
88    }
89
90    fn finish(&mut self) -> Poll<Result<Vec<u8>>> {
91        self.done = true;
92        Poll::Ready(Ok(std::mem::take(&mut self.out)))
93    }
94}
95
96impl Future for AsyncRead<'_> {
97    type Output = Result<Vec<u8>>;
98
99    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
100        let me = self.get_mut();
101        if me.done {
102            // Already resolved; return rather than starting another transfer.
103            return Poll::Ready(Err(ErrorCode::ErrorInvJobId.into()));
104        }
105        if !me.prepared {
106            // A fixed-length read must not stop early on a termination character, or
107            // binary block data would be cut at the first byte that happens to be one.
108            if let Err(e) = me.ss.prepare_read(me.timeout, me.limit.is_none()) {
109                me.done = true;
110                return Poll::Ready(Err(e));
111            }
112            me.prepared = true;
113        }
114        loop {
115            if me.limit == Some(me.out.len()) {
116                return me.finish();
117            }
118            if me.id.is_none() {
119                let mut buf = vec![0u8; me.chunk_len()];
120                match unsafe { me.ss.instr.visa_read_async(&mut buf) } {
121                    Ok((job_id, completion)) => {
122                        // The buffer moves into the pack, which keeps it alive for as
123                        // long as visa might write to it.
124                        me.ss.callback().register(job_id, cx.waker(), buf);
125                        note_sync_completion(completion, job_id);
126                        me.id = Some(job_id);
127                    }
128                    Err(e) => {
129                        // Nothing was queued, so visa never saw the buffer.
130                        me.done = true;
131                        return Poll::Ready(Err(e));
132                    }
133                }
134            }
135            let job_id = me.id.expect("set immediately above");
136            match me.ss.poll_job(job_id, cx) {
137                Poll::Pending => return Poll::Pending,
138                Poll::Ready((Err(e), _)) => {
139                    me.id = None;
140                    me.done = true;
141                    return Poll::Ready(Err(e));
142                }
143                Poll::Ready((Ok(completed), buf)) => {
144                    me.id = None;
145                    match collect_chunk(&mut me.out, me.limit, completed, buf) {
146                        Ok(true) => return me.finish(),
147                        Ok(false) => {}
148                        Err(e) => {
149                            me.done = true;
150                            return Poll::Ready(Err(e));
151                        }
152                    }
153                }
154            }
155        }
156    }
157}
158
159impl Drop for AsyncRead<'_> {
160    fn drop(&mut self) {
161        if let Some(job_id) = self.id.take() {
162            self.ss.cancel_job(job_id);
163        }
164    }
165}
166
167/// Future returned by [`AsyncInstrument::async_write`].
168///
169/// Dropping it before completion requests cancellation.
170#[must_use = "futures do nothing unless you `.await` or poll them"]
171pub struct AsyncWrite<'a> {
172    ss: &'a AsyncInstrument,
173    buf: Option<Vec<u8>>,
174    id: Option<JobID>,
175    len: usize,
176    written: usize,
177    done: bool,
178}
179
180impl<'a> AsyncWrite<'a> {
181    pub(super) fn new(ss: &'a AsyncInstrument, buf: Vec<u8>) -> Self {
182        Self {
183            ss,
184            len: buf.len(),
185            written: 0,
186            buf: Some(buf),
187            id: None,
188            done: false,
189        }
190    }
191}
192
193impl Future for AsyncWrite<'_> {
194    type Output = Result<usize>;
195
196    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
197        let me = self.get_mut();
198        if me.done {
199            return Poll::Ready(Err(ErrorCode::ErrorInvJobId.into()));
200        }
201        loop {
202            if me.written == me.len {
203                me.done = true;
204                return Poll::Ready(Ok(me.written));
205            }
206            if me.id.is_none() {
207                let buf = me
208                    .buf
209                    .take()
210                    .expect("buffer held until the transfer starts");
211                // Keep the original allocation alive; each job reads the next slice.
212                match unsafe { me.ss.instr.visa_write_async(&buf[me.written..]) } {
213                    Ok((job_id, completion)) => {
214                        me.ss.callback().register(job_id, cx.waker(), buf);
215                        note_sync_completion(completion, job_id);
216                        me.id = Some(job_id);
217                    }
218                    Err(e) => {
219                        me.done = true;
220                        return Poll::Ready(Err(e));
221                    }
222                }
223            }
224            let job_id = me.id.expect("set immediately above");
225            let (result, buf) = std::task::ready!(me.ss.poll_job(job_id, cx));
226            me.id = None;
227            match result.and_then(|c| advance_write(&mut me.written, me.len, c.count)) {
228                Ok(true) => {
229                    me.done = true;
230                    return Poll::Ready(Ok(me.written));
231                }
232                Ok(false) => me.buf = Some(buf),
233                Err(e) => {
234                    me.done = true;
235                    return Poll::Ready(Err(e));
236                }
237            }
238        }
239    }
240}
241
242// Continue only after a full chunk. Preserve the API's short-write byte count.
243fn advance_write(written: &mut usize, len: usize, count: usize) -> Result<bool> {
244    let requested = async_transfer_count(len - *written) as usize;
245    if count > requested {
246        return Err(ErrorCode::ErrorIo.into());
247    }
248    *written += count;
249    Ok(count < requested || *written == len)
250}
251
252impl Drop for AsyncWrite<'_> {
253    fn drop(&mut self) {
254        if let Some(job_id) = self.id.take() {
255            self.ss.cancel_job(job_id);
256        }
257    }
258}
259
260#[cfg(test)]
261mod tests {
262    use super::*;
263
264    #[test]
265    fn transfer_counts_fit_visa() {
266        for len in [0, 1, 4096, u32::MAX as usize] {
267            assert_eq!(async_transfer_count(len) as usize, len);
268        }
269    }
270
271    #[cfg(target_pointer_width = "64")]
272    #[test]
273    fn oversized_write_advances_without_wrapping() {
274        let max = u32::MAX as usize;
275        assert_eq!(async_transfer_count(max + 1) as usize, max);
276        assert_eq!(async_transfer_count(usize::MAX) as usize, max);
277        let len = 2 * max + 17;
278        let mut written = 0;
279        assert!(!advance_write(&mut written, len, max).unwrap());
280        assert_eq!(written, max);
281        assert!(!advance_write(&mut written, len, max).unwrap());
282        assert_eq!(written, 2 * max);
283        assert!(advance_write(&mut written, len, 17).unwrap());
284        assert_eq!(written, len);
285
286        // Exactly 4 GiB is MAX + one, not a zero-byte request.
287        written = 0;
288        assert!(!advance_write(&mut written, max + 1, max).unwrap());
289        assert!(advance_write(&mut written, max + 1, 1).unwrap());
290        assert_eq!(written, max + 1);
291
292        // A short final chunk reports the cumulative count, not just that chunk.
293        written = max;
294        assert!(advance_write(&mut written, max + 17, 3).unwrap());
295        assert_eq!(written, max + 3);
296    }
297
298    #[test]
299    fn write_progress_preserves_short_counts_and_rejects_overruns() {
300        let mut written = 0;
301        assert!(advance_write(&mut written, 8, 3).unwrap());
302        assert_eq!(written, 3);
303        assert!(advance_write(&mut written, 8, 0).unwrap());
304        assert_eq!(written, 3);
305        assert_eq!(
306            advance_write(&mut written, 8, 6),
307            Err(ErrorCode::ErrorIo.into())
308        );
309        assert_eq!(written, 3);
310    }
311
312    #[test]
313    fn exact_read_continues_after_short_end() {
314        let mut out = Vec::new();
315        let first = Completed {
316            count: 3,
317            completion: CompletionCode::Success,
318        };
319        assert!(!collect_chunk(&mut out, Some(8), first, b"abc\0\0\0\0\0".to_vec()).unwrap());
320        assert_eq!(out, b"abc");
321        let last = Completed {
322            count: 5,
323            completion: CompletionCode::SuccessMaxCnt,
324        };
325        assert!(collect_chunk(&mut out, Some(8), last, b"defgh".to_vec()).unwrap());
326        assert_eq!(out, b"abcdefgh");
327    }
328
329    #[test]
330    fn exact_read_finishes_at_count_regardless_of_completion_code() {
331        for completion in [
332            CompletionCode::Success,
333            CompletionCode::SuccessMaxCnt,
334            CompletionCode::SuccessTermChar,
335        ] {
336            let mut out = Vec::new();
337            assert!(collect_chunk(
338                &mut out,
339                Some(3),
340                Completed {
341                    count: 3,
342                    completion
343                },
344                b"abc".to_vec()
345            )
346            .unwrap());
347            assert_eq!(out, b"abc");
348        }
349    }
350
351    #[test]
352    fn exact_read_errors_on_zero_progress() {
353        for mut out in [Vec::new(), b"abc".to_vec()] {
354            let completed = Completed {
355                count: 0,
356                completion: CompletionCode::Success,
357            };
358            assert_eq!(
359                collect_chunk(&mut out, Some(8), completed, vec![0; 8]),
360                Err(ErrorCode::ErrorIo.into())
361            );
362        }
363    }
364
365    #[test]
366    fn response_read_still_finishes_at_end() {
367        let mut out = Vec::new();
368        let completed = Completed {
369            count: 3,
370            completion: CompletionCode::Success,
371        };
372        assert!(collect_chunk(&mut out, None, completed, b"abc\0\0".to_vec()).unwrap());
373        assert_eq!(out, b"abc");
374    }
375
376    #[test]
377    fn response_read_continues_after_full_chunk() {
378        let mut out = Vec::new();
379        let completed = Completed {
380            count: 3,
381            completion: CompletionCode::SuccessMaxCnt,
382        };
383        assert!(!collect_chunk(&mut out, None, completed, b"abc".to_vec()).unwrap());
384        assert_eq!(out, b"abc");
385    }
386}