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
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_actor →
PortHandle → 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
partial: PartialOctetReadThe 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_actor → PortHandle → 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
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.
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.
PortNotFound(String)
PortAlreadyRegistered(String)
ParamNotFound(String)
ParamAlreadyExists(String)
C parity: asynParamAlreadyExists —
paramList::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: asynParamUndefined —
paramVal::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
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
impl AsynError
Sourcepub fn status(&self) -> AsynStatus
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).
Sourcepub fn message(&self) -> String
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.
Sourcepub fn never_ran(&self) -> bool
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 setOption → getOptions
fall-through, its monitorStatus tail) may run.
Sourcepub fn is_queue_refused(&self) -> bool
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.
Sourcepub fn is_queue_timeout(&self) -> bool
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 setOption → getOptions fall-through) may run.
Sourcepub fn is_transport_io(&self) -> bool
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.
Sourcepub fn is_fatal_transport(&self) -> bool
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.
Sourcepub fn partial_read(&self) -> Option<&PartialOctetRead>
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.
Sourcepub fn with_partial_read(self, partial: PartialOctetRead) -> Self
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.
Sourcepub fn partial_write(&self) -> Option<usize>
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.
Sourcepub fn with_partial_write(self, nbytes: usize) -> Self
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 Error for AsynError
impl Error for AsynError
Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()