Skip to main content

RecordInstance

Struct RecordInstance 

Source
pub struct RecordInstance {
Show 14 fields pub name: String, pub record: Box<dyn Record>, pub common: CommonFields, pub subscribers: HashMap<String, Vec<Subscriber>>, pub parsed_inp: ParsedLink, pub parsed_out: ParsedLink, pub parsed_flnk: ParsedLink, pub parsed_sdis: ParsedLink, pub parsed_tsel: ParsedLink, pub device: Option<Box<dyn DeviceSupport>>, pub subroutine: Option<Arc<SubroutineFn>>, pub reprocess_generation: Arc<AtomicU64>, pub watchdog_generation: Arc<AtomicU64>, pub info: HashMap<String, String>, /* private fields */
}
Expand description

A type-erased record instance stored in the database.

Fields§

§name: String§record: Box<dyn Record>§common: CommonFields§subscribers: HashMap<String, Vec<Subscriber>>§parsed_inp: ParsedLink§parsed_out: ParsedLink§parsed_flnk: ParsedLink§parsed_sdis: ParsedLink§parsed_tsel: ParsedLink§device: Option<Box<dyn DeviceSupport>>§subroutine: Option<Arc<SubroutineFn>>§reprocess_generation: Arc<AtomicU64>

Generation counter for ReprocessAfter timer cancellation. Bumped each process cycle. Spawned timers check this to avoid stale re-processes from accumulated timers.

§watchdog_generation: Arc<AtomicU64>

Generation counter for the monitor watchdog (Record::watchdog_interval / Record::watchdog_fire), bumped by each PvDatabase::arm_watchdog so a re-arm supersedes the tick already in flight — C callbackRequestDelayed replacing an outstanding delayed callback. Deliberately NOT reprocess_generation: C’s histogram wdog is its own epicsCallback, independent of the record’s SDLY/async re-entry, so an SDLY defer must not cancel the watchdog nor vice versa.

§info: HashMap<String, String>

Per-record info tags from info("key", "value") directives in the .db file (epics-base info(…) grammar). Consumers include asyn (asyn:READBACK), record-as-PV bridge tags (Q:group, Q:form), and IOC-specific extensions. Empty for records loaded without info(…) clauses.

Implementations§

Source§

impl RecordInstance

Source

pub fn new(name: String, record: impl Record) -> Self

The raw text of one COMMON_LINK_FIELDS entry, or None for any other field name.

The parse cache of one COMMON_LINK_FIELDS entry, or None for any other field name. The only mutable handle on the cache outside put_common_field, so the iocInit locality commit cannot reach a slot that has no matching raw text.

The link fields whose target metadata this record’s rset serves — the work list PvDatabase::resolve_link_backed_metadata resolves for a batch post, and the set Self::link_backed_metadata_field_of answers one field out of.

Source

pub fn new_boxed(name: String, record: Box<dyn Record>) -> Self

Source

pub fn soft_output_value(&self) -> Option<Option<EpicsValue>>

SINGLE OWNER of the DTYP -> soft-output-dset mapping. The dset table decides what a soft OUT-link write carries; no caller may re-derive it.

C ships two soft output dsets per output record type and DTYP picks one: devXxxSoft.c::write_xxx puts VAL/OVAL on the OUT link, while devXxxSoftRaw.c::write_xxx puts the RAW word — dbPutLink(&prec->out, DBR_LONG, &prec->rval, 1) (devAoSoftRaw.c:44, devBoSoftRaw.c:65) or data = prec->rval & prec->mask (devMbboSoftRaw.c:71-75, devMbboDirectSoftRaw.c:71-75).

Record::raw_soft_output_value IS the SoftRaw column of that table: Some exactly for the record types C ships a SoftRaw dset for. A record type C has no SoftRaw dset for keeps the plain soft-channel value — DTYP="Raw Soft Channel" on a longout is a .db error C rejects at init (“no device support”), and the port’s lenient reading of it (the same one crate::server::device_support::is_soft_dtyp already applies on the input side) must not turn the write into a silent no-op.

