pub trait LinkSet: Send + Sync {
Show 17 methods
// Required methods
fn is_connected(&self, name: &str) -> bool;
fn get_value<'life0, 'life1, 'async_trait>(
&'life0 self,
name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Option<EpicsValue>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
// Provided methods
fn init_ready(&self, name: &str) -> bool { ... }
fn get_cached_value(&self, name: &str) -> Option<EpicsValue> { ... }
fn connect_link<'life0, 'life1, 'async_trait>(
&'life0 self,
name: &'life1 str,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn put_admission(&self, name: &str) -> PutAdmission { ... }
fn put_value<'life0, 'life1, 'async_trait>(
&'life0 self,
name: &'life1 str,
value: EpicsValue,
op: LinkPutOp,
) -> Pin<Box<dyn Future<Output = Result<(), String>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn scan_forward(&self, name: &str) -> Result<(), String> { ... }
fn flush_puts<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait { ... }
fn alarm_message(&self, _name: &str) -> Option<String> { ... }
fn alarm_severity(&self, _name: &str) -> Option<i32> { ... }
fn alarm_status(&self, _name: &str) -> Option<i32> { ... }
fn remote_alarm(&self, _name: &str) -> Option<RemoteAlarm> { ... }
fn time_stamp(&self, _name: &str) -> Option<(i64, i32, u64)> { ... }
fn link_metadata(&self, _name: &str) -> Option<LinkMetadata> { ... }
fn link_names(&self) -> Vec<String> { ... }
fn link_diagnostics<'life0, 'life1, 'async_trait>(
&'life0 self,
_name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Option<LinkDiagnostics>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
}Expand description
Pluggable backend for one URL scheme’s link operations.
All methods take &self so the implementation must use interior
mutability for any cached state. None / false is the
“unavailable” sentinel — the database falls back to a generic
LINK/INVALID alarm when an lset returns None.
Required Methods§
Sourcefn is_connected(&self, name: &str) -> bool
fn is_connected(&self, name: &str) -> bool
True iff a fresh value is available for name without
blocking. Used by the record processing loop to decide
whether to mark the record’s STAT as LINK_ALARM.
Synchronous: asked on the record-processing thread inside the record’s advisory write gate. MUST NOT perform I/O.
Sourcefn get_value<'life0, 'life1, 'async_trait>(
&'life0 self,
name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Option<EpicsValue>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn get_value<'life0, 'life1, 'async_trait>(
&'life0 self,
name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Option<EpicsValue>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Read the current value of name. Returns None when the
upstream isn’t yet connected or the lset has no cache for
this name.
MAY perform I/O (open the channel, issue a one-shot GET). It is
therefore called only from the database’s link work owner task —
the record-processing path uses Self::get_cached_value.
Provided Methods§
Sourcefn init_ready(&self, name: &str) -> bool
fn init_ready(&self, name: &str) -> bool
True iff name has completed every post-connect init action
iocInit’s external-link wait holds for — C testInitReady
(dbCa.c:835-845 at ef4829829, epics-base #856 “dbCa: iocInit
wait for all conditions” — post-R7.0.10 and in no tag, so this
is the one citation here that is not pin-relative): connected with
the first monitor event cached AND
the attribute (metadata) fetch complete. Distinct from
Self::is_connected, which is C’s lset isConnected and keeps
its readable-cache semantics.
Synchronous and non-blocking like is_connected; polled only by
the iocInit wait. Default: is_connected — right for an lset
with no post-connect init actions.
Sourcefn get_cached_value(&self, name: &str) -> Option<EpicsValue>
fn get_cached_value(&self, name: &str) -> Option<EpicsValue>
Read name from cached, monitor-fed state ONLY — the
record-processing read. C dbCaGetLink (dbCa.c:419-506) copies
out of pca->pgetNative, the buffer the CA monitor callback
(eventCallback, dbCa.c:891-967, the fill at :941-944)
keeps fresh on the dbCaTask; it never opens a channel and never
waits on the wire. Returns None
when the link has no cached value yet, which is C returning -1 for
!pca->isConnected (dbCa.c:430-435) — the reading record takes
LINK/INVALID for that cycle.
MUST NOT perform I/O — which is why this is a fn and
Self::get_value is an async fn.
Default: None, i.e. “this lset keeps no cache”. That is C’s
!pca->isConnected arm verbatim: the reading record takes
LINK/INVALID for the cycle and the database stages the link’s
open on the link work owner (Self::connect_link), which is
what warms the cache for the next cycle. An lset that CAN answer
from memory MUST override, or its links never read.
Sourcefn connect_link<'life0, 'life1, 'async_trait>(
&'life0 self,
name: &'life1 str,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn connect_link<'life0, 'life1, 'async_trait>(
&'life0 self,
name: &'life1 str,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Open (subscribe / connect) name so later
Self::get_cached_value reads have a cache to serve — C
dbCaAddLink (dbCa.c:397-401), which stages a CA_CONNECT
action whose ca_create_channel +
ca_add_array_event run on the dbCaTask, not on the caller.
Called from the database’s link work owner task, so it MAY block on the network. Idempotent: the owner may call it again for a link that is already open or still connecting.
More precisely, it is called on the tokio runtime the database
captured at construction, so tokio::net is usable here — and that
is the only place it is usable. A database built with no runtime
entered anywhere captured none, and there is no second executor that
could stand in: the process-global background executor deliberately
carries no tokio::net reactor. Such a database therefore never
calls this method at all; it refuses the link instead
(PvDatabase::external_put_gate). Do not read the absence of a
runtime as a reason to open the channel synchronously on the
caller — there is no caller thread that may block that way.
Default: drive the lset’s own lazy open by reading through
Self::get_value and discarding the result — correct for every
existing lset, and it runs off the record-processing thread.
Sourcefn put_admission(&self, name: &str) -> PutAdmission
fn put_admission(&self, name: &str) -> PutAdmission
Non-blocking admission gate for an OUT-link write, asked on the
record-processing thread before the write is staged onto the
database’s link-put queue — C dbCaPutLinkCallback’s
if (!pca->isConnected || !pca->hasWriteAccess) return -1;
(db/dbCa.c:529-532 (dbCaPutLinkCallback); epics-base R7.0.10).
MUST NOT perform I/O. It is the one lset call left inside the record’s advisory write gate, and the whole point of the queue is that nothing there touches the network.
Default: derive from Self::is_connected, which the trait already
documents as answerable “without blocking” — i.e. only C’s FIRST
operand. An lset whose protocol also carries a write right MUST
override and test both, because the default cannot see the second
one; so MUST an lset whose OUT links live in a different cache than
its INP links (pvalink keys its registry on direction), or every OUT
write to a perfectly healthy channel is refused.
Sourcefn put_value<'life0, 'life1, 'async_trait>(
&'life0 self,
name: &'life1 str,
value: EpicsValue,
op: LinkPutOp,
) -> Pin<Box<dyn Future<Output = Result<(), String>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn put_value<'life0, 'life1, 'async_trait>(
&'life0 self,
name: &'life1 str,
value: EpicsValue,
op: LinkPutOp,
) -> Pin<Box<dyn Future<Output = Result<(), String>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Write value to name with the delivery semantics named by
op (LinkPutOp::Plain for a fire-and-forget put,
LinkPutOp::Async for a put that is part of a put-notify /
blocking-put chain). Returns Err with a human-readable reason
on failure (denied, type-mismatch, no-such-pv, etc.). Default
impl rejects all writes — read-only lsets keep the default.
Called from the database’s link-put owner task, never from a
record-processing thread — this is the dbCaTask half of the
split (db/dbCa.c:1161-1183 (dbCaTask); epics-base R7.0.10), so it
may block on the network.
As with Self::connect_link, “may block on the network” means “on
the tokio runtime the database captured”, which is where the owner
dispatches it. A database that captured no runtime never reaches this
method: the write is refused at PvDatabase::external_put_gate with
nothing staged, C’s shape for a put it cannot deliver
(db/dbCa.c:529-532 (dbCaPutLinkCallback); R7.0.10).
Sourcefn scan_forward(&self, name: &str) -> Result<(), String>
fn scan_forward(&self, name: &str) -> Result<(), String>
Fire name’s forward link (FLNK): trigger the remote target to
process, transferring no value.
The lset counterpart of C dbScanFwdLink → lset->scanForward
(dbLink.c:475), realised by the pvalink lset as pvaScanForward
(pvxs/ioc/pvalink_lset.cpp:672-688). A forward link is never
deferred (“FWD_LINK is never deferred, and always results in a
Put”) and carries no staged value: it forces the remote record to
process when the source record fires its FLNK.
The lset applies the same non-retry validity gate pvxs does
(pvxs/ioc/pvalink_lset.cpp:677): on a non-retry link that is not currently
connected it performs NO trigger and returns Err, so the caller
raises LINK/INVALID on the owning record — pvxs calls
recGblSetSevrMsg(LINK_ALARM, INVALID_ALARM, "Disconn") there.
Default impl: Ok(()) no-op. A read-only or DB-local lset
forwards nothing through this hook — a DB FLNK target is processed
directly by the database’s scanOnce path (the DB lset’s
scanForward), not through an external link set.
Sourcefn flush_puts<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn flush_puts<'life0, 'async_trait>(
&'life0 self,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Flush any OUT-link writes the lset has queued but not yet sent — the production drain trigger for an async OUT channel owner.
Two queued states this drains: a write deferred for sibling
coalescing, and a write that failed mid-disconnect and is held
for replay once the upstream reconnects (retry). The database
calls this after every external OUT-link write so the
“retry on connect” path has a production caller from record
processing — not only test code. Default no-op: a synchronous
lset (DB links, a read-only lset) queues nothing.
Mirrors the role of pvxs’s shared pvaLinkChannel::put() being
driven from record processing rather than left to manual calls
(pvxs/ioc/pvalink_lset.cpp:653, pvxs/ioc/pvalink_channel.cpp:220-280).
Sourcefn alarm_message(&self, _name: &str) -> Option<String>
fn alarm_message(&self, _name: &str) -> Option<String>
Most recent alarm message string from the upstream PV, when available. None means no alarm or no cache.
Sourcefn alarm_severity(&self, _name: &str) -> Option<i32>
fn alarm_severity(&self, _name: &str) -> Option<i32>
Alarm severity (0 = NO_ALARM … 3 = INVALID) to fold into
the owning record’s LINK_ALARM, when the link should
propagate one.
None means “do not propagate” — either the upstream has no
alarm, the lset has no cache, or the link’s maximize-severity
mode (NMS/MS/MSI) suppresses it. The lset is expected to
apply that mode gate itself (the pva://X?sevr=MS modifier is
stripped before epics-base-rs sees the link, so only the lset
retains it). A returned Some(sev) is therefore already
gated and the record processing loop propagates it verbatim
as a maximize-severity contribution. Mirrors pvxs
pvxs/ioc/pvalink_lset.cpp pvaGetAlarm feeding recGblSetSevr.
Sourcefn alarm_status(&self, _name: &str) -> Option<i32>
fn alarm_status(&self, _name: &str) -> Option<i32>
Remote alarm status code (the EPICS alarm_status enum:
0 = NO_ALARM, 1 = READ, … 17 = COMM, …) from the upstream
PV, when available.
used to honour the MSS (maximize-severity-and-
status) link modifier — the owning record then adopts the remote
STAT instead of the generic LINK_ALARM. None means the lset
cannot report a remote status (no cache, or the link set does not
track it); the caller falls back to LINK_ALARM, which is the
behaviour for every non-MSS modifier and for lsets that leave
this default. Mirrors pvxs/ioc/pvalink_lset.cpp pvaGetAlarm
surfacing the remote alarm.status to recGblSetSevrMsg.
Sourcefn remote_alarm(&self, _name: &str) -> Option<RemoteAlarm>
fn remote_alarm(&self, _name: &str) -> Option<RemoteAlarm>
Ungated remote alarm snapshot — the remote (severity, status, message) after a successful value read, WITHOUT the
maximize-severity (MS/NMS/MSI) gate that
LinkSet::alarm_severity applies.
This is the split pvxs draws between two operations: pvaGetValue
applies the sevr gate only when raising the owning record’s
LINK_ALARM (pvxs/ioc/pvalink_lset.cpp:424-431 — surfaced here
through LinkSet::alarm_severity), whereas pvaGetAlarmMsg
returns the cached snap_severity / snap_message snapshot
directly and never consults sevr
(pvxs/ioc/pvalink_lset.cpp:542-569, with pvaGetAlarm :571-575
its no-message-buffer wrapper — surfaced here). A caller
inspecting the DB link’s alarm (dbGetAlarm / dbGetAlarmMsg)
therefore sees the remote severity even on a default NMS link
that leaves the owning record unraised.
None means the lset cannot report a snapshot: no cache, the
link is not connected (pvxs CHECK_VALID — pvxs/ioc/pvalink_lset.cpp:548),
or the link set does not track remote alarms. Default: none.
Sourcefn time_stamp(&self, _name: &str) -> Option<(i64, i32, u64)>
fn time_stamp(&self, _name: &str) -> Option<(i64, i32, u64)>
(seconds_past_epoch, nanoseconds, userTag) from the upstream
PV’s timestamp slot, when available. The userTag is the remote
timeStamp.userTag widened to the 64-bit epicsUTag tag without
sign extension, or 0 when the source carries none (CA links, or
a PVA source whose timeStamp omits the field).
Sourcefn link_metadata(&self, _name: &str) -> Option<LinkMetadata>
fn link_metadata(&self, _name: &str) -> Option<LinkMetadata>
Remote display / control / valueAlarm metadata for name, as
a single snapshot.
The Rust counterpart of pvxs’s pvalink lset metadata getter
set (pvaGetDBFtype, pvaGetElements, pvaGetControlLimits,
pvaGetGraphicLimits, pvaGetAlarmLimits, pvaGetPrecision,
pvaGetUnits — installed at pvxs/ioc/pvalink_lset.cpp:706-732).
A structured snapshot is used instead of seven separate trait
methods so the lset reads its cache once and record support
gets every linked-metadata field together.
None means the lset has no cached value for name (not yet
connected); a Some(LinkMetadata) with individual None
fields means the remote NT value simply did not carry that
piece of metadata — the record then keeps its local default,
matching the C getters that leave the caller’s buffer
untouched on a missing sub-field. Default impl: no metadata.
Sourcefn link_names(&self) -> Vec<String>
fn link_names(&self) -> Vec<String>
Enumerate every PV name this lset has opened (i.e., is
actively tracking). Used by dbpvxr to dump per-record
link state without forcing the caller to know the full
name list up-front.
Sourcefn link_diagnostics<'life0, 'life1, 'async_trait>(
&'life0 self,
_name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Option<LinkDiagnostics>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn link_diagnostics<'life0, 'life1, 'async_trait>(
&'life0 self,
_name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Option<LinkDiagnostics>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
This link’s dbcar report state, or None when the lset has never
opened name — C’s pca == NULL, which dbcar prints as a
not-connected link with zero counters (dbCaTest.c:127-132).
Async because C’s host field is ca_host_name(pca->chid), and this
port’s twin (CaChannel::host_name) resolves the peer’s PTR record
on a blocking thread exactly as libca’s hostNameCache does. That
makes this the one LinkSet method neither half of the C split owns:
it is a diagnostic, called from iocsh, never from record processing
and never from the link work owner.
Default: None, i.e. “this lset has no per-link report state”.
Such an lset’s links are invisible to dbcar, which is right — C’s
dbcar walks plink->type == CA_LINK and no other link flavour.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".