pub struct ProcessVariable {
pub name: String,
pub value: RwLock<EpicsValue>,
pub subscribers: PriorityInheritanceMutex<Vec<Subscriber>>,
/* private fields */
}Expand description
A process variable hosted by the server.
Fields§
§name: String§value: RwLock<EpicsValue>The stored value. A synchronous parking_lot::RwLock (matching the
sibling posted_meta / metadata / hook locks): every access is a
single-expression read-or-write with no .await held across the
guard, so the value-read path (get, snapshot) is pure lock work
with no reactor dependency — the sans-io READ path. The write side
(set / set_snapshot) still .awaits the monitor fan-out, but
drops this guard first.
subscribers: PriorityInheritanceMutex<Vec<Subscriber>>Monitor fan-out list — L7 of doc/rtems-priority-locks-design.md
§3.
A BLOCKING mutex, not the async one: every emission path runs from a
record-processing thread with the record’s advisory gate (L1) held, and
C’s db_post_events likewise takes evUser->lock from inside
dbScanLock (dbEvent.c::db_post_events). Holding an async mutex here
would put a suspension point inside that window. Every critical section
below is bounded list work (retain / push / sub.post), with no
I/O and no .await inside it.
Specifically a PriorityInheritanceMutex rather than a plain
parking_lot::Mutex, because evUser->lock is an epicsMutex and on
the RTEMS arm every epicsMutex is a PTHREAD_PRIO_INHERIT pthread
mutex (os/posix/osdMutex.c:71-88, compiled for RTEMS via
os/RTEMS-posix/osdMutex.c:8). It is taken from banded IOC threads on
both sides — the emitting record-processing thread and a CAS-client
thread running remove_subscriber — so a plain mutex here reintroduces
the inversion L1 was converted to remove. Off the PI targets this is
parking_lot::Mutex, i.e. exactly what it was.
A leaf of the acquisition order (record_lock.rs module doc): no other
lock is taken while it is held.
Implementations§
Source§impl ProcessVariable
impl ProcessVariable
pub fn new(name: String, initial: EpicsValue) -> Self
Sourcepub fn set_metadata(&self, metadata: PvMetadata)
pub fn set_metadata(&self, metadata: PvMetadata)
Install (or replace) the shadow DBR_GR_/DBR_CTRL_/enum
metadata served on this PV’s snapshots. Used by the CA / PVA
gateway after fetching the upstream IOC’s control metadata. See
PvMetadata. To publish the change to downstream property
monitors, follow with Self::post_property.
Sourcepub fn metadata(&self) -> PvMetadata
pub fn metadata(&self) -> PvMetadata
Snapshot (clone) of the installed shadow metadata; empty
(Default) for a plain local PV.
Sourcepub fn set_access_hook(&self, hook: AccessHook)
pub fn set_access_hook(&self, hook: AccessHook)
Install an access hook. Replaces any previously installed hook.
Sourcepub fn access_hook(&self) -> Option<AccessHook>
pub fn access_hook(&self) -> Option<AccessHook>
Snapshot of the installed access hook (clone of the Arc), or
None. Consulted by the CA server’s compute_access; cheap and
non-async, like Self::write_hook.
Sourcepub fn set_read_hook(&self, hook: ReadHook)
pub fn set_read_hook(&self, hook: ReadHook)
Install a read hook. Replaces any previously-installed hook.
Used by the CA gateway’s no-cache mode so each downstream GET is
served by a fresh upstream fetch. See ReadHook.
Sourcepub fn read_hook(&self) -> Option<ReadHook>
pub fn read_hook(&self) -> Option<ReadHook>
Snapshot of the installed read hook (clone of the Arc), or
None. Cheap and non-async, like Self::write_hook: the read
lock is released before the cloned Arc returns, so the caller’s
subsequent await on the hook holds no lock.
Sourcepub fn set_write_hook(&self, hook: WriteHook)
pub fn set_write_hook(&self, hook: WriteHook)
Install a write hook. Replaces any previously-installed hook.
Sourcepub fn clear_write_hook(&self)
pub fn clear_write_hook(&self)
Remove any installed write hook.
Sourcepub fn write_hook(&self) -> Option<WriteHook>
pub fn write_hook(&self) -> Option<WriteHook>
Snapshot of the installed write hook (clone of the Arc), or
None if none. Used by the CA TCP write path; cheap and
non-async — the read lock is released before the cloned Arc
returns, so the caller’s subsequent await on the hook does
not hold any lock.
Sourcepub fn get(&self) -> EpicsValue
pub fn get(&self) -> EpicsValue
Get the current value.
Synchronous: a single-expression read-lock clone with no .await,
so the value-read path carries no reactor dependency (sans-io).
Sourcepub fn snapshot(&self) -> Snapshot
pub fn snapshot(&self) -> Snapshot
Build a Snapshot for this bare PV.
A ProcessVariable is a non-record-backed channel: it has no
alarm engine, no DESC/EGU/PREC metadata and no timestamp user
tag of its own. The snapshot is therefore value + NO_ALARM +
wall-clock now, with user_tag = 0. Display / control / enum
metadata is None unless a proxy installed it via
Self::set_metadata (the CA / PVA gateway shadowing an
upstream IOC) — see Self::apply_metadata. Record-backed
channels build their snapshot via
RecordInstance::snapshot_for_field, which carries the record’s
own alarm/metadata. The only path that injects a non-zero alarm
onto a bare PV is Self::post_alarm (used by the gateway
adapter to surface upstream disconnect).
Sourcepub async fn read_snapshot(&self) -> Result<Snapshot, CaError>
pub async fn read_snapshot(&self) -> Result<Snapshot, CaError>
Build the snapshot served on a one-shot client GET
(CA_PROTO_READ / CA_PROTO_READ_NOTIFY).
When a ReadHook is installed (the CA gateway’s no-cache mode),
the snapshot is fetched fresh through the hook — value and its
upstream alarm status/severity and IOC timestamp together — and the
shadow’s last-known property metadata (display/control/enum) is
overlaid for the fields a DBR_TIME_* event does not carry; on hook
error the failure propagates so the server can answer ECA_GETFAIL,
matching C ca-gateway forwarding each read to the IOC under
-no_cache (gateVc.cc:1361-1369, gatePv.cc:976/:1789-1794).
Without a hook this is exactly Self::snapshot wrapped in Ok,
so the GET path is unchanged for every record-backed and cached PV.
Only the GET path calls this; monitor fan-out, the initial monitor
event, and access-rights re-posts keep using Self::snapshot
(the stored value), so a no-cache PV still backs a downstream
monitor with its upstream subscription’s events rather than a
per-event upstream get.
Sourcepub fn read_snapshot_local(&self) -> Option<Snapshot>
pub fn read_snapshot_local(&self) -> Option<Snapshot>
Synchronous companion to Self::read_snapshot for the one-shot GET
path (CA_PROTO_READ / CA_PROTO_READ_NOTIFY).
Some(snapshot) when NO read hook is installed — the sans-io GET that
every record-backed and cached PV takes: Self::snapshot of the
stored value, produced with no .await and no reactor dependency.
None when a gateway no-cache ReadHook IS installed, whose hook()
is a genuine upstream network GET; the caller must then take the async
Self::read_snapshot instead. This keeps the hook / no-hook decision
in one owner, in lockstep with read_snapshot — the only difference is
that the async fallible upstream fetch is surfaced to the caller as
None rather than performed here.
Sourcepub fn set(&self, new_value: EpicsValue)
pub fn set(&self, new_value: EpicsValue)
Set a new value and notify all subscribers.
Sourcepub fn set_with_origin(&self, new_value: EpicsValue, origin: u64)
pub fn set_with_origin(&self, new_value: EpicsValue, origin: u64)
Self::set tagged with the writer’s origin: the value post carries
origin so an origin-aware consumer can recognise (and skip) the
writer’s own event — the simple-PV side of the
put_pv_and_post_with_origin self-write contract. Origin 0 is the
untagged default (never filtered).
Sourcepub fn set_snapshot(&self, snapshot: Snapshot)
pub fn set_snapshot(&self, snapshot: Snapshot)
Set value from a full snapshot (value + alarm + timestamp) and notify
all subscribers. Used by the CA gateway forwarding task to propagate
the upstream alarm status/severity and IOC timestamp to downstream
monitors. Mirrors gateVcData::setEventData + vcPostEvent in the
C ca-gateway: the incoming dbr_time_xxx GDD carries all three fields.
Sourcepub fn post_alarm(&self, severity: u16, status: u16)
pub fn post_alarm(&self, severity: u16, status: u16)
Push a fresh monitor event holding the current value but with the supplied alarm severity/status. Used by the PVA / CA gateway adapter to surface upstream-disconnect to downstream monitor subscribers without dropping the simple PV (which would force every downstream client into ECA_DISCONN + reconnect storms when the upstream is just briefly unreachable). Mirrors gatePvData::death’s “alarm-post” alternative discussed in the C++ ca-gateway audit.
Sourcepub async fn post_property(&self, snapshot: Snapshot)
pub async fn post_property(&self, snapshot: Snapshot)
Post a DBE_PROPERTY monitor event carrying the decoded upstream
CTRL event snapshot — its value plus the upstream status /
severity and timestamp — overlaid with the installed shadow
metadata, so downstream property-change monitors re-read the units /
precision / limits / enum labels with the upstream alarm state.
Used by the CA / PVA gateway when an upstream DBE_PROPERTY event
fires (metadata changed) after it has refreshed the shadow PV via
Self::set_metadata. The caller supplies the snapshot rather than
this method synthesising one: C ca-gateway decodes the upstream
DBR_CTRL_* callback and re-posts the value with setStatSevr()
status/severity preserved (gatePv.cc:2413-2438,
runValueDataCB), leaving the timestamp as the control DBR carries
none — it must NOT be replaced with a fresh NO_ALARM /
wall-clock-now snapshot just because metadata changed. Pass the
timestamp the upstream value carried (the control event has none of
its own); pass status/severity from the upstream CTRL payload.
Property events are a distinct class from value/alarm: only
DBE_PROPERTY subscribers receive them.
Sourcepub fn add_subscriber(
&self,
sid: u32,
data_type: DbFieldType,
mask: u16,
) -> Option<EventReader>
pub fn add_subscriber( &self, sid: u32, data_type: DbFieldType, mask: u16, ) -> Option<EventReader>
Add an in-process subscriber, attached to an event queue of its own.
C db_add_event(ctx, ...) puts a monitor on the queue chain of the
event_user (client) that owns it. An in-process consumer is its own
client, so it gets its own EventUser — nothing else shares its
queue, and flow control (a CA circuit concept) never engages on it.
The CA server, whose subscriptions DO share one circuit-wide queue, uses
Self::add_subscriber_on.
Sourcepub fn add_subscriber_on(
&self,
user: &EventUser,
sid: u32,
data_type: DbFieldType,
mask: u16,
) -> Option<EventReader>
pub fn add_subscriber_on( &self, user: &EventUser, sid: u32, data_type: DbFieldType, mask: u16, ) -> Option<EventReader>
Add a subscriber whose events are queued 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: a duplicate queued for one of them releases the
EVENTS_OFF drain for all of them (dbEvent.c:947).
Returns None when the per-PV subscriber cap is reached (defends
against a misbehaving client opening many MONITOR ops against one shared
PV; the per-channel cap limits channels but not subscriber rows on a
single PV). Operators override it via EPICS_CAS_MAX_SUBSCRIBERS_PER_PV.
Sourcepub fn attach_filters_to_subscriber(&self, sid: u32, filters: FilterChain)
pub fn attach_filters_to_subscriber(&self, sid: u32, filters: FilterChain)
attach a channel-filter chain to an already-added
subscriber (looked up by sid). The CA server first
add_subscribers, then attaches the chain parsed from the
channel’s .{...} suffix — symmetric with the record-field
RecordInstance::attach_filter_to_last_subscriber path, so a
SimplePv monitor runs the SAME filter chain as a record-field
monitor instead of the empty default FilterChain that
add_subscriber installs. Update delivery
(Self::notify_subscribers / Self::post_alarm) already
applies sub.filters; this is the missing wiring that populates
it.
The caller passes a FRESH chain per subscriber so stateful
filters (dbnd last-value, dec counter, sync state) stay
isolated across subscribers. An empty chain is a no-op (keeps the
default). No-op when no subscriber matches sid (e.g. it was
reaped between add and attach).
Sourcepub fn remove_subscriber(&self, sid: u32)
pub fn remove_subscriber(&self, sid: u32)
Remove a subscriber by subscription ID.