mod callback;
mod future;
pub use future::{AsyncRead, AsyncWrite};
use callback::{Completed, JobMap};
use crate::{
enums::{attribute, event, status::CompletionCode},
session::AsRawSs,
wrap_raw_error_in_unsafe, Instrument, JobID, Result,
};
#[cfg(feature = "tokio")]
use std::task::Waker;
use std::{
task::{Context, Poll},
time::Duration,
};
use visa_sys as vs;
pub struct AsyncInstrument {
pub(super) instr: Instrument,
callback: Option<Box<JobMap>>,
}
impl From<AsyncInstrument> for Instrument {
fn from(mut async_instr: AsyncInstrument) -> Self {
async_instr.uninstall_handler();
let async_instr = std::mem::ManuallyDrop::new(async_instr);
let instr = unsafe { std::ptr::read(&async_instr.instr) };
let callback = unsafe { std::ptr::read(&async_instr.callback) };
drop(callback);
instr
}
}
impl AsyncInstrument {
pub fn new(instr: Instrument) -> Result<Self> {
use crate::enums::attribute::HasAttribute;
instr.set_attr(attribute::AttrTermcharEn::VI_TRUE)?;
instr.set_attr(attribute::AttrSuppressEndEn::VI_FALSE)?;
let callback = Box::new(JobMap::new());
wrap_raw_error_in_unsafe!(vs::viInstallHandler(
instr.as_raw_ss(),
event::EventKind::EventIoCompletion as _,
Some(JobMap::call_in_c),
&*callback as *const JobMap as *mut std::ffi::c_void,
))?;
let async_instr = Self {
instr,
callback: Some(callback),
};
async_instr.instr.enable_event(
event::EventKind::EventIoCompletion,
event::Mechanism::Handler,
)?;
Ok(async_instr)
}
fn callback(&self) -> &JobMap {
self.callback
.as_deref()
.expect("callback present until teardown")
}
pub fn instrument(&self) -> &Instrument {
&self.instr
}
pub fn async_read(&self, timeout: Duration) -> AsyncRead<'_> {
AsyncRead::new(self, None, timeout)
}
pub fn async_read_exact(&self, len: usize, timeout: Duration) -> AsyncRead<'_> {
AsyncRead::new(self, Some(len), timeout)
}
pub fn async_write(&self, buf: impl Into<Vec<u8>>) -> AsyncWrite<'_> {
AsyncWrite::new(self, buf.into())
}
pub(super) fn prepare_read(&self, timeout: Duration, termchar: bool) -> Result<()> {
use crate::enums::attribute::HasAttribute;
let ms = timeout.as_millis().min(vs::VI_TMO_INFINITE as u128) as _;
let tmo = attribute::AttrTmoValue::new_checked(ms).ok_or(crate::Error(
crate::enums::status::ErrorCode::ErrorNsupAttrState,
))?;
self.instr.set_attr(tmo)?;
self.instr.set_attr(if termchar {
attribute::AttrTermcharEn::VI_TRUE
} else {
attribute::AttrTermcharEn::VI_FALSE
})?;
Ok(())
}
#[cfg(feature = "tokio")]
pub(crate) fn start_read_id(&self, mut buf: Vec<u8>, waker: &Waker) -> Result<JobID> {
let (job_id, completion) = unsafe { self.instr.visa_read_async(buf.as_mut())? };
self.callback().register(job_id, waker, buf);
note_sync_completion(completion, job_id);
Ok(job_id)
}
#[cfg(feature = "tokio")]
pub(crate) fn start_write_id(&self, buf: Vec<u8>, waker: &Waker) -> Result<JobID> {
let (job_id, completion) = unsafe { self.instr.visa_write_async(&buf)? };
self.callback().register(job_id, waker, buf);
note_sync_completion(completion, job_id);
Ok(job_id)
}
pub(crate) fn poll_job(
&self,
job_id: JobID,
cx: &Context<'_>,
) -> Poll<(Result<Completed>, Vec<u8>)> {
self.callback()
.poll_job(job_id, cx)
.map(|(result, buf)| (result, buf.unwrap_or_default()))
}
pub(crate) fn cancel_job(&self, job_id: JobID) {
self.callback().cancel(job_id);
if let Err(e) = wrap_raw_error_in_unsafe!(vs::viTerminate(
self.instr.as_raw_ss(),
vs::VI_NULL as _,
job_id.0
)) {
log::warn!("terminating unfinished async io: {}", e)
};
}
fn uninstall_handler(&mut self) {
let Some(callback) = self.callback.as_deref() else {
log::error!("cannot uninstall IO completion handler: callback state is missing");
return;
};
if let Err(e) = self.instr.disable_event(
event::EventKind::EventIoCompletion,
event::Mechanism::Handler,
) {
log::warn!("error disabling io completion event: {}", e)
};
let user_data = callback as *const JobMap as *mut std::ffi::c_void;
if let Err(e) = wrap_raw_error_in_unsafe!(vs::viUninstallHandler(
self.instr.as_raw_ss(),
event::EventKind::EventIoCompletion as _,
Some(JobMap::call_in_c),
user_data,
)) {
if let Some(callback) = self.callback.take() {
let _ = Box::leak(callback);
}
log::warn!(
"failed to uninstall IO completion handler: {}; leaked callback state to keep VISA's user-data pointer valid",
e
);
};
}
}
impl Drop for AsyncInstrument {
fn drop(&mut self) {
self.uninstall_handler();
}
}
fn note_sync_completion(completion: CompletionCode, job_id: JobID) {
if completion == CompletionCode::SuccessSync {
log::trace!("async io for job {} completed synchronously", job_id.0);
}
}