pub struct PvDatabase { /* private fields */ }Expand description
Database of all process variables hosted by this server.
Implementations§
Source§impl PvDatabase
impl PvDatabase
Sourcepub fn get_pv_blocking(&self, name: &str) -> CaResult<EpicsValue>
pub fn get_pv_blocking(&self, name: &str) -> CaResult<EpicsValue>
Get a PV value synchronously from a blocking thread.
Uses block_in_place + Handle::block_on to bridge the async
get_pv call. Safe to call from std::threads spawned within
a tokio runtime context.
Sourcepub async fn get_pv(&self, name: &str) -> CaResult<EpicsValue>
pub async fn get_pv(&self, name: &str) -> CaResult<EpicsValue>
Get the current value of a PV or record field. Uses resolve_field for records (3-level priority).
Sourcepub async fn put_pv(&self, name: &str, value: EpicsValue) -> CaResult<()>
pub async fn put_pv(&self, name: &str, value: EpicsValue) -> CaResult<()>
Set a PV value or record field, notifying subscribers. Tries record put_field first, then put_common_field as fallback.
Acquires the record’s advisory write gate.
Sourcepub async fn put_pv_already_locked(
&self,
name: &str,
value: EpicsValue,
) -> CaResult<()>
pub async fn put_pv_already_locked( &self, name: &str, value: EpicsValue, ) -> CaResult<()>
put_pv variant for a caller already holding the
record’s advisory write gate (QSRV atomic group PUT). See
Self::put_record_field_from_ca_already_locked.
Sourcepub async fn put_pv_and_post(
&self,
name: &str,
value: EpicsValue,
) -> CaResult<()>
pub async fn put_pv_and_post( &self, name: &str, value: EpicsValue, ) -> CaResult<()>
Write a value and post monitor events if changed.
Equivalent to C EPICS dbPut + db_post_events(DBE_VALUE|DBE_LOG).
Use for readback/status mirror PVs that are written by sequencer-style code and need to be visible to CA monitors without triggering record processing. Clears UDF/UDF_ALARM on primary field write.
origin: writer ID for self-write filtering. Subscribers with the
same ignore_origin will skip this event. Pass 0 to disable.
Sourcepub async fn post_alarm(
&self,
name: &str,
severity: u16,
status: u16,
) -> CaResult<()>
pub async fn post_alarm( &self, name: &str, severity: u16, status: u16, ) -> CaResult<()>
Push a monitor event holding the simple PV’s current value
but with explicit alarm severity/status. Used by the gateway
to surface upstream-disconnect to downstream monitor
subscribers without dropping the shadow PV (which would force
downstream clients into ECA_DISCONN reconnect storms on every
transient hiccup). Returns ChannelNotFound for record-backed
PVs — those carry their own common.sevr/stat in record
processing.
Sourcepub async fn put_pv_and_post_snapshot(
&self,
name: &str,
snapshot: Snapshot,
) -> CaResult<()>
pub async fn put_pv_and_post_snapshot( &self, name: &str, snapshot: Snapshot, ) -> CaResult<()>
Propagate a full upstream snapshot (value + alarm status/severity +
IOC timestamp) to a simple shadow PV and fan out to downstream
monitor subscribers. Used by the CA gateway forwarding task to avoid
discarding the upstream alarm and timestamp decoded from the incoming
DBR_TIME_* frame. Returns ChannelNotFound for record-backed PVs
(those carry their own alarm engine and are not shadow PVs).
Sourcepub async fn set_pv_metadata(
&self,
name: &str,
snapshot: &Snapshot,
) -> CaResult<()>
pub async fn set_pv_metadata( &self, name: &str, snapshot: &Snapshot, ) -> CaResult<()>
Install upstream DBR_CTRL_* metadata (display / control limits,
enum labels) on a shadow simple PV WITHOUT posting an event.
The CA gateway calls this once on upstream connect, after its initial
DBR_CTRL_* get, so a later downstream DBR_CTRL_* / DBR_GR_* read
returns the real limits instead of zeroed ones. No DBE_PROPERTY
monitor event fires — nothing has changed yet, this only seeds the
attribute cache. Mirrors C gatePvData::getCB → runDataCB →
vc->setPvData(dd) (gatePv.cc:1693-1695), which seeds the property
cache from the initial control get in both cache modes before any
monitor is enabled.
Returns ChannelNotFound for record-backed PVs — those own their own
metadata via record processing and are not gateway shadow PVs.
Sourcepub async fn post_pv_property(
&self,
name: &str,
snapshot: Snapshot,
) -> CaResult<()>
pub async fn post_pv_property( &self, name: &str, snapshot: Snapshot, ) -> CaResult<()>
Refresh a shadow simple PV’s upstream metadata AND post a
DBE_PROPERTY monitor event carrying snapshot to downstream
property subscribers.
snapshot is the decoded upstream DBR_CTRL_* property event: it
carries the control value and the upstream status / severity,
and (because control DBR structs carry no timestamp) an undefined
timestamp the caller must NOT replace with a fresh wall-clock. The
gateway’s property monitor calls this on every upstream
DBE_PROPERTY event, mirroring C gatePvData::propEventCB →
runDataCB + setPvData + runValueDataCB +
vcPostEvent(propertyEventMask()) (gatePv.cc:1571-1607): the
attribute cache is refreshed and a property event is posted with the
upstream alarm state preserved (setStatSevr) and the undefined
control-DBR timestamp left as-is (gatePv.cc:1594-1595).
Returns ChannelNotFound for record-backed PVs.
Sourcepub async fn put_pv_and_post_with_origin(
&self,
name: &str,
value: EpicsValue,
origin: u64,
) -> CaResult<()>
pub async fn put_pv_and_post_with_origin( &self, name: &str, value: EpicsValue, origin: u64, ) -> CaResult<()>
Like put_pv_and_post but with explicit origin tag.
Sourcepub async fn put_record_field_from_ca(
&self,
record_name: &str,
field: &str,
value: EpicsValue,
) -> CaResult<Option<Receiver<()>>>
pub async fn put_record_field_from_ca( &self, record_name: &str, field: &str, value: EpicsValue, ) -> CaResult<Option<Receiver<()>>>
CA client’s unified entry point for record field put. Handles DISP/PROC/PACT/LCNT checks, field put, device write, and Passive process.
Acquires the record’s advisory write gate
(dbScanLock analogue) for the duration of the write.
Sourcepub async fn put_record_field_from_ca_already_locked(
&self,
record_name: &str,
field: &str,
value: EpicsValue,
) -> CaResult<Option<Receiver<()>>>
pub async fn put_record_field_from_ca_already_locked( &self, record_name: &str, field: &str, value: EpicsValue, ) -> CaResult<Option<Receiver<()>>>
Variant for a caller that already owns the target
record’s advisory write gate — the QSRV atomic group PUT,
which acquired every member-record gate up-front via
Self::lock_records. The per-record tokio::sync::Mutex
gate is NOT reentrant, so the atomic group path MUST use this
_already_locked entry to avoid dead-locking on its own
ManyRecordWriteGuard.
Sourcepub async fn put_pv_no_process(
&self,
name: &str,
value: EpicsValue,
) -> CaResult<()>
pub async fn put_pv_no_process( &self, name: &str, value: EpicsValue, ) -> CaResult<()>
Put a PV value without triggering process (for restore).
Source§impl PvDatabase
impl PvDatabase
Sourcepub async fn evaluate_calc_link(&self, calc: &CalcLink) -> Option<EpicsValue>
pub async fn evaluate_calc_link(&self, calc: &CalcLink) -> Option<EpicsValue>
lnkCalc evaluation: fetch each input PV, bind to calc engine
vars A..L, run expr, return the result as EpicsValue::Double.
Returns None if any input fetch fails, expr compile fails, or
eval fails — the caller treats the link as unresolvable.
Sourcepub async fn evaluate_calc_link_with_time(
&self,
calc: &CalcLink,
) -> Option<(EpicsValue, Option<SystemTime>)>
pub async fn evaluate_calc_link_with_time( &self, calc: &CalcLink, ) -> Option<(EpicsValue, Option<SystemTime>)>
lnkCalc evaluation that also returns the timestamp pulled from
the input named by time_source (e.g. 'A' → first input).
Returns (value, Some(time)) when time_source is set and
the referenced input record has a timestamp, (value, None)
otherwise. The caller (link read path) uses None to mean
“consumer keeps its own apply_timestamp time”.
Sourcepub async fn external_link_alarm_snapshot(
&self,
name: &str,
) -> Option<RemoteAlarm>
pub async fn external_link_alarm_snapshot( &self, name: &str, ) -> Option<RemoteAlarm>
Ungated remote alarm snapshot for an external (pva:// /
ca://) link — the DB-link inspection counterpart of
Self::external_link_alarm.
Where external_link_alarm returns the gated maximize-severity
contribution folded into the owning record’s LINK_ALARM (pvxs
pvaGetValue applying the MS/NMS/MSI gate,
pvalink_lset.cpp:424-431), this returns the ungated remote
(severity, status, message) snapshot pvxs exposes through
dbGetAlarm / dbGetAlarmMsg (pvaGetAlarmMsg,
pvalink_lset.cpp:542-575). A default NMS link reports its
remote severity here even though it leaves the owning record
unraised.
None when no lset is registered for the scheme, the link is not
connected, or the lset does not track remote alarms. Scheme
dispatch mirrors Self::external_link_alarm.
Sourcepub async fn external_link_metadata(&self, name: &str) -> Option<LinkMetadata>
pub async fn external_link_metadata(&self, name: &str) -> Option<LinkMetadata>
Remote display / control / valueAlarm metadata for an external
(pva:// / ca://) link, resolved through the registered
lset’s [LinkSet::link_metadata] hook.
This is the DB-link-API entry point that exposes the linked PV
metadata pvxs’s pvalink lset surfaces through its
pvaGetDBFtype / pvaGetElements / pvaGetControlLimits /
pvaGetGraphicLimits / pvaGetAlarmLimits / pvaGetPrecision
/ pvaGetUnits getters
(pvxs/ioc/pvalink_lset.cpp:700). Scheme dispatch mirrors
Self::external_link_alarm: an explicit pva:// / ca://
prefix selects the lset directly, a bare name tries every
registered lset until one reports metadata.
None when no lset is registered for the scheme or the lset
has no cached value for the link (not yet connected).
Sourcepub async fn read_link_value_soft(
&self,
link: &ParsedLink,
is_soft: bool,
visited: &mut HashSet<String>,
depth: usize,
) -> Option<EpicsValue>
pub async fn read_link_value_soft( &self, link: &ParsedLink, is_soft: bool, visited: &mut HashSet<String>, depth: usize, ) -> Option<EpicsValue>
Read a value from a parsed link for INP (only reads DB links when soft channel).
visited / depth are the caller’s processing-chain state — a PP
input link’s source is processed within that same chain so the
visited cycle guard spans the PP hop (see
Self::process_passive_db_source).
Sourcepub async fn register_cp_link(
&self,
source_record: &str,
target_record: &str,
passive_only: bool,
)
pub async fn register_cp_link( &self, source_record: &str, target_record: &str, passive_only: bool, )
Register a CP link: when source_record changes, process target_record.
Both names are normalised to canonical form so the cp_links
map’s key/value always match the canonical record name that
dispatch_cp_targets uses for lookup. Without this, a user
who wrote INP="ALIAS_NAME CP" in their .db file would
register the CP edge under the alias key and then never see
the target processed (the source record’s canonical-name
dispatch would miss).
passive_only is true for a CPP edge (process the target only when
its SCAN is Passive) and false for CP (always process). When the
same source→target edge is registered from both a CP and a CPP link,
CP dominates: the merged edge keeps passive_only == false, matching
C, where an unconditional CP CA_DBPROCESS overrides any CPP gate on
the same record.
Sourcepub async fn get_cp_targets(&self, source_record: &str) -> Vec<CpTarget>
pub async fn get_cp_targets(&self, source_record: &str) -> Vec<CpTarget>
Get target edges to process when source_record changes (CP/CPP links).
Sourcepub async fn register_external_cp_link(
&self,
external_pv: &str,
target_record: &str,
passive_only: bool,
)
pub async fn register_external_cp_link( &self, external_pv: &str, target_record: &str, passive_only: bool, )
Register an EXTERNAL CP/CPP link: when the remote PV external_pv
(a cross-IOC CA/PVA source, e.g. OTHER:PV from
INP="OTHER:PV CP CA") changes, process target_record.
Twin of Self::register_cp_link for cross-IOC sources. The key is
the scheme-stripped external PV name — it is not a local record,
so it is NOT alias-resolved. It MUST equal the name the calink/pvalink
monitor dispatches under: that monitor is opened through the lset,
which strips the ca:// / pva:// scheme first, so its pv_name
(passed to Self::dispatch_external_cp_targets) is the bare PV.
Stripping the same schemes here — the set Self::resolve_external_pv
already knows — guarantees the registry key and the dispatch key can
never diverge. The target_record (the local holder) IS alias-resolved
so the dispatch processes the canonical record. CP dominates CPP on a
merged edge, identical to the local path.
Sourcepub async fn get_external_cp_targets(&self, external_pv: &str) -> Vec<CpTarget>
pub async fn get_external_cp_targets(&self, external_pv: &str) -> Vec<CpTarget>
Get holder edges to process when the remote PV external_pv changes
(external CA/PVA CP/CPP links).
Sourcepub async fn external_cp_pv_names(&self) -> Vec<String>
pub async fn external_cp_pv_names(&self) -> Vec<String>
Every external PV name that has at least one CP/CPP holder edge.
The calink/pvalink integration warms (opens a monitor for) each of
these at iocInit so the remote source has a live subscription whose
callback can drive Self::dispatch_external_cp_targets — a Passive
CP holder is otherwise never read and its monitor never opens.
Sourcepub async fn setup_cp_links(&self)
pub async fn setup_cp_links(&self)
Scan all records for CP/CPP input links and register them. Local
Db links land in the local CP registry; external Ca links land
in the external CP registry, whose holders are processed by the
calink monitor callback.
Source§impl PvDatabase
impl PvDatabase
Sourcepub async fn process_record(&self, name: &str) -> CaResult<()>
pub async fn process_record(&self, name: &str) -> CaResult<()>
Process a record by name (process_local + notify). Alias-aware (epics-base PR #336).
Sourcepub async fn process_record_already_locked(&self, name: &str) -> CaResult<()>
pub async fn process_record_already_locked(&self, name: &str) -> CaResult<()>
process_record variant for a caller that already
owns the record’s advisory write gate — the QSRV atomic group
PUT applying a +proc member. The gate Mutex is not
reentrant; the atomic group path MUST use this entry. See
crate::server::database::PvDatabase::lock_records.
Sourcepub fn process_record_with_links<'a>(
&'a self,
name: &'a str,
visited: &'a mut HashSet<String>,
depth: usize,
) -> Pin<Box<dyn Future<Output = CaResult<()>> + Send + 'a>>
pub fn process_record_with_links<'a>( &'a self, name: &'a str, visited: &'a mut HashSet<String>, depth: usize, ) -> Pin<Box<dyn Future<Output = CaResult<()>> + Send + 'a>>
Process a record with full link handling (INP -> process -> alarms -> OUT -> FLNK). Uses visited set for cycle detection and depth limit.
Foreign-caller entry: FLNK dispatch, scan loop, scan_event, CA put,
process(PROC=1) etc. Hits the PACT entry guard (mirrors C dbProcess
at dbAccess.c:537-559) when the record is mid-async.
this is a foreign full-processing entry, so it acquires
the record’s advisory write gate (dbScanLock analogue) for the
entry record before processing. A QSRV atomic group or pvalink
atomic scan-on-update epoch that holds lock_records over the
same record blocks a foreign scan/event/FLNK-dispatch caller
here, and vice versa — restoring the DBManyLock exclusion. The
recursive FLNK / OUT / CP fan-out within one chain does NOT
re-acquire the gate (process_record_with_links_recursive),
mirroring C processTarget (dbDbLink.c:436) which asserts the
target’s lock set is already owned by the calling thread; the
visited cycle guard prevents re-processing the entry record.
Sourcepub fn process_record_with_links_already_locked<'a>(
&'a self,
name: &'a str,
visited: &'a mut HashSet<String>,
depth: usize,
) -> Pin<Box<dyn Future<Output = CaResult<()>> + Send + 'a>>
pub fn process_record_with_links_already_locked<'a>( &'a self, name: &'a str, visited: &'a mut HashSet<String>, depth: usize, ) -> Pin<Box<dyn Future<Output = CaResult<()>> + Send + 'a>>
full-processing entry for a caller that already owns the
record’s advisory write gate via PvDatabase::lock_records —
the QSRV atomic group GET/PUT and the pvalink atomic
scan-on-update epoch. The advisory gate Mutex is not
reentrant; a transaction owner holding lock_records over the
member set MUST use this entry to scan a member record, or it
would deadlock against its own epoch guard. Foreign (non-owner)
callers must use Self::process_record_with_links so the gate
is taken.
Sourcepub fn process_record_continuation<'a>(
&'a self,
name: &'a str,
visited: &'a mut HashSet<String>,
depth: usize,
) -> Pin<Box<dyn Future<Output = CaResult<()>> + Send + 'a>>
pub fn process_record_continuation<'a>( &'a self, name: &'a str, visited: &'a mut HashSet<String>, depth: usize, ) -> Pin<Box<dyn Future<Output = CaResult<()>> + Send + 'a>>
Owner-driven continuation re-entry — bypasses the PACT entry guard.
Used by ProcessAction::ReprocessAfter timer fires: the spawned
re-entry task IS the owner of the async cycle, equivalent to C
callbackRequestDelayed’s direct call to the record’s process()
(which bypasses dbProcess). Foreign callers must still go through
process_record_with_links so FLNK / scan / CA put cannot race
during the wait window.
the timer fire is a fresh task — the original cycle’s
advisory gate was released when process_record_with_links
returned async-pending. In C, callbackRequestDelayed dispatches
through a callback that re-takes dbScanLock(precord) for the
completion process(). This entry therefore re-acquires the
advisory write gate, so the continuation cannot interleave with a
QSRV atomic group or another foreign scan of the same record.
Sourcepub fn async_handle(&self) -> AsyncDbHandle
pub fn async_handle(&self) -> AsyncDbHandle
A cycle-free AsyncDbHandle for this database, handed to each
record via crate::server::record::Record::set_async_context at
registration. Holds only a Weak reference, so a record stashing
it never keeps the database alive.
Sourcepub async fn mint_async_token(&self, name: &str) -> Option<AsyncToken>
pub async fn mint_async_token(&self, name: &str) -> Option<AsyncToken>
Mint a fresh async re-entry AsyncToken for name.
Minting advances the record’s generation counter, so any
previously-minted token for the same record is superseded — its
AsyncToken::fire becomes a structural no-op. This mirrors C
callbackRequestDelayed replacing an outstanding delayed callback
for a record. name must be the canonical record name (the value
of RecordInstance::name). Returns None if the record is absent.
Sourcepub async fn cancel_async_reentry(&self, name: &str)
pub async fn cancel_async_reentry(&self, name: &str)
Cancel any outstanding async re-entry token for name (C
callbackCancelDelayed): advance the record’s generation counter so
every previously-minted AsyncToken for it becomes stale and its
fire is a no-op. A subsequent Self::mint_async_token produces a
fresh, current token. No-op if the record is absent.
Sourcepub async fn post_fields(
&self,
name: &str,
fields: Vec<(String, EpicsValue)>,
) -> CaResult<Vec<String>>
pub async fn post_fields( &self, name: &str, fields: Vec<(String, EpicsValue)>, ) -> CaResult<Vec<String>>
Post an async-side field update for name — the C db_post_events
analogue called from device-support / async-callback context.
Each (field, value) is written through the internal put (bypassing
the read-only field gate, like a record’s own process() writes)
and a monitor event is posted with DBE_VALUE | DBE_LOG — the mask C
device support uses for an out-of-process value post
(db_post_events(precord, &prec->field, DBE_VALUE | DBE_LOG)).
Metadata-class writes invalidate the metadata cache via
notify_field_written, honouring the snapshot-cache contract.
Unlike Self::complete_async_record, this runs no alarm /
timestamp / FLNK tail: it is the immediate “push these fields to
monitors now” primitive (e.g. asyn TRACE info, motor intermediate
readback) that is independent of any process cycle. Returns the
field names actually posted, or CaError::ChannelNotFound if the
record is absent.
Sourcepub fn new_put_notify() -> (Arc<NotifyWaitSet>, Receiver<()>)
pub fn new_put_notify() -> (Arc<NotifyWaitSet>, Receiver<()>)
Create a put-notify wait-set for a downstream operation a record is
about to drive, returning the wait-set (to attach to the downstream
target instance’s notify) and the completion receiver.
C dbNotify.c processNotify: the set arms pending = 1 for the
downstream operation and fires the oneshot when that slot (plus any
FLNK/OUT chain members that enter it) drains to zero — i.e. on
dbNotifyCompletion. Pair with Self::reprocess_on_notify to
re-enter a waiting record when the downstream completes (SSEQ
WAITn).
Sourcepub fn reprocess_on_notify(
&self,
token: AsyncToken,
completion: Receiver<()>,
) -> JoinHandle<()>
pub fn reprocess_on_notify( &self, token: AsyncToken, completion: Receiver<()>, ) -> JoinHandle<()>
Wire a downstream put-notify completion to an async re-entry: spawn a
task that awaits completion (the oneshot from
Self::new_put_notify, fired on dbNotifyCompletion) and then
token.fires, re-entering the waiting record’s process(). A
superseded / cancelled token re-enters nothing. Returns the spawned
task handle; fire-and-forget callers may drop it.
Sourcepub async fn put_link_notify(
&self,
record_name: &str,
link_str: &str,
value: EpicsValue,
) -> Option<Receiver<()>>
pub async fn put_link_notify( &self, record_name: &str, link_str: &str, value: EpicsValue, ) -> Option<Receiver<()>>
Issue a put-WITH-completion to an OUT link and hand the caller only
the completion receiver — the non-blocking sibling of
Self::reprocess_on_notify.
Each call mints its own put-notify wait-set (C dbProcessNotify),
writes the link through it with the source record’s committed PUTF /
alarm propagated (C recGblInheritSevrMsg), releases the initiator
count, and returns the oneshot that fires on dbNotifyCompletion.
The caller owns when (and whether) to await each receiver, so several
puts can be outstanding at once — unlike
[ProcessAction::WriteDbLinkNotify], which wires the completion
straight to a single superseding async re-entry token and so allows
only one outstanding put per record. This is the seam C
calcApp/src/sseqRecord.c needs to run multiple WAITn put-callbacks
concurrently in flight (processNextLink).
record_name is the source whose PUTF/alarm propagate into the
target, link_str the already-resolved OUT link spelling, value
the value to write. None if the source record is gone; an empty
link_str returns a receiver that fires immediately (nothing joined
the set).
Sourcepub fn complete_async_record<'a>(
&'a self,
name: &'a str,
) -> Pin<Box<dyn Future<Output = CaResult<()>> + Send + 'a>>
pub fn complete_async_record<'a>( &'a self, name: &'a str, ) -> Pin<Box<dyn Future<Output = CaResult<()>> + Send + 'a>>
Complete an asynchronous record’s post-process steps. Call after device support signals completion (clears PACT, runs alarms, snapshot, OUT, FLNK).
Sourcepub async fn dispatch_external_cp_targets(&self, external_pv: &str)
pub async fn dispatch_external_cp_targets(&self, external_pv: &str)
Process every holder of an EXTERNAL CP/CPP link to external_pv —
the cross-IOC twin of Self::dispatch_cp_targets. Called by the
calink/pvalink CA monitor callback on every remote change, this is
the Rust equivalent of C dbCa.c eventCallback adding
CA_DBPROCESS for a CP (or Passive CPP) link (dbCa.c:993-994)
and the worker thread running db_process(prec) (dbCa.c:1295).
A cross-IOC source never processes locally, so this callback is the
only trigger; without it a CP/CPP link’s holder never processes
on a remote change.
A fresh visited set and depth = 0 start a new process chain —
the monitor event is an independent external trigger, like a scan,
not a continuation of an in-flight local chain.
Source§impl PvDatabase
impl PvDatabase
Sourcepub async fn lock_record(&self, record: &str) -> RecordWriteGuard
pub async fn lock_record(&self, record: &str) -> RecordWriteGuard
Acquire the advisory write gate for a single record.
This is the dbScanLock(precord) analogue. The plain CA/PVA
write path holds this for the duration of one record write so
it cannot interleave with a multi-record transaction that owns
the same record’s gate via Self::lock_records.
record is alias-resolved internally, so an alias and its
target always map to the same gate as Self::lock_records
keys them.
Sourcepub async fn lock_records<I, S>(&self, records: I) -> ManyRecordWriteGuard
pub async fn lock_records<I, S>(&self, records: I) -> ManyRecordWriteGuard
Acquire the advisory write gates for a set of records — the
DBManyLock / DBManyLocker equivalent.
Every name is alias-resolved to its canonical record name, the
set is sorted and de-duplicated, then the per-record gates are
acquired in that sorted order. Sorting guarantees two
overlapping multi-record transactions acquire their shared
records in the same global order and cannot deadlock (mirrors
pvxs DBManyLock sorting the lock set in dbLock.c); the
dedup means a record bound by more than one member link is
locked exactly once.
The returned ManyRecordWriteGuard must be held for the
whole transaction. While it is alive, a concurrent plain write
to any of those records blocks on Self::lock_record, and a
concurrent overlapping transaction blocks on this method.
Names that do not resolve to a record still get a gate (keyed
by the post-alias name) — matching dbLockerAlloc, which
accepts the record pointers it is given without a liveness
re-check.
Source§impl PvDatabase
impl PvDatabase
Sourcepub async fn update_scan_index(
&self,
name: &str,
old_scan: ScanType,
_new_scan: ScanType,
old_phas: i16,
_new_phas: i16,
)
pub async fn update_scan_index( &self, name: &str, old_scan: ScanType, _new_scan: ScanType, old_phas: i16, _new_phas: i16, )
Update scan index when a record’s SCAN or PHAS field changes.
Takes registration_mutex so the read of
the records map (to verify the record still exists) and the
scan_index mutation are atomic vs. concurrent remove_record.
The new_scan / new_phas parameters
the caller passes are advisory only. After acquiring the
mutex we read the LIVE record’s current scan/phas and insert
based on those. Pre-fix a put-then-update sequence could
race a remove+re-add of the same name: the caller’s
new_scan reflected the old (now-removed) record’s value;
inserting that under the fresh record’s name produced a
stale scan-index entry pointing at a wrong scan rate. The
live-read makes the index strictly reflect the record’s
current state at insert time.
Sourcepub async fn records_for_scan(&self, scan_type: ScanType) -> Vec<String>
pub async fn records_for_scan(&self, scan_type: ScanType) -> Vec<String>
Get record names for a given scan type, sorted by PHAS then
database load order (C dbScan.c stable same-PHAS FIFO).
Sourcepub async fn pini_records(&self) -> Vec<String>
pub async fn pini_records(&self) -> Vec<String>
Get all record names that have PINI=true.
Snapshot the records map under the outer
read lock, then drop it before fanning out per-record reads.
Pre-fix the outer records.read() lock was held across every
rec.read().await — under contention with a pending
add_record (which now takes the registration_mutex →
records.write()), startup could stall while every PINI
record was inspected serially.
Sourcepub async fn post_event(&self)
pub async fn post_event(&self)
Process all records with SCAN=Event, regardless of EVNT.
Back-compat entry point for the iocsh postEvent command,
whose handler currently drops the numeric event argument.
Prefer Self::post_event_named for C-correct per-event
routing — see dbScan.c:548-552 post_event →
postEvent(pevent_list[event]).
Sourcepub async fn post_event_named(&self, event_name: &str)
pub async fn post_event_named(&self, event_name: &str)
Process only the SCAN=Event records whose EVNT resolves to
event_name. Mirrors C dbScan.c event routing: each
event_list (eventNameToHandle) holds exactly the records
whose EVNT matches, and postEvent walks only that list.
Event-name matching follows eventNameToHandle (dbScan.c:469):
surrounding whitespace is trimmed, and a numeric string with an
integer part in [1,255] is normalised to its integer form so
"5", " 5 " and "5.0" all name the same event.
Source§impl PvDatabase
impl PvDatabase
pub fn new() -> Self
Sourcepub fn try_claim_scan_start(&self) -> bool
pub fn try_claim_scan_start(&self) -> bool
Atomically claim the right to start the scan scheduler for this DB.
Returns true on the first call, false on subsequent calls.
Used by ScanScheduler::run_with_hooks to prevent duplicate scan tasks
when multiple protocol servers (CA + PVA) both try to start scanning.
Sourcepub fn mark_pini_done(&self)
pub fn mark_pini_done(&self)
Mark PINI processing complete. Wakes any non-owner scan schedulers that were waiting before running their hooks.
Sourcepub async fn wait_for_pini(&self)
pub async fn wait_for_pini(&self)
Wait until the scan owner has completed PINI processing. Returns immediately if PINI has already completed.
Sourcepub async fn set_search_resolver(&self, resolver: SearchResolver)
pub async fn set_search_resolver(&self, resolver: SearchResolver)
Install an async resolver invoked when PvDatabase::has_name
fails to find a name. Used by proxy/gateway implementations to
lazily populate PVs on first search.
Sourcepub async fn clear_search_resolver(&self)
pub async fn clear_search_resolver(&self)
Remove the previously installed search resolver, if any.
Sourcepub async fn set_existence_gate(&self, gate: ExistenceGate)
pub async fn set_existence_gate(&self, gate: ExistenceGate)
Install the per-request existence gate (see ExistenceGate).
Replaces any previously installed gate. Used by the CA gateway so
a cached shadow PV re-runs host/state admission per request.
Sourcepub async fn clear_existence_gate(&self)
pub async fn clear_existence_gate(&self)
Remove the previously installed existence gate, if any.
Sourcepub async fn set_external_resolver(&self, resolver: ExternalPvResolver)
pub async fn set_external_resolver(&self, resolver: ExternalPvResolver)
Set an external PV resolver for CA/PVA link resolution. The resolver is called synchronously from link reads.
Sourcepub async fn register_link_set(&self, scheme: &str, lset: DynLinkSet)
pub async fn register_link_set(&self, scheme: &str, lset: DynLinkSet)
Register a LinkSet under scheme (e.g. "pva" /
"ca"). The lset is consulted for ParsedLink::Pva /
ParsedLink::Ca link reads/writes before falling back to
the legacy ExternalPvResolver. Subsequent calls for the
same scheme replace the previous binding.
Sourcepub async fn link_set(&self, scheme: &str) -> Option<DynLinkSet>
pub async fn link_set(&self, scheme: &str) -> Option<DynLinkSet>
Look up the lset for scheme, if any.
Sourcepub async fn registered_link_schemes(&self) -> Vec<String>
pub async fn registered_link_schemes(&self) -> Vec<String>
Snapshot of every registered scheme name. Stable order for
dbpvxr dumps.
Sourcepub async fn wait_for_external_links(&self, timeout: Duration) -> (usize, usize)
pub async fn wait_for_external_links(&self, timeout: Duration) -> (usize, usize)
Wait for the CA links to local records to report
is_connected() == true. Mirrors dbCa: iocInit wait for local CA links to connect (epics-base PR #768/#856). The working set is
exactly Self::external_link_targets: only the CA facility’s
local-target links — pva:// links and non-local CA links connect
in the background and are never waited on (pvxs parity).
Polls every 100 ms. Returns:
Ok(connected_count)— the number of links that ended up connected. May be smaller than the total when the timeout expired before everyone was ready.- The total link count — i.e. the size of the working set
that was checked.
(connected, total)lets the caller log “M/N CA links connected”.
Pure no-op when no CA link set is registered, or when its
link_names() has no local-target link yet (e.g. lazy-open lsets
that haven’t observed any record link — record processing creates
the entries on first read, after iocInit returns).
Sourcepub async fn unconnected_external_links(&self) -> Vec<String>
pub async fn unconnected_external_links(&self) -> Vec<String>
Names of the waited-on CA links (local-target, per
Self::external_link_targets) that are opened but not yet
connected. iocInit calls this after
Self::wait_for_external_links times out so the
“M/N connected” diagnostic can name the N-M it proceeded
without, instead of leaving the operator to run dbcar.
pva:// links are not in this set — they never block iocInit.
Sourcepub async fn record_link_fields(
&self,
record_name: &str,
) -> Vec<(String, String, ParsedLink)>
pub async fn record_link_fields( &self, record_name: &str, ) -> Vec<(String, String, ParsedLink)>
Enumerate every link-shaped field on record_name. Returns
(field_name, link_string, parsed) tuples for fields whose
raw value parses as a non-trivial link via
crate::server::record::parse_link_v2. Used by dbpvxr to
dump per-record link state without hardcoding the field-name
list — works across record types as long as they expose link
strings via Record::get_field.
Returns an empty Vec when the record doesn’t exist.
Sourcepub async fn add_pv(&self, name: &str, initial: EpicsValue) -> CaResult<()>
pub async fn add_pv(&self, name: &str, initial: EpicsValue) -> CaResult<()>
Add a simple PV with an initial value.
Returns Err when name is already registered as a simple PV,
a record, or an alias — mirroring epics-base C IOC which treats
duplicate dbLoadRecords names as a fatal error. Callers that
want replace-on-overwrite semantics must first call
remove_simple_pv / remove_record / remove_alias.
Serialized through registration_mutex so the
cross-namespace check is atomic with the insert and the lock
order across all add_/remove_ methods is identical (no
cross-namespace deadlock).
Sourcepub async fn add_pv_with_hook(
&self,
name: &str,
initial: EpicsValue,
hook: WriteHook,
) -> CaResult<()>
pub async fn add_pv_with_hook( &self, name: &str, initial: EpicsValue, hook: WriteHook, ) -> CaResult<()>
Add a simple PV that already has a [WriteHook] installed.
Equivalent to add_pv followed by find_pv + set_write_hook,
but the PV is constructed with the hook in place so it is
inserted into the simple_pvs map ATOMICALLY with the hook
already attached. Closes a small race in proxy/gateway code
where a downstream client could (in principle) CREATE_CHAN +
WRITE_NOTIFY between the two awaits and hit the local
pv.set() fallback path before the hook landed.
Returns Err on duplicate name (see [add_pv]).
Sourcepub async fn add_pv_with_hooks(
&self,
name: &str,
initial: EpicsValue,
write_hook: WriteHook,
access_hook: Option<AccessHook>,
) -> CaResult<()>
pub async fn add_pv_with_hooks( &self, name: &str, initial: EpicsValue, write_hook: WriteHook, access_hook: Option<AccessHook>, ) -> CaResult<()>
like Self::add_pv_with_hook but also installs an
optional AccessHook so the CA
gateway can route this shadow PV’s read/write access-rights
decision through its own ACF. Both hooks are attached before the
PV is inserted into simple_pvs, so a downstream CREATE_CHAN
cannot observe the PV without its access hook bound.
Sourcepub async fn add_pv_with_hooks_full(
&self,
name: &str,
initial: EpicsValue,
write_hook: WriteHook,
access_hook: Option<AccessHook>,
read_hook: Option<ReadHook>,
) -> CaResult<()>
pub async fn add_pv_with_hooks_full( &self, name: &str, initial: EpicsValue, write_hook: WriteHook, access_hook: Option<AccessHook>, read_hook: Option<ReadHook>, ) -> CaResult<()>
like Self::add_pv_with_hooks but also installs an optional
ReadHook so a proxy (the CA
gateway in no-cache mode) can serve each downstream GET from a
fresh upstream fetch instead of the stored value. All three hooks
are attached before the PV is inserted into simple_pvs, so a
downstream CREATE_CHAN cannot observe the PV without its hooks
bound — the read hook lands atomically with registration, closing
the same race the write/access hooks already close. read_hook: None is identical to Self::add_pv_with_hooks.
Sourcepub async fn remove_simple_pv(&self, name: &str) -> Option<Arc<ProcessVariable>>
pub async fn remove_simple_pv(&self, name: &str) -> Option<Arc<ProcessVariable>>
Remove a simple PV by name. Returns Some(pv) if a PV was
removed. Used by the gateway sweep so an evicted upstream
subscription doesn’t leave a stale shadow PV (with a now-dead
WriteHook capturing an aborted upstream channel).
Also purges any aliases that pointed AT this name (otherwise a re-add of the same alias name would fail with “already registered as an alias” even though its target is gone).
Sourcepub async fn add_record(
&self,
name: &str,
record: Box<dyn Record>,
) -> CaResult<()>
pub async fn add_record( &self, name: &str, record: Box<dyn Record>, ) -> CaResult<()>
Add a record (accepts a boxed Record to avoid double-boxing).
Returns Err when name collides with an existing record,
simple PV, or alias. The C IOC’s dbLoadRecords treats this as
fatal; do not silently replace.
The records-map insert AND scan-index insert run
under the same registration_mutex hold, eliminating the
TOCTOU window where remove_record could land between them
and leave a phantom scan entry.
Sourcepub async fn remove_record(&self, name: &str) -> bool
pub async fn remove_record(&self, name: &str) -> bool
Remove a record by name. Returns true if a record was removed,
false if no such name was registered. Mirrors epics-base PR
#505 — deletion at database creation, exposed here as a public
API so iocsh dbDeleteRecord and tests can drive it.
The cleanup covers the three indices that add_record populates:
the records map, the scan index, and CP-link source/target lists.
Live subscribers on the removed record drop their Sender clone
when the RecordInstance is dropped — they observe Closed on
next recv, matching the existing dbEvent cancel flow.
Sourcepub async fn add_alias(&self, alias: &str, target: &str) -> CaResult<()>
pub async fn add_alias(&self, alias: &str, target: &str) -> CaResult<()>
Register an alias alias for an existing record target.
Mirrors epics-base PR #336. Returns Err(...) when the target
does not exist or the alias name is already in use anywhere
in the database (records, simple PVs, or other aliases).
Pre-fix the alias path checked only
records and aliases — a simple-PV with the same name as
the proposed alias was missed, leaving the database in a
state where find_pv(alias) could resolve to either the
simple PV or the alias-mapped record depending on lookup
order. Now we run the same cross-namespace check_name_free
guard the other add_* paths use.
Sourcepub async fn resolve_alias(&self, name: &str) -> Option<String>
pub async fn resolve_alias(&self, name: &str) -> Option<String>
Resolve an alias to its target record name, or None when the
name is not an alias.
Sourcepub fn queue_after_ioc_running(&self, line: impl Into<String>)
pub fn queue_after_ioc_running(&self, line: impl Into<String>)
Queue an iocsh command line for post-PINI execution.
Mirrors epics-base PR #558 — afterIocRunning <command> lets
the startup script schedule actions that run after iocInit
completes (when the record set is fully wired up).
Sourcepub fn take_after_ioc_running(&self) -> Vec<String>
pub fn take_after_ioc_running(&self) -> Vec<String>
Drain the post-PINI iocsh command queue. Called by
IocApplication::run after PINI processing.
Sourcepub async fn find_entry(&self, name: &str) -> Option<PvEntry>
pub async fn find_entry(&self, name: &str) -> Option<PvEntry>
Look up an entry by name. Supports “record.FIELD” syntax.
If the name is not found and a search resolver is installed, the resolver is invoked once. If the resolver returns true, the database is re-checked.
Sourcepub async fn find_entry_from(
&self,
name: &str,
peer: Option<SocketAddr>,
) -> Option<PvEntry>
pub async fn find_entry_from( &self, name: &str, peer: Option<SocketAddr>, ) -> Option<PvEntry>
Like Self::find_entry, but threads the downstream client’s
socket address into the search resolver. The CA TCP CREATE_CHANNEL
handler passes the connection peer so the gateway can apply
host-scoped .pvlist admission.
Sourcepub async fn has_name(&self, name: &str) -> bool
pub async fn has_name(&self, name: &str) -> bool
Check if a base name exists (for UDP search).
If the name is not in the database and a search resolver is installed, the resolver is invoked. The resolver may populate the database (e.g., subscribe to an upstream IOC and add a placeholder PV) and return true; this method then re-checks.
Sourcepub async fn has_name_from(&self, name: &str, peer: Option<SocketAddr>) -> bool
pub async fn has_name_from(&self, name: &str, peer: Option<SocketAddr>) -> bool
Like Self::has_name, but threads the downstream client’s
socket address into the search resolver. The CA UDP search
responder passes the datagram source address so the gateway can
apply host-scoped .pvlist admission.
Sourcepub async fn find_pv(&self, name: &str) -> Option<Arc<ProcessVariable>>
pub async fn find_pv(&self, name: &str) -> Option<Arc<ProcessVariable>>
Look up a simple PV by name (backward-compatible).
Sourcepub async fn get_record(
&self,
name: &str,
) -> Option<Arc<RwLock<RecordInstance>>>
pub async fn get_record( &self, name: &str, ) -> Option<Arc<RwLock<RecordInstance>>>
Get a record Arc by name. Alias-aware (epics-base PR #336):
when name is not a canonical record but matches a registered
alias, the alias’ target record is returned. Mirrors base
dbNameToAddr behaviour, so dbpf/dbpr/dbgf, CA channel lookup,
and DB-link target resolution all work transparently for
aliases.
Use Self::get_record_no_resolve when the caller already
holds a canonical name and wants to suppress the alias path
(e.g. to detect alias collisions during builder wiring).
Sourcepub async fn get_record_no_resolve(
&self,
name: &str,
) -> Option<Arc<RwLock<RecordInstance>>>
pub async fn get_record_no_resolve( &self, name: &str, ) -> Option<Arc<RwLock<RecordInstance>>>
Strict variant of Self::get_record — does NOT consult the
alias table. Returns Some only when a canonical record with
that exact name exists.
Sourcepub async fn all_record_names(&self) -> Vec<String>
pub async fn all_record_names(&self) -> Vec<String>
Get all record names.
Sourcepub async fn all_alias_names(&self) -> Vec<String>
pub async fn all_alias_names(&self) -> Vec<String>
Get all alias names registered against existing records.
Mirrors the alias-half of base’s dbFirstRecord iteration —
dbgrep / dbglob / dbsr walk both record names and
aliases when matching a glob.
Sourcepub async fn aliases_for_record(&self, canonical: &str) -> Vec<String>
pub async fn aliases_for_record(&self, canonical: &str) -> Vec<String>
Return every alias that points at canonical. Sorted for
stable output; empty when the record has no aliases. Used by
dbpr to surface alias-form names so admins can see how
clients may reach the record.
Sourcepub async fn all_simple_pv_names(&self) -> Vec<String>
pub async fn all_simple_pv_names(&self) -> Vec<String>
Get all simple PV names.
Trait Implementations§
Source§impl Clone for PvDatabase
impl Clone for PvDatabase
Source§fn clone(&self) -> PvDatabase
fn clone(&self) -> PvDatabase
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more