None means DTYP names device support that owns the write — real hardware. “Async Soft Channel” is NOT that: C’s devXxxSoftCallback.c::write_xxx puts the same VAL/OVAL the plain soft dset puts, only through dbPutLinkAsync (devAoSoftCallback.c:49, devLoSoftCallback.c:49), and falls back to a synchronous dbPutLink when the link has no LSET. Returning None for it made every DTYP("Async Soft Channel") output record write nothing at all — measured on pva2pva/testApp/testpvalink.db:30-35, whose longout drives a pva OUT link that never fired.

Source

pub fn set_info(&mut self, key: impl Into<String>, value: impl Into<String>)

Set a single info("key", "value") tag on this record. Last write wins. Used by the .db loader (info(...) directive) and dbpf-style tools.

Source

pub fn get_info(&self, key: &str) -> Option<&str>

Look up a single info tag. Returns None when the record has no tag with that key.

Source

pub fn invalidate_metadata_cache(&self)

Invalidate the metadata cache. Called after writing any metadata-class field (EGU, PREC, HOPR/LOPR, alarm limits, DRVH/DRVL, enum strings). The next snapshot will rebuild the cache from the new values.

Source

pub fn notify_field_written(&self, field: &str)

Hook called by the database after a field is written. If the field is a metadata-cache source, the cache is invalidated so the next snapshot picks up the new value. Posts nothing — a caller that also owes the DBE_PROPERTY event uses Self::notify_field_written_if_changed.

Field name is automatically uppercased.

Source

