pvAccessCPP
7.1.7
|
The epics::pvAccess namespace.
See pv/pvAccess.h header.
The Client and Server APIs revolve around the epics::pvAccess::ChannelProvider class.
In the following discussion the Client Role calls methods of ChannelProvider and associated classes. The Server Role implements ChannelProvider and associated classes and is called by a client.
By convention, instances of ChannelProvider are registered and retrieved through one of epics::pvAccess::ChannelProviderRegistry::clients() or epics::pvAccess::ChannelProviderRegistry::servers()
The classes associated with ChannelProvider come in pairs. eg.
In the following discussions the term "Operation" refers to eg. Channel, ChannelGet, or similar while "Requester" refers to ChannelRequester, ChannelGetRequester, or similar.
The "Requester" classes are implemented by the Client role and called by the Server role to give notification to the client of certain events. For example, epics::pvAccess::ChannelRequester::channelStateChange() is called when a Channel becomes (dis)connected.
A "Requester" sub-class must be provided when each "Operation" is created. This Requester then becomes bound to the Operation.
For convenience each Operation class has a member typedef for it's associated Requester, and vis. versa. For example ChannelGet::requester_type is ChannelGetRequester and ChannelGetRequester::operation_type is ChannelGet.
Operations methods may call requester methods, and vis versa. The following rules must be followed to avoid deadlocks.
These rules place the burdon of avoiding deadlocks on the ChannelProvider implementation (Server role).
Clients must still be aware when some Operation methods can call some Requester methods recursively, and consider this when locking.
For example, the following call stack may legitimetly occur for a ChannelProvider to for a Get which accesses locally stored data.
Thus care should be taken when calling ChannelGet::get() from within ChannelGetRequester::getDone() to avoid infinite recursion.
"Operations" and "Requesters" are always handled via std::tr1::shared_ptr.
In the following dicussions an instance of std::tr1::shared_ptr is referred to as a "reference", specifically a strong reference. The associated std::tr1::weak_ptr is referred to as a weak reference.
shared_ptr instances can exist on the stack (local variables) or as struct/class members.
Situations where an object contains a reference to itself, either directly or indirectly, are known as "reference loops". As long as a reference loop persists, any cleanup of resources associated with the shared_ptr (s) involved will not be done. A reference loop which is never broken is called a "reference leak".
In order to avoid reference leaks, required relationships between various classes will be described, and some rules stated.
In discussing the usage of an API diagrams like the following will be used to illustrate roles and ownership requirements.
The distinction of what is "user" code will depend on context. For example, when discussing the Client role, epics::pvAccess::Channel will not be implemented by "user code". When discussing the Server role, user code will implement Channel.
A shared_ptr is said to be unique if it is the only strong reference to the underlying object. In this case shared_ptr::unique() returns true.
The general rule is that functions which create/allocate new objects using shared_ptr must yield a unique shared_ptr. Yielding a non-unique shared_ptr is a sign that an internal reference leak exists.
In this example user code implements a custom MyClient class and a sub-class of ChannelRequester in order to make use of a Channel. In order to avoid a reference loop, the sub-class of ChannelRequester uses a weak_ptr to refer to MyClient during channelStateChange() events.
A client will by configured with a certain provider name string. It will begin by passing this string to epics::pvAccess::ChannelProviderRegistry::createProvider() to obtain a ChannelProvider instance.
Custom configuration of provider can be accomplished by passing a epics::pvAccess::Configuration to createProvider(). A Configuration is a set of string key/value parameters which the provider may use as it sees fit.
If a Configuration is not provided (or NULL) then the provider will use some arbitrary defaults. By convention default Configuration use the process environment.
The primary (and in many cases only) ChannelProvider method of interest is epics::pvAccess::ChannelProvider::createChannel() from which a new epics::pvAccess::Channel can be obtained.
Each call to createChannel() produces a "new" std::shared_ptr<Channel> which is uniquely owned by the caller (see Uniqueness). As such, the caller must keep a reference to to the Channel or it will be destroyed. This may be done explicitly, or implicitly by storing a reference to an Operation.
A Channel can be created at any time, and shall succeed as long as the provided name and address are syntactically valid, and the priority is in the valid range.
When created, a Channel may or may not already be in the Connected state.
On creation epics::pvAccess::ChannelRequester::channelCreated will be called before createChannel() returns.
Notification of connection state changes are made through epics::pvAccess::ChannelRequester::channelStateChange() as well as through the *Connect() and channelDisconnect() methods of Requesters of any Operations on a Channel (eg. epics::pvAccess::MonitorRequester::channelDisconnect() ).
This section describes commonalities between the various Operation supported: Get, Put, Monitor, RPC, PutGet, Process, and Array.
An Operation is created/allocated with one of the create* methods of Channel. All behave in a similar way.
The created Operation is unique (see Uniqueness).
The *Connect() method of the corresponding Requester will be called when the Operation is "ready" (underlying Channel is connected). This may happen before the create* method has returned, or at some time later.
When the underlying Channel becomes disconnected or is destroyed, then the channelDisconnect() method of each Requester is called (eg. see epics::pvAccess::ChannelBaseRequester::channelDisconnect() ). All operations are implicitly cancelled/stopped before channelDisconnect() is called.
An Operation can be created at any time regardless of whether a Channel is connected or not. An Operation will remain associated with a Channel through (re)connection and disconnection.
After an Operation becomes ready/connected an additional step is necessary to request data.
Once one of these methods is called to execute an operation, none may be again until the corresponding completion callback is called, or the operation is cancel()ed (or epics::pvAccess::Monitor::stop() ).
epics::pvAccess::Monitor operations are handled differently than others as more than one subscription update may be delivered after start() is called.
During or after epics::pvAccess::MonitorRequester::monitorConnect() it is necessary to call epics::pvAccess::Monitor::start() to begin receiving subscription updates.
The epics::pvAccess::Monitor::poll() and epics::pvAccess::Monitor::release() methods access a FIFO queue of subscription updates which have been received. The epics::pvAccess::MonitorRequester::monitorEvent() method is called when this FIFO becomes not empty.
epics::pvAccess::MonitorRequester::unlisten() is called to indicate a subscription has reached a definite end without an error. Not all subscription sources will use this.
epics::pvAccess::Monitor::getStats() can help diagnose problems related to the Monitor FIFO. See epics::pvAccess::Monitor::Stats.
The following shows the implicit ownership in classes outside the control of client code, as well as the expected ownerships of of Client user code. "External" denotes references stored by Client objects which can't participate in reference cycles.