Skip to main content

LinkSet

Trait LinkSet 

Source
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§

Source

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.

Source

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§

Source

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.

Source

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.

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.

Source

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.

Source

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).

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

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.

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.

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".

Implementors§