Skip to main content

AsynError

Enum AsynError 

Source
pub enum AsynError {
Show 19 variants Status { status: AsynStatus, message: String, }, PartialRead { source: Box<AsynError>, partial: PartialOctetRead, }, PartialWrite { source: Box<AsynError>, nbytes: usize, }, QueueTimeout { port: String, }, QueueRefused { status: AsynStatus, message: String, }, PortNotFound(String), PortAlreadyRegistered(String), ParamNotFound(String), ParamAlreadyExists(String), ParamIndexOutOfRange(usize), ParamUndefined(usize), TypeMismatch { expected: &'static str, actual: &'static str, }, InterfaceNotSupported(String), AddressOutOfRange(i32), AlreadySubscribed, OptionNotFound(String), InvalidLinkSyntax(String), DowncastFailed, Io(Error),
}
Expand description

Error type for asyn-rs operations.

Variants§

§

Status

Fields

§status: AsynStatus
§message: String
§

PartialRead

An octet read that failed after transferring bytes into the caller’s buffer.

C parity: asynOctet::read reports *nbytesTransfered and *eomReason together with a failing asynStatus — the EOS interpose breaks out of its accumulation loop on a lower-layer error and still runs the common tail (asynInterposeEos.c:242-253: null-terminate, *eomReason = eom, *nbytesTransfered = nRead, return status). A device that emits a partial line and then goes quiet therefore reaches the record as asynTimeout plus the bytes it did send; asynRecord commits both (asynRecord.c:1591,1627: eomr and nord are assigned regardless of status).

AsynError::Status alone cannot express that: ? on a partially-filled read discards the count and the eom reason, and the bytes already written into the caller’s buffer become unrecoverable because the interpose’s in_buf_tail has advanced past them — and because every dispatch hop above the driver (port_actorPortHandle → device support) owns its own buffer, which ? drops. Carrying the bytes in the error is what makes the transfer and the status one value: a consumer cannot take the failure without also being handed everything the device did send. Build this with AsynError::with_partial_read rather than by hand, and read it back with AsynError::partial_read.

The carrier wraps the failure it decorates instead of copying its status and message out of it: flattening would erase which kind of failure it was, and callers legitimately ask that question — the drivers’ fatal-transport test (AsynError::is_fatal_transport) tears the link down for a real errno (AsynError::Io) but leaves it up for a timeout, so a flattened Io (a recv that returned ECONNRESET after the EOS interpose had already buffered half a line) would silently stop disconnecting. Every question about the underlying failure — AsynError::status, AsynError::message, AsynError::is_transport_io — is answered through the carrier.

Fields

§source: Box<AsynError>

The failure that ended the transfer, intact.

§partial: PartialOctetRead

The bytes transferred before the failure and the end-of-message reason accumulated up to that point — the *nbytesTransfered / *eomReason pair C writes out alongside the error.

§

PartialWrite

An octet write that failed after the device accepted bytes.

C parity: asynOctet::write reports *nbytesTransfered together with a failing asynStatus, at every layer of the write chain — drvAsynSerialPort.c::writeIt (:849) assigns *nbytesTransfered = numchars - nleft on the way out of the loop no matter whether it broke on asynTimeout or on a fatal write() errno; asynInterposeEcho.c::writeIt (:88) and asynInterposeDelay.c (:52) assign *nbytesTransfered = transfered on every break; and asynInterposeEos.c::writeIt (:196) clamps the lower layer’s count to the caller’s numchars and returns it beside the status. asynRecord commits the result unconditionally — nawt = nbytesTransfered (asynRecord.c:1547) runs before the status check at :1551 — so a half-written command lands NAWT=3 next to its Write error, nout=3 diagnostic.

AsynError::Status alone cannot express that: ? on a partially accepted write discards the count, and every hop above the driver (port_actorPortHandle → record/device support) only sees the status. This is the write-side twin of AsynError::PartialRead, for the same reason: carrying the count in the error makes the transfer and the status one value, so a consumer cannot take the failure without being handed how far the device got. Build it with AsynError::with_partial_write and read it back with AsynError::partial_write. Like AsynError::PartialRead it wraps the underlying failure rather than flattening it, so the transport classifiers still see the errno.

Fields

§source: Box<AsynError>

The failure that ended the write, intact.

§nbytes: usize

The bytes the device accepted before the failure — C’s *nbytesTransfered on the error path.

§

QueueTimeout

The request waited in the port queue past the deadline its queueRequest was given, so it was removed and never ran.