pub fn notify_field_written_if_changed( &mut self, field: &str, prev: Option<&EpicsValue>, backing: LinkBacking<'_>, )

Like Self::notify_field_written, plus the DBE_PROPERTY post C makes from dbPut — and both are skipped when the put did not actually change the field’s value. Mirrors epics-base faac1df1: property events fire only on real changes, not on idempotent writes (the C path compares paddr->pfield against the converted payload before setting the propertyUpdate flag).

The two effects have independent gates. Invalidation follows is_metadata_cache_source (what this port’s cache reads); the post follows Self::field_posts_property (what the .dbd declares). A field can be either without being both.

prev is the value captured BEFORE the put. Callers that don’t need the change-detection (e.g. internal writers that know the field is neither) can keep using Self::notify_field_written.

backing is what the sweep needs and could not have: the post below names EVERY subscribed field, so it reaches a link-backed one whenever a client is monitoring it, and this method runs under the record’s own write lock where the target’s lock cannot be taken. The put path that calls it has already resolved one at its no-lock point.

Source

pub fn is_no_mod(&self, field: &str) -> bool

C dbChannelSpecial(chan) == SPC_NOMODthe single owner of the no-modify declaration, for every consumer that needs to know whether a field can be written.

C declares it once, in the .dbd, and reads it in two unrelated places:

  • dbPut (dbAccess.c:123-126, via dbPutSpecial(paddr, 0)) refuses the write — the port’s check_no_mod gate;
  • rsrvCheckPut (rsrv/camessage.c:2540-2551) — if (dbChannelSpecial(pciu->dbch) == SPC_NOMOD) return 0; — which feeds the CA ACCESS_RIGHTS write bit (camessage.c:1154-1156) as well as both put paths, so a client sees Access: read, no write and never sends the doomed write.

Only the first consumer existed in the port, so every dbCommon NOMOD field advertised WRITE on the wire (caput N1.SEVR 2 was refused server-side, after the client had already sent it, with an async exception instead of C’s clean client-side “Write access denied”).

Three sources, one answer:

  1. the dbCommon SPC_NOMOD set below — common fields, so no record’s field_list declares them;
  2. the record type’s declaration, resolved by Self::field_desc — the vendored .dbd whenever one exists, and only for a record type that has no .dbd at all (motor, optics, scaler, std) the record’s own hand-written table, which for those Tier 3 types genuinely is their declaration;
  3. Record::field_no_mod — an SPC_NOMOD a record’s cvt_dbaddr raises from its own state (compress VAL under BALG=LIFO, compressRecord.c:404-405), which a static FieldDesc cannot express.

field may be any case.

Source

pub fn is_processing(&self) -> bool

Check if the record is currently processing (PACT equivalent).

Source

pub fn enter_pact(&self)

C prec->pact = TRUE — the record goes busy for an async device round-trip, an SDLY simulation defer, or an ODLY reprocess window.

Source

pub fn leave_pact(&mut self) -> PactExit

C prec->pact = FALSE — the ONLY release of PACT.

The returned PactExit carries the release’s debt to the cycle tail, where a queued put-notify is restarted — the omission the open-coded processing.store(false) at the ODLY continuation and the three SIM/SDLY releases made.

#[must_use] does NOT enforce that debt and never did: the lint fires on an unused expression, so a site that binds the token with let and then leaves by ? or an early return warns about nothing. The enforcement is processing::CycleEndGuard, whose Drop pays the tail for every exit that did not.

Source

pub fn pact_exit_without_release(&self) -> PactExit

The cycle-tail token for a record this cycle did NOT release PACT on.

Still consults the queue: a notify parked behind an in-flight wait-set on an idle record is freed by the wait-set completion, and the tail is what promotes it.

Source

pub fn notify_put_is_owned(&self) -> bool

C processNotifyCommon’s two defer tests (dbNotify.c:213, 225), as one question: may a NEWLY ARRIVING put-notify take this record now?

true for an in-flight wait-set (precord->ppn), for PACT, and for a non-empty restart list — the last so a notify arriving in the window between a completion and the restart check cannot jump the queue.

A RESTARTED put is not asked this: it is already the record’s owner (C precord->ppn == ppn, state notifyRestartCallbackRequested, which dbNotify.c:213 exempts by name) and only PACT can stop it — see Self::requeue_notify_put.

Source

pub fn notify_put_has_owner(&self) -> bool

C processNotifyCommon’s FIRST defer test alone (dbNotify.c:213): another processNotify owns this record, or one is already queued behind it. Self::notify_put_is_owned folds in the PACT arm (:225) as well.

A DBF link-field put waits on ownership but NOT on PACT. A bare sub with an empty SNAM parks PACT=TRUE forever (subRecord.c:119-122), so a link put that waited on the PACT arm there would never be written and caput <sub>.INPA 0 would read back empty. Ownership carries no such trap: the restart check drains the queue at every cycle end.

Source

pub fn install_or_queue_notify( &mut self, completion: Sender<()>, ) -> Option<Arc<NotifyWaitSet>>

C ellSafeAdd(&precord->ppnr->restartList, &ppn->restartNode) — the arriving put-notify joins the back of the queue, unwritten.

Infallible: C has no “refuse” arm here, and a refusal loses the client’s write. Call only under Self::notify_put_is_owned. Take this record’s put-notify slot, or queue behind whoever holds it.

C processNotifyCommon (dbNotify.c:211-231) has exactly two outcomes and no third: the record is free and the notify takes it, or it is owned and the notify joins precord->ppnr->restartList. There is no refusal arm — ECA_PUTCBINPROG has one sender in all of base, the 60-second put-callback timeout in write_notify_action (rsrv/camessage.c:1701 at R7.0.10).

None means queued, and the caller MUST NOT process: the replay drives the record and fires the callback, so processing here would run the cycle twice for one client request.

Ownership alone decides — NOT Self::notify_put_has_owner. A non-empty restart list stops a fresh arrival at the entry gate, but a replay reaching here has already been popped off that list and must take the slot with its successors still queued behind it, exactly as C restartCheck (dbNotify.c:158-168) assigns precord->ppn = pfirst while leaving the rest of restartList in place.

Source

pub fn join_put_notify(&mut self, src: Option<&Arc<NotifyWaitSet>>)

C dbNotifyAdd (dbNotify.c:477-501): a link target joins the wait-set of the put-notify driving the chain, so the initiator’s completion waits for this record’s cycle too.

One of the two callers of take_notify_slot, the sole writer; the other is Self::install_or_queue_notify. All three live here so the slot has no assignment site outside this module — an open-coded one elsewhere is how a wait-set came to be installed without the record’s write gate.

A record already carrying a wait-set keeps it (C’s if (!pto->ppn …) at :492), so this never displaces a live one, and the enter is paired with the leave the target’s own cycle tail performs.

Source

pub fn has_notify(&self) -> bool

Whether a put-notify owns this record — C precord->ppn != NULL.

The public read of the slot. The wait-set itself stays crate-private so no caller outside this crate can enter/leave a set it does not own, which is the accounting NotifyWaitSet exists to keep.

Source

pub fn queue_notify_put(&mut self, put: DeferredNotify)

Source

pub fn resolve_field(&self, name: &str) -> Option<EpicsValue>

Unified field resolution: record fields → common fields → virtual fields — and, for a link field, C dbGet’s rendering of it.

This is the port’s dbGet (dbAccess.c:625-961): the read every external reader arrives at, whether it came from PvDatabase::get_pv on behalf of a CA client, from dbgf, or from dbpr. C’s dbGet sends DBF_INLINK/DBF_OUTLINK/DBF_FWDLINK to getLinkValue (:944-947), which renders the link with dbGetString (:850-856), so applying that here is what makes every reader agree without any of them knowing the rule.

The STORE is still the text — Record::get_field — and that is what the link layer parses. The two are not the same value and do not share a name: C likewise reads precord->inp directly when it wants the link and dbGet when it wants what a client would see.

Source

pub fn resolve_field_stored(&self, name: &str) -> Option<EpicsValue>

Self::resolve_field without the reader’s view — what the field HOLDS, which for a link field is the text C’s dbParseLink takes (dbStaticLib.c:2246) rather than what dbGetString renders (:1906-2050).

dbpr needs both of the same field, and in C they come from one address: it prints the link’s resolved TYPE in front of the rendered text (dbTest.c:1205-1224). Splitting the accessor chain here keeps that one address — a second walk to find the stored text would be a second answer to “which field is this”, and the round before this one is what happens when those two disagree.

name must already be upper-case.

Source

pub fn resolve_string_view_field(&self, name: &str) -> Option<EpicsValue>

Resolve a field for EPICS $ long-string (character-array) access.

The $ channel-name modifier (C dbChannel.c:486-505) re-views a field as a DBR_CHAR array: a DBF_STRING field becomes a char array of field_size elements, a link field a char array of PVLINK_STRINGSZ, and every other field type is rejected with S_dbLib_fieldNotFound. pvxs serves that char view as a form = "String" long-string NTScalar — it reads the DBR_CHAR bytes and NUL-terminates them back into a string (ioc/iocsource.cpp:133-136, ioc/channel.cpp:62-74).

Both DBF_STRING fields and link fields resolve to an EpicsValue::String in this database (a link resolves to its textual form, see Self::get_common_field), so a field is $-eligible exactly when it resolves to a string value. Returns that string value for an eligible field, or None for a field the $ modifier cannot view as a char array (the S_dbLib_fieldNotFound case) — the single owner of the dbChannel $-eligibility rule for the channel-resolution layer.

Source

pub fn declared_field_type(&self, field: &str) -> Option<DbFieldType>

The DBF_* type field is SERVED as — the single source of truth for the type on the wire, on every delivery path.

This is the field’s DECLARED type (FieldDesc::dbf_type, from the .dbd), not the type of whatever variant the record happens to store. C resolves a channel’s field_type from the dbFldDes at name-resolution time (dbChannelCreate -> dbNameToAddr, dbAccess.c:184-205) and every later dbGet/db_post_events converts the stored bytes to it — the storage is private to the record, the declaration is the contract.

Two answers are NOT the declaration:

  • a FieldDesc::runtime_typed field — C’s cvt_dbaddr overwrites paddr->field_type from record state (FTVL, FTA, SDEF), and this port’s cvt_dbaddr is the variant the record stores;
  • a field with no FieldDesc at all (a virtual field).

In both cases the value’s own type is the answer, so this returns None and Self::project_to_declared_type leaves the value alone.

Source

pub fn project_to_declared_type( &self, field: &str, value: EpicsValue, ) -> EpicsValue

Project a field’s stored value onto its declared type (Self::declared_field_type) — the single owner of “what type this field goes on the wire as”, run by the CA create-channel path (Self::client_field_value), the GET path (Self::snapshot_for_field) and the MONITOR path (Self::make_monitor_snapshot), so all three announce and serve the same type.

The projection is EpicsValue::convert_to, the one value-coercion owner — the same routine dbGet converts through. Never re-derive a conversion here: C picks its routine from BOTH the source and the destination type, and only convert_to knows that table.

Idempotent: a value already of its declared type is short-circuited by convert_to, and re-projecting a projected value is a no-op. That is what lets the CA path derive the native type from the value it is about to serve.

Source

pub fn client_field_value(&self, field: &str) -> Option<EpicsValue>

The client-facing value of field: the resolved value projected onto the field’s declared type (Self::project_to_declared_type), so a native type derived from the value — which is what the CA create-channel path does — is the DECLARED type, and matches the GET/MONITOR data byte for byte.

Source

pub fn snapshot_for_field(&self, field: &str) -> Option<Snapshot>

Build a Snapshot with full metadata for the given field — for a field no link backs.

A link-backed field answers None here on purpose. Its metadata has to be resolved from the target record, which needs a PvDatabase and, because the port has one lock per record instead of C’s per-lock-set recursive mutex, has to happen with no record lock held. That is PvDatabase::channel_snapshot_for_field, and it is the only entry point that can serve one. Answering None rather than a seeded snapshot is what makes a caller that reached for the wrong door serve nothing instead of something stale.

Source

pub fn snapshot_for_field_with( &self, field: &str, backing: LinkBacking<'_>, ) -> Option<Snapshot>

Self::snapshot_for_field with the link metadata the caller resolved for this build. PvDatabase is the intended caller; see LinkBacking.

Source

pub fn channel_field_value( &self, field: &str, string_view: bool, ) -> Option<EpicsValue>

The value a channel bound to field serves, through the $ view the channel was bound with.

dbChannelCreate decides the view ONCE, at bind time (dbChannel.c:486-505), and every delivery path then reads through the dbChannel it produced; this is that single read. Callers must not re-derive it: resolving the bare field name answers “yes” for VAL whatever its type, so a path that does drops the eligibility half of the view entirely and admits REC.VAL$ on a DBF_DOUBLE.

None is S_dbLib_fieldNotFound: the record has no such field, or $ was applied to a field that cannot be re-viewed as a character array (see Self::resolve_string_view_field).

Source

pub fn channel_snapshot_for_field( &self, field: &str, string_view: bool, backing: LinkBacking<'_>, ) -> Option<Snapshot>

Self::snapshot_for_field_with through the same $ view as Self::channel_field_value — the metadata is the field’s either way, only the value is re-viewed.

This is the _with variant deliberately: the view decides the VALUE, backing decides the METADATA, and the two are independent. A caller that has resolved a LinkBacking passes it straight through, so a link-backed $ member keeps its target’s units/precision.

Source

pub fn property_support_for_field(&self, field: &str) -> PropertySupport

The property mask a channel on field supplies, without building a snapshot — what a PVA server needs to decide which NT leaves it may MARK for a channel it has not read yet (QSRV resolves a group’s member masks once, at monitor start, rather than per event).

Same two gates, same owner as Self::assign_property_support: an unknown field supplies nothing.

Source

pub fn get_common_field(&self, name: &str) -> Option<EpicsValue>

Get a common field value.

Source

pub fn put_common_field( &mut self, name: &str, value: EpicsValue, ) -> CaResult<CommonFieldPutResult>

Set a common field value from a runtime dbPut (CA/PVA/dbpf/link). Returns what scan index changes are needed.

A DBF_MENU common field’s string is converted by C’s runtime converter, dbConvert.c::putStringMenu — see MenuBound::DbPut.

Source

pub fn set_scan(&mut self, new_scan: ScanType) -> CommonFieldPutResult

The single owner of a record’s SCAN transition — C dbPutField on SCAN, which is scanDelete(precord)scanAdd(precord) (dbAccess.c::dbPutSpecial SPC_SCAN, dbScan.c:236-248).

Two callers reach it, and they are the two C sites that move a record between scan lists: a SCAN put (Self::put_common_field) and the simulation-mode scan swap (recGblCheckSimm, recGbl.c:427-437, which calls exactly the same scanDelete/scanAdd pair). Returns the delta for the scan-index owner (PvDatabase::update_scan_index) to apply once the record lock is down; CommonFieldPutResult::NoChange when the scan did not move.

Source

pub fn rec_gbl_save_simm(&mut self)

C recGblSaveSimm (recGbl.c:421-425) — latch the CURRENT simulation mode into OLDSIMM:

void recGblSaveSimm(const epicsEnum16 sscn,
    epicsEnum16 *poldsimm, const epicsEnum16 simm) {
    if (sscn == USHRT_MAX) return;
    *poldsimm = simm;
}

The only writer of CommonFields::oldsimm. Must run BEFORE the SIMM value moves — C calls it from special(SPC_MOD) pass 0 (before the put) and from recGblGetSimm/recGblInitSimm before the SIML read. The sscn == 65535 guard is C’s: with SSCN unset there is no scan to swap to, so the latch is not even taken (and Self::rec_gbl_check_simm bails on the same test, so the stale OLDSIMM is never read).

A record type with no SSCN/OLDSIMM in its C dbd (busy, swait) passes neither pointer to any recGbl helper: no-op here.

Source

pub fn rec_gbl_check_simm(&mut self) -> CommonFieldPutResult

C recGblCheckSimm (recGbl.c:427-437) — on a SIMM transition, swap the record’s SCAN with SSCN:

void recGblCheckSimm(struct dbCommon *pcommon, epicsEnum16 *psscn,
    const epicsEnum16 oldsimm, const epicsEnum16 simm) {
    if (*psscn == USHRT_MAX) return;
    if (simm != oldsimm) {
        epicsUInt16 scan = pcommon->scan;
        scanDelete(pcommon);
        pcommon->scan = *psscn;
        scanAdd(pcommon);
        *psscn = scan;
    }
}

This is what makes SSCN mean anything at all: a record configured field(SCAN,"1 second") field(SSCN,"Passive") stops periodic scanning the moment SIMM leaves NO, and resumes it when SIMM goes back — with the two fields having traded places each time. Both are a genuine swap, not an assignment: SSCN ends up holding the scan the record just left.

The only writer of the SIMM-driven SCAN/SSCN swap. The scan-list move itself goes through the single SCAN owner Self::set_scan, whose CommonFieldPutResult the caller hands to PvDatabase::update_scan_index once the record lock is down. Runs AFTER the SIMM value moved — C special(SPC_MOD) pass 1, and the tail of recGblGetSimm/recGblInitSimm.

Source

pub fn put_ackt(&mut self, value: u16, backing: LinkBacking<'_>)

C dbAccess.c::putAckt (:1285-1300) — the only writer of ACKT.

Reached from dbPut for a DBR_PUT_ACKT request type (dbAccess.c:1331-1332), ABOVE the SPC_NOMOD gate that refuses every ordinary put to the field. Posts exactly what C posts: the ACKT change, the ACKS it may lower, and the record-wide DBE_ALARM — and only when ackt actually changed (C returns 0 early otherwise).

Source

pub fn put_acks(&mut self, value: u16, backing: LinkBacking<'_>)

C dbAccess.c::putAcks (:1302-1315) — the only runtime writer of ACKS. Reached from dbPut for a DBR_PUT_ACKS request type, ABOVE the SPC_NOMOD gate.

The acknowledged severity is compared against the STORED unacknowledged severity acks, not the current sevr: an operator acknowledging at the severity that was latched into ACKS clears it even after sevr has since dropped. A too-low acknowledgement changes nothing and posts nothing; an acknowledgement of an already-clear ACKS still posts, which is C’s literal if (*psev >= precord->acks) (0 >= 0 holds).

Source

pub fn put_common_field_db_load( &mut self, name: &str, value: EpicsValue, ) -> CaResult<CommonFieldPutResult>

Set a common field value from the .db loader, which in C is a different converter with a different out-of-menu bound (dbStaticRun.c::dbPutStringNum; see MenuBound::DbLoad). It is what lets field(SSCN,"65535") — the menuScan “use SCAN” sentinel, out of the menu’s 0-9 range — load, while caput REC.SSCN 65535 is refused at runtime exactly as C refuses it.

Source

pub fn get_virtual_field(&self, name: &str) -> Option<EpicsValue>

Get virtual fields (NAME, RTYP).

Source

pub fn evaluate_alarms(&mut self)

Evaluate alarms based on record type and current value. Uses rec_gbl_set_sevr to accumulate into nsta/nsev.

CALC_ALARM is NOT raised here. C raises it inside the record’s own process() (calcRecord.c:121-123, calcoutRecord.c:238-241, sCalcoutRecord.c:357-363, aCalcoutRecord.c:304-305, swaitRecord.c:409-410), and in the port Record::check_alarms — which runs immediately before this — is that owner. It used to be raised here instead, keyed on a hardcoded rtype list plus a CALC_ALARM pseudo-field no DBD declares; swait is what that construction cost: it carried the flag but was not on the list, so a failed calcPerform alarmed nowhere.

Source

pub fn process_local( &mut self, ) -> CaResult<(ProcessSnapshot, Vec<(&'static str, EventMask)>)>

Basic process: process record, evaluate alarms, timestamp, build snapshot. This does NOT handle links — see process_with_context in database.rs.

Returns the value/log snapshot plus a list of alarm-field posts (SEVR/STAT/AMSG/ACKS) with their individual C event masks. SEVR is posted DBE_VALUE only; STAT/AMSG carry DBE_ALARM (sevr/amsg change) and/or DBE_VALUE (stat change). The caller must fire these via notify_field so a DBE_VALUE-only .SEVR subscriber is not missed on an alarm-only change and a DBE_ALARM-only subscriber is not wrongly notified — C parity with recGblResetAlarms (recGbl.c:202-222), matching the processing.rs link path.

Source

pub fn check_deadband_ext(&mut self) -> (bool, bool)

Source

pub fn make_monitor_snapshot( &self, field: &str, value: EpicsValue, backing: LinkBacking<'_>, ) -> Snapshot

Build a Snapshot for a given value, populated with the record’s display metadata and the link metadata the poster resolved for this batch. Uses the metadata cache so the populate cost is paid at most once per metadata-stable interval (cf. cached_metadata).

There is deliberately no backing-less form. One existed, defaulting to LinkBacking::none, and it made “nothing was resolved” the thing a caller says by saying nothing — which is how the DBE_PROPERTY sweep came to post CALC.A with the calc’s own precision (see link_backed_metadata_is_read_live.rs). A caller with nothing to resolve still writes LinkBacking::none(), and then it is a claim a reviewer can see and check.

The monitor path reaches the same one consumer the GET path does (finish_field_snapshot -> route_field_metadata), so it carried the same defect: measured on the wire, a camonitor -s on a calc’s A after caput TARGET.PREC 4 with the source never processed printed 5.0 where C printed 5.0000. The resolve cannot happen here — the post runs with the record’s own lock held — so the caller that owns the process/put cycle resolves it at a point where no lock is held and hands it in.

Source

pub fn notify_from_snapshot( &self, snapshot: &ProcessSnapshot, backing: LinkBacking<'_>, )

Notify subscribers from a snapshot (call outside lock). Each entry carries its own posting mask: only subscribers whose mask intersects that field’s mask are notified, and the delivered MonitorEvent reports that intersection — C db_post_events(prec, &field, mask) per-field granularity, then pLog->mask = caEventMask & pevent->select per subscriber.

backing is the link metadata the process cycle resolved for this batch, at its own no-lock-held point. See Self::make_monitor_snapshot for why it has no default.

Source

pub fn notify_field(&mut self, field: &str, mask: EventMask)

Notify subscribers of a specific field, filtering by event mask.

The last wrapper that still answers for its callers: none() here is a claim that no caller of this function names a link-backed field, and it is made once for 25 production call sites rather than at each of them. Self::notify_field_backed is the form for a caller that cannot make that claim.

Source

pub fn notify_field_backed( &mut self, field: &str, mask: EventMask, backing: LinkBacking<'_>, )

Self::notify_field for a poster that may name a link-backed field and has resolved its backing.

Source

pub fn notify_record_alarm(&mut self, backing: LinkBacking<'_>)

C db_post_events(precord, NULL, DBE_ALARM): post a record-wide alarm event. Delivers to every subscriber on any field whose mask includes DBE_ALARM, each carrying its own monitored field’s current value (the per-field notify_field already filters by mask intersection). Used by the alarm-acknowledge (ACKT/ACKS) put path so an alarm-mask monitor on any field observes the acknowledgement.

Source

pub fn notify_field_with_origin( &mut self, field: &str, mask: EventMask, origin: u64, backing: LinkBacking<'_>, )

Notify subscribers with an origin tag for self-write filtering.

This is C db_post_events(precord, pfield, mask) for one field, and — per the last_posted contract — the poster that advances the already-published value when mask carries a value class. Taking &mut self is what makes that unbypassable: there is no way to publish a field’s value through the framework without the change detector learning that it was published.

backing is the link metadata the put path resolved for this post, at its own no-lock-held point. See Self::make_monitor_snapshot for why it has no default.

Source

pub fn add_subscriber( &mut self, field: &str, sid: u32, data_type: DbFieldType, mask: u16, ) -> Option<EventReader>

Add a subscriber for a specific field. Returns None when the per-field subscriber cap (EPICS_CAS_MAX_SUBSCRIBERS_PER_PV) is reached. the parallel cap on ProcessVariable defends against a misbehaving client opening many MONITOR ops against one shared PV; the same defence is needed for record fields, which the CA server’s ChannelTarget::RecordField path lands on.

Source

pub fn add_subscriber_on( &mut self, user: &EventUser, field: &str, sid: u32, data_type: DbFieldType, mask: u16, ) -> Option<EventReader>

Add a field subscriber whose events queue on user’s event queue — C db_add_event with the circuit’s event_user as context. Every subscription on one CA circuit shares that queue and therefore its nDuplicates, so a duplicate queued for one of them releases the EVENTS_OFF drain for all of them (dbEvent.c:947). In-process consumers use Self::add_subscriber, which gives each its own event_user.

Source

pub fn attach_filter_to_last_subscriber( &mut self, field: &str, filter: Arc<dyn SubscriptionFilter>, ) -> bool

Attach a filter to the most recently added subscriber for field. Returns false when no subscriber exists yet on that field (call add_subscriber first). The CA / PVA channel-name parsers will use this once .{filter:opts} syntax is wired. Tests can also use it directly to compose filter chains.

Source

pub fn remove_subscriber(&mut self, sid: u32)

Remove a subscriber by subscription ID from all fields.

Source

pub fn is_destroyed(&self) -> bool

Whether Self::destroy has run.

Source

pub fn set_subscriber_active(&mut self, sid: u32, active: bool)

Pause / resume one subscriber’s event flow at the source (db_event_disable / db_event_enable). active == false suppresses every subsequent post to this subscriber, so the record stops doing per-event work for it. Entries already queued stay queued and are still delivered, exactly as in C: db_event_disable only unlinks the subscription from the record’s monitor list (dbEvent.c:524-535) and never reaches into the event queue. No-op if no subscriber has this sid. The caller holds the record write lock, so this is exclusive with the read-locked post paths that consult Subscriber::active.

Source

pub fn cleanup_subscribers(&mut self)

Clean up subscriber rows whose consumer is gone.

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> 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, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

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