Skip to main content

PvDatabase

Struct PvDatabase 

Source
pub struct PvDatabase { /* private fields */ }
Expand description

Database of all process variables hosted by this server.

Implementations§

Source§

impl PvDatabase

Source

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.

Source

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

Source

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.

Source

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.

Source

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.

Source

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.

Source

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

Source

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::getCBrunDataCBvc->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.

Source

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::propEventCBrunDataCB + 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.

Source

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.

Source

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.

Source

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.

Source

pub async fn put_record_field_from_ca_no_notify( &self, record_name: &str, field: &str, value: EpicsValue, ) -> CaResult<()>

Fire-and-forget variant — C dbPutField semantics: the put processes the record but creates NO put-notify wait-set (C builds a putNotify only in dbPutNotify, i.e. for WRITE_NOTIFY). A caller that does not await the returned receiver MUST use this entry: parking a wait-set whose receiver is dropped occupies RecordInstance::notify until the record’s async work ends (a motor’s whole motion), failing every legitimate WRITE_NOTIFY on the record with ECA_PUTCBINPROG in the meantime.

Source

pub async fn put_record_field_from_ca_no_notify_already_locked( &self, record_name: &str, field: &str, value: EpicsValue, ) -> CaResult<()>

Source

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

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.

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

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.

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

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

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.

Source

pub async fn get_cp_targets(&self, source_record: &str) -> Vec<CpTarget>

Get target edges to process when source_record changes (CP/CPP links).

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.

Source

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

Source

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.

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

Source

pub async fn process_record(&self, name: &str) -> CaResult<()>

Process a record by name (process_local + notify). Alias-aware (epics-base PR #336).

Source

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.

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.

Source

pub fn process_record_readback<'a>( &'a self, name: &'a str, visited: &'a mut HashSet<String>, depth: usize, ) -> Pin<Box<dyn Future<Output = CaResult<()>> + Send + 'a>>

Driver-callback (asyn:READBACK) full-processing entry.

The single owner of this entry is the I/O Intr wiring ([crate::server::ioc_app::setup_io_intr] and its ioc_builder twin): the spawned task processes a record because the driver fired an interrupt callback, not because of a client put / FLNK / scan. device_callback = true tells Self::process_record_with_links_inner that, for an output record, this cycle must READ the callback value back into VAL and MUST NOT write it to the driver — C devAsynInt32.c::processBo (and processAo/processLongout/…) take the readback branch when newOutputCallbackValue is set, never processCallbackOutput’s write(). Without this, the readback re-asserts the setpoint and re-triggers the driver (e.g. AD Acquire looping). Input records (!can_device_write) are unaffected: their read stage already runs, and the no-write gate is keyed on the record being an output.

Acquires the entry record’s advisory write gate exactly like Self::process_record_with_links — the callback task is a foreign caller w.r.t. any QSRV atomic group / pvalink epoch.

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub async fn post_property_fields( &self, name: &str, fields: Vec<(String, EpicsValue)>, ) -> CaResult<Vec<String>>

Out-of-band PROPERTY-class field post — the C db_post_events(precord, &precord->val, DBE_PROPERTY) analogue used for enum-string table re-propagation (asyn callbackEnum, devAsynInt32.c:711-762). Writes each (field, value) through the internal put, invalidates the metadata cache, and posts a DBE_PROPERTY event so subscribers re-read enum choices / control metadata.

Unlike Self::post_fields (which posts DBE_VALUE | DBE_LOG) this signals a property change, not a value change: a driver that re-keys its enum strings has not produced a new reading, only new choice labels. Returns the field names actually posted.

Source

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

Source

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.

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

Source

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

Source

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

Source

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.

Source

pub async fn lock_records<I, S>(&self, records: I) -> ManyRecordWriteGuard
where I: IntoIterator<Item = S>, S: AsRef<str>,

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

Source

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.

Source

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

Source

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.

Source

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_eventpostEvent(pevent_list[event]).

Source

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

Source

pub fn new() -> Self

Source

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.

Source

pub fn mark_pini_done(&self)

Mark PINI processing complete. Wakes any non-owner scan schedulers that were waiting before running their hooks.

Source

pub async fn wait_for_pini(&self)

Wait until the scan owner has completed PINI processing. Returns immediately if PINI has already completed.

Source

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.

Source

pub async fn clear_search_resolver(&self)

Remove the previously installed search resolver, if any.

Source

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.

Source

pub async fn clear_existence_gate(&self)

Remove the previously installed existence gate, if any.

Source

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.

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.

Look up the lset for scheme, if any.

Snapshot of every registered scheme name. Stable order for dbpvxr dumps.

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

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.

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.

Source

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

Source

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

Source

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.

Source

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.

Source

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

Source

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.

Source

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.

Source

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.

Source

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.

Source

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

Source

pub fn take_after_ioc_running(&self) -> Vec<String>

Drain the post-PINI iocsh command queue. Called by IocApplication::run after PINI processing.

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub async fn find_pv(&self, name: &str) -> Option<Arc<ProcessVariable>>

Look up a simple PV by name (backward-compatible).

Source

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

Source

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.

Source

pub async fn all_record_names(&self) -> Vec<String>

Get all record names.

Source

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.

Source

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.

Source

pub async fn all_simple_pv_names(&self) -> Vec<String>

Get all simple PV names.

Trait Implementations§

Source§

impl Clone for PvDatabase

Source§

fn clone(&self) -> PvDatabase

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. 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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. 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