C queueTimeoutCallback (asynManager.c:647-700): the timer queueRequest(pasynUser, priority, timeout) armed at enqueue (asynManager.c:1617-1623) fires while isQueued is still true, the request is unlinked from the port’s queue list, and the caller’s timeoutUser runs instead of its processUser. Only a caller that asked for a queue deadline can see this — device support passes queueRequest(..., 0.0) (devAsynInt32.c:838) and arms no timer at all, while asynRecord passes QUEUE_TIMEOUT = 10 s for both its process and its special requests (asynRecord.c:71,343,572).

Distinct from AsynStatus::Timeout, which means the driver did run and the device did not answer in time. C reports this one as plain asynError (asynRecord.c:919-926), which is what AsynError::status’s default branch gives it.

Fields

§port: String
§

QueueRefused

The port’s queue gate refused the request, so it was never queued and never ran — C’s queueRequest returning asynDisabled / asynDisconnected (asynManager.c:1541-1552).

Distinct from an AsynError::Status carrying the same AsynStatus, and that distinction is the whole point: in C the two arrive by different routes and mean opposite things. A refusal is queueRequest’s return value — the callback never runs, so nothing it implies happened: no bytes moved, no option was written, no readback, no monitorStatus (asynRecord.c:571-576 reports pasynUser->errorMessage and frees the user). A driver error arrives inside the callback, which did run and whose tail C still executes. Collapsing them into one variant made a refused special() report as a callback that ran (R14-46).

Built only by the gate owner (crate::port::PortDriverBase::check_queue) and asked about through AsynError::never_ran.

Fields

§status: AsynStatus
§message: String
§

PortNotFound(String)

§

PortAlreadyRegistered(String)

§

ParamNotFound(String)

§

ParamAlreadyExists(String)

C parity: asynParamAlreadyExistsparamList::createParam (asynPortDriver.cpp:126-138) returns this status when a second createParam(name, ...) arrives with the same name. The asynPortDriver::createParam wrapper (asynPortDriver.cpp:991-1011) translates it to asynError with an asynPrint(ASYN_TRACE_ERROR, ...) log line. The lax Rust ParamList::create_param silently returns the existing index to match the idempotent build pattern used by ad-core-rs/ad-plugins-rs (e.g. ADDriverParams::create after NDArrayDriverParams::create); use ParamList::create_param_strict when you need C parity for the duplicate-name error.

§

ParamIndexOutOfRange(usize)

§

ParamUndefined(usize)

C parity: asynParamUndefinedparamVal::getInteger/getInteger64/getDouble/getUInt32/getString throws ParamValNotDefined when the value has never been set, and paramList::getInteger/... translates that to asynParamUndefined (asynPortDriver/asynPortDriver.cpp:301-401,543-566). The lax Rust getters (ParamList::get_int32 etc.) return the type default (0, 0.0, "") silently — that mirrors many existing call sites that use .unwrap_or(...). Use the _strict variants (ParamList::get_int32_strict etc.) to surface this status the way C reportGetParamErrors does.

§

TypeMismatch

Fields

§expected: &'static str
§actual: &'static str
§

InterfaceNotSupported(String)

§

AddressOutOfRange(i32)

§

AlreadySubscribed

§

OptionNotFound(String)

An option key the port does not implement — C’s trailing else if (epicsStrCaseCmp(key, "") != 0) arm, in setOption and getOption alike (drvAsynSerialPort.c:594-597, :1171; drvAsynSerialPortWin32.c:341-344; drvAsynIPPort.c:902-905). The text is C’s, verbatim: it is what reaches the operator through pasynUser->errorMessage and lands in the record’s ERRS.

§

InvalidLinkSyntax(String)

§

DowncastFailed

§

Io(Error)

Implementations§

Source§

impl AsynError

Source

pub fn status(&self) -> AsynStatus

The asynStatus this error carries — the single owner of the error → status mapping.

Every consumer that classifies a failure by status (record alarm mapping, fatal-transport detection, protocol reply status) MUST go through this instead of matching AsynError::Status directly: a bare match silently misclassifies every other status-carrying variant (that is exactly how AsynError::PartialRead would have downgraded a timeout to a generic error). Variants that carry no status take C’s generic asynError, matching the asynStatusToEpicsAlarm default branch (asynEpicsUtils.c:234-266).

Source

pub fn message(&self) -> String

The driver/interpose diagnostic behind this error — C’s pasynUser->errorMessage, which every reportError call site splices into ERRS. Reads through the partial carriers, so a failed transfer reports the same text whether or not it moved bytes first.

Source

pub fn never_ran(&self) -> bool

True iff the request never ran at all — the port’s queue gate refused it (AsynError::QueueRefused) or it sat past its deadline and was removed (AsynError::QueueTimeout).

