Skip to main content

ProcessVariable

Struct ProcessVariable 

Source
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

Source

pub fn new(name: String, initial: EpicsValue) -> Self

Source

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.

Source

pub fn metadata(&self) -> PvMetadata

Snapshot (clone) of the installed shadow metadata; empty (Default) for a plain local PV.

Source

pub fn set_access_hook(&self, hook: AccessHook)

Install an access hook. Replaces any previously installed hook.

Source

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.

Source

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.

Source

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.

Source

pub fn set_write_hook(&self, hook: WriteHook)

Install a write hook. Replaces any previously-installed hook.

Source

pub fn clear_write_hook(&self)

Remove any installed write hook.

Source

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.

Source

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

Source

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

Source

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.

Source

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.

Source

pub fn set(&self, new_value: EpicsValue)

Set a new value and notify all subscribers.

Source

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

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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

Source

pub fn remove_subscriber(&self, sid: u32)

Remove a subscriber by subscription ID.

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 = 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