pvAccessCPP  7.1.2
ChannelProvider API

Table of Contents

The epics::pvAccess namespace.

See pv/pvAccess.h header.

#include <pv/configuration.h>
#include <pv/pvAccess.h>
#include <pv/clientFactory.h>

Roles

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

Operation and Requester

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.

Note
An exception to this is epics::pvAccess::ChannelProvider::createChannel() Where a epics::pvAccess::ChannelRequester may be omitted.

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.

provider_roles_requester_locking

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.

shared_ptr<> and Ownership

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

Uniqueness

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.

dot_ownership.png
shared_ptr relationships

struct MyClient {
epics::pvAccess::Channel::shared_ptr channel;
};
struct MyChannelRequester : public epics::pvAccess::ChannelRequester
{
std::tr1::weak_ptr<MyClient> client;
virtual void channelStateChange(Channel::shared_pointer const & channel, Channel::ConnectionState connectionState) {
std::tr1::shared_ptr<MyClient> C(client.lock());
if(!C) return;
...
}
};

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.

Client Role

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.

#include <pv/pvAccess.h>
#include <pv/clientFactory.h>
// add "pva" to registry
// create a new client instance.
epics::pvAccess::ChannelProvider::shared_pointer prov;
prov = epics::pvAccess::getChannelProviderRegistry()->createProvider("pva");
// createProvider() returns NULL if the named provider hasn't been registered
if(!prov)
throw std::runtime_error("PVA provider not registered");

Client Channel

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.

Note
The returned Channel does not hold a strong reference for the ChannelProvider from which it was created. User code must keep a reference to the provider as long as Channels are in use. All Channels are automatically closed when their provider is destroyed.

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

Client Operations

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.

Operation Lifetime and (dis)connection

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.

Executing an Operation

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

Monitor Operation

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.

Note
The pvAccess::MonitorRequester::monitorEvent() is called from a server internal thread which may be shared with other operations. In order to avoid delaying other channels/operations it is recommended to use monitorEvent() as notification for a client specific worker thread where poll() and release() are called.

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.

Warning
It is critical that any non-NULL MonitorElement returned by poll() must be passed to release(). Failure to do this will result in a resource leak and possibly stall the monitor. See epics::pvAccess::Monitor::Stats::noutstanding

epics::pvAccess::Monitor::getStats() can help diagnose problems related to the Monitor FIFO. See epics::pvAccess::Monitor::Stats.

Client Ownership

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.

dot_client_ownership.png
Client implicit relationships

Client Examples

Server Role