The single owner of that question, and the one every caller of a queued request must ask before doing anything a completed request implies. C draws the line structurally: both outcomes are decided before processUser is ever dispatched — queueRequest returns the refusal (asynManager.c:1541-1552) and queueTimeoutCallback runs timeoutUser instead of processUser (:647-700) — so no bytes moved, no option or EOS was written, no connect was attempted, and none of the follow-up work a completed request implies (asynRecord’s setOptiongetOptions fall-through, its monitorStatus tail) may run.

Source

pub fn is_queue_refused(&self) -> bool

True iff the port’s queue gate refused the request — see AsynError::QueueRefused. Ask AsynError::never_ran unless you specifically need to tell a refusal from a queue timeout.

Source

pub fn is_queue_timeout(&self) -> bool

True iff the request never ran because it sat in the port queue past its deadline — C’s queueTimeoutCallback outcome, see AsynError::QueueTimeout.

The single owner of that test. Callers that arm a queue deadline MUST ask through this rather than matching the variant, and MUST NOT treat the failure as an I/O result: C runs timeoutUser instead of processUser, so nothing the request would have done happened — no bytes moved, no option was written, and none of the follow-up work a completed request implies (asynRecord’s setOptiongetOptions fall-through) may run.

Source

pub fn is_transport_io(&self) -> bool

True when the failure came from the OS transport itself — a real errno on the fd/socket, not a timeout and not a higher-layer complaint.

C’s drivers decide this at the errno itself, calling closeConnection right where read/write failed (drvAsynIPPort.c:642-651, drvAsynSerialPort.c:836-845) while returning asynTimeout with the link intact on a poll expiry. Rust re-derives the decision one layer up, in AsynError::is_fatal_transport, so the errno has to survive the trip: ask through the partial carriers rather than matching AsynError::Io by variant, or a half-transferred ECONNRESET reads as non-fatal and leaves a dead socket reporting connected forever.

Source

pub fn is_fatal_transport(&self) -> bool

This failure means the link is dead and the driver must tear the connection down — C’s closeConnection contract: a real errno on the fd/socket, or an explicit disconnect, but not a timeout (C returns asynTimeout with the link intact and lets the next transfer retry).

The single owner of that test for every octet driver (drvAsynIPPort, drvAsynSerialPort, its Win32 twin), which each used to keep a private copy — and each copy independently proxied “real errno” through matches!(e, AsynError::Io(_)), a variant match that the partial-transfer carriers defeat.

Source

pub fn partial_read(&self) -> Option<&PartialOctetRead>

The partial octet transfer delivered before this error, if any — C’s *nbytesTransfered / *eomReason / caller-buffer contents on the failure path.

Every consumer of an octet read MUST consult this on the error path: C asynRecord::performOctetIO (asynRecord.c:1591-1629) and devAsynOctet::readIt (devAsynOctet.c:693-717) both publish the transfer regardless of the returned status, so an error branch that looks only at the status silently drops device data C delivers.

Source

pub fn with_partial_read(self, partial: PartialOctetRead) -> Self

Attach a partial octet transfer to a failing read. This is the only way to build AsynError::PartialRead, so the underlying failure can never be lost in the conversion — it is wrapped, not copied.

Re-attaching overwrites the count: in a stacked interpose chain the outermost layer is the one that filled the caller’s buffer, so its count is the authoritative *nbytesTransfered. The original source is kept — re-wrapping must not bury it one layer deeper each hop.

Source

pub fn partial_write(&self) -> Option<usize>

The bytes the device accepted before this write failed — C’s *nbytesTransfered on a failing asynOctet::write. None means the layer reported no transfer, which is C’s pre-call nbytesTransfered = 0 (asynRecord.c:1526) left untouched: zero bytes.

Every consumer of an octet write MUST consult this on the error path: C asynRecord::performOctetIO publishes nawt = nbytesTransfered before it looks at the status (asynRecord.c:1547-1551), so an error branch that reports only “0 written” contradicts what the device actually received.

Source

pub fn with_partial_write(self, nbytes: usize) -> Self

Attach the accepted-byte count to a failing write. This is the only way to build AsynError::PartialWrite, so the underlying failure can never be lost in the conversion — it is wrapped, not copied.

Re-attaching overwrites the count: in a stacked interpose chain the outermost layer is the one that owns the caller’s numchars (the EOS interpose must not report its appended terminator bytes), so its count is the authoritative *nbytesTransfered. The original source is kept.

Trait Implementations§

Source§

impl Debug for AsynError

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for AsynError

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Error for AsynError

Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0:

use the Display impl or to_string()

1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0:

replaced by Error::source, which can support downcasting

Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl From<Error> for AsynError

Source§

fn from(source: Error) -> Self

Converts to this type from the input type.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more