1mod callback;
14mod future;
15
16pub use future::{AsyncRead, AsyncWrite};
17
18use callback::{Completed, JobMap};
19
20use crate::{
21 enums::{attribute, event, status::CompletionCode},
22 session::AsRawSs,
23 wrap_raw_error_in_unsafe, Instrument, JobID, Result,
24};
25#[cfg(feature = "tokio")]
26use std::task::Waker;
27use std::{
28 task::{Context, Poll},
29 time::Duration,
30};
31use visa_sys as vs;
32
33pub struct AsyncInstrument {
35 pub(super) instr: Instrument,
39 callback: Option<Box<JobMap>>,
40}
41
42impl From<AsyncInstrument> for Instrument {
43 fn from(mut async_instr: AsyncInstrument) -> Self {
44 async_instr.uninstall_handler();
47 let async_instr = std::mem::ManuallyDrop::new(async_instr);
48 let instr = unsafe { std::ptr::read(&async_instr.instr) };
52 let callback = unsafe { std::ptr::read(&async_instr.callback) };
53 drop(callback);
54 instr
55 }
56}
57
58impl AsyncInstrument {
59 pub fn new(instr: Instrument) -> Result<Self> {
64 use crate::enums::attribute::HasAttribute;
65 instr.set_attr(attribute::AttrTermcharEn::VI_TRUE)?;
66 instr.set_attr(attribute::AttrSuppressEndEn::VI_FALSE)?;
67 let callback = Box::new(JobMap::new());
68 wrap_raw_error_in_unsafe!(vs::viInstallHandler(
69 instr.as_raw_ss(),
70 event::EventKind::EventIoCompletion as _,
71 Some(JobMap::call_in_c),
72 &*callback as *const JobMap as *mut std::ffi::c_void,
73 ))?;
74 let async_instr = Self {
77 instr,
78 callback: Some(callback),
79 };
80 async_instr.instr.enable_event(
81 event::EventKind::EventIoCompletion,
82 event::Mechanism::Handler,
83 )?;
84 Ok(async_instr)
85 }
86
87 fn callback(&self) -> &JobMap {
88 self.callback
89 .as_deref()
90 .expect("callback present until teardown")
91 }
92
93 pub fn instrument(&self) -> &Instrument {
94 &self.instr
95 }
96
97 pub fn async_read(&self, timeout: Duration) -> AsyncRead<'_> {
108 AsyncRead::new(self, None, timeout)
109 }
110
111 pub fn async_read_exact(&self, len: usize, timeout: Duration) -> AsyncRead<'_> {
119 AsyncRead::new(self, Some(len), timeout)
120 }
121
122 pub fn async_write(&self, buf: impl Into<Vec<u8>>) -> AsyncWrite<'_> {
127 AsyncWrite::new(self, buf.into())
128 }
129
130 pub(super) fn prepare_read(&self, timeout: Duration, termchar: bool) -> Result<()> {
132 use crate::enums::attribute::HasAttribute;
133 let ms = timeout.as_millis().min(vs::VI_TMO_INFINITE as u128) as _;
134 let tmo = attribute::AttrTmoValue::new_checked(ms).ok_or(crate::Error(
135 crate::enums::status::ErrorCode::ErrorNsupAttrState,
136 ))?;
137 self.instr.set_attr(tmo)?;
138 self.instr.set_attr(if termchar {
139 attribute::AttrTermcharEn::VI_TRUE
140 } else {
141 attribute::AttrTermcharEn::VI_FALSE
142 })?;
143 Ok(())
144 }
145
146 #[cfg(feature = "tokio")]
148 pub(crate) fn start_read_id(&self, mut buf: Vec<u8>, waker: &Waker) -> Result<JobID> {
149 let (job_id, completion) = unsafe { self.instr.visa_read_async(buf.as_mut())? };
150 self.callback().register(job_id, waker, buf);
151 note_sync_completion(completion, job_id);
152 Ok(job_id)
153 }
154
155 #[cfg(feature = "tokio")]
157 pub(crate) fn start_write_id(&self, buf: Vec<u8>, waker: &Waker) -> Result<JobID> {
158 let (job_id, completion) = unsafe { self.instr.visa_write_async(&buf)? };
159 self.callback().register(job_id, waker, buf);
160 note_sync_completion(completion, job_id);
161 Ok(job_id)
162 }
163
164 pub(crate) fn poll_job(
165 &self,
166 job_id: JobID,
167 cx: &Context<'_>,
168 ) -> Poll<(Result<Completed>, Vec<u8>)> {
169 self.callback()
170 .poll_job(job_id, cx)
171 .map(|(result, buf)| (result, buf.unwrap_or_default()))
172 }
173
174 pub(crate) fn cancel_job(&self, job_id: JobID) {
176 self.callback().cancel(job_id);
177 if let Err(e) = wrap_raw_error_in_unsafe!(vs::viTerminate(
178 self.instr.as_raw_ss(),
179 vs::VI_NULL as _,
180 job_id.0
181 )) {
182 log::warn!("terminating unfinished async io: {}", e)
183 };
184 }
185 fn uninstall_handler(&mut self) {
187 let Some(callback) = self.callback.as_deref() else {
188 log::error!("cannot uninstall IO completion handler: callback state is missing");
189 return;
190 };
191 if let Err(e) = self.instr.disable_event(
192 event::EventKind::EventIoCompletion,
193 event::Mechanism::Handler,
194 ) {
195 log::warn!("error disabling io completion event: {}", e)
196 };
197 let user_data = callback as *const JobMap as *mut std::ffi::c_void;
201 if let Err(e) = wrap_raw_error_in_unsafe!(vs::viUninstallHandler(
202 self.instr.as_raw_ss(),
203 event::EventKind::EventIoCompletion as _,
204 Some(JobMap::call_in_c),
205 user_data,
206 )) {
207 if let Some(callback) = self.callback.take() {
210 let _ = Box::leak(callback);
211 }
212 log::warn!(
213 "failed to uninstall IO completion handler: {}; leaked callback state to keep VISA's user-data pointer valid",
214 e
215 );
216 };
217 }
218}
219
220impl Drop for AsyncInstrument {
221 fn drop(&mut self) {
222 self.uninstall_handler();
223 }
224}
225
226fn note_sync_completion(completion: CompletionCode, job_id: JobID) {
229 if completion == CompletionCode::SuccessSync {
230 log::trace!("async io for job {} completed synchronously", job_id.0);
231 }
232}