346 lines
14 KiB
Plaintext
346 lines
14 KiB
Plaintext
#ifndef PROVIDERS_H
|
|
#define PROVIDERS_H
|
|
|
|
/**
|
|
@page providers ChannelProvider API
|
|
|
|
@tableofcontents
|
|
|
|
The epics::pvAccess namespace.
|
|
See pv/pvAccess.h header.
|
|
|
|
@code
|
|
#include <pv/configuration.h>
|
|
#include <pv/pvAccess.h>
|
|
#include <pv/clientFactory.h>
|
|
@endcode
|
|
|
|
@section providers_roles Roles
|
|
|
|
The Client and Server APIs revolve around the epics::pvAccess::ChannelProvider class.
|
|
|
|
In the following discussion the @ref providers_client calls methods of ChannelProvider and associated classes.
|
|
The @ref providers_server 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()
|
|
|
|
@subsection provider_roles_requester Operation and Requester
|
|
|
|
The classes associated with ChannelProvider come in pairs.
|
|
eg.
|
|
|
|
- epics::pvAccess::Channel and epics::pvAccess::ChannelRequester
|
|
- epics::pvAccess::ChannelGet and epics::pvAccess::ChannelGetRequester
|
|
- epics::pvAccess::ChannelPut and epics::pvAccess::ChannelPutRequester
|
|
- epics::pvAccess::Monitor and epics::pvAccess::MonitorRequester
|
|
- epics::pvAccess::ChannelRPC and epics::pvAccess::ChannelRPCRequester
|
|
- epics::pvAccess::ChannelProcess and epics::pvAccess::ChannelProcessRequester
|
|
- epics::pvAccess::ChannelPutGet and epics::pvAccess::ChannelPutGetRequester
|
|
- epics::pvAccess::ChannelArray and epics::pvAccess::ChannelArrayRequester
|
|
|
|
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.
|
|
|
|
@subsubsection provider_roles_requester_locking
|
|
|
|
Operations methods may call requester methods, and vis versa.
|
|
The following rules must be followed to avoid deadlocks.
|
|
|
|
- No locks must be held when Requester methods are called.
|
|
- Locks may be held when Operation methods are called.
|
|
|
|
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.
|
|
|
|
- Channel::createChannelGet()
|
|
- ChannelGetRequester::channelGetConnect()
|
|
- ChannelGet::get()
|
|
- ChannelGetRequester::getDone()
|
|
|
|
Thus care should be taken when calling ChannelGet::get() from within ChannelGetRequester::getDone()
|
|
to avoid infinite recursion.
|
|
|
|
@subsection providers_ownership 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.
|
|
|
|
@subsubsection providers_ownership_unique 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.
|
|
|
|
@dotfile ownership.dot "shared_ptr relationships"
|
|
|
|
- A box denotes a class implemented by user code
|
|
- An oval denotes a class not implemented by user code
|
|
- A red line is a shared_ptr<>
|
|
- A green line is a weak_ptr<>
|
|
- A dashed line indicates a relationship which is outside the control of user code
|
|
|
|
@code
|
|
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;
|
|
...
|
|
}
|
|
};
|
|
@endcode
|
|
|
|
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.
|
|
|
|
@section providers_client 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.
|
|
|
|
@code
|
|
#include <pv/pvAccess.h>
|
|
#include <pv/clientFactory.h>
|
|
// add "pva" to registry
|
|
epics::pvAccess::ClientFactory::start();
|
|
// 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");
|
|
@endcode
|
|
|
|
@subsection providers_client_channel 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 @ref providers_ownership_unique).
|
|
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()
|
|
).
|
|
|
|
@subsection providers_client_operations 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.
|
|
|
|
- epics::pvAccess::Channel::createChannelGet()
|
|
- epics::pvAccess::Channel::createChannelPut()
|
|
- epics::pvAccess::Channel::createMonitor()
|
|
- epics::pvAccess::Channel::createChannelRPC()
|
|
- epics::pvAccess::Channel::createChannelPutGet()
|
|
- epics::pvAccess::Channel::createChannelProcess()
|
|
- epics::pvAccess::Channel::createChannelArray()
|
|
|
|
The created Operation is unique (see @ref providers_ownership_unique).
|
|
|
|
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.
|
|
|
|
- epics::pvAccess::ChannelGetRequester::channelGetConnect()
|
|
- epics::pvAccess::ChannelPutRequester::channelPutConnect()
|
|
- epics::pvAccess::MonitorRequester::monitorConnect()
|
|
- epics::pvAccess::ChannelRPCRequester::channelRPCConnect()
|
|
- epics::pvAccess::ChannelPutGetRequester::channelPutGetConnect()
|
|
- epics::pvAccess::ChannelProcessRequester::channelProcessConnect()
|
|
- epics::pvAccess::ChannelArrayRequester::channelArrayConnect()
|
|
|
|
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.
|
|
|
|
@subsubsection providers_client_operations_lifetime 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.
|
|
|
|
@subsubsection providers_client_operations_exec Executing an Operation
|
|
|
|
After an Operation becomes ready/connected an additional step is necessary to request data.
|
|
|
|
- epics::pvAccess::ChannelGet::get()
|
|
- epics::pvAccess::ChannelPut::get()
|
|
- epics::pvAccess::ChannelPut::put()
|
|
- epics::pvAccess::ChannelRPC::request()
|
|
- epics::pvAccess::ChannelPutGet::putGet()
|
|
- epics::pvAccess::ChannelPutGet::getPut()
|
|
- epics::pvAccess::ChannelPutGet::getGet()
|
|
- epics::pvAccess::ChannelProcess::process()
|
|
- epics::pvAccess::ChannelArray::putArray()
|
|
- epics::pvAccess::ChannelArray::getArray()
|
|
- epics::pvAccess::ChannelArray::getLength()
|
|
- epics::pvAccess::ChannelArray::setLength()
|
|
|
|
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::ChannelGetRequester::getDone()
|
|
- epics::pvAccess::ChannelPutRequester::getDone()
|
|
- epics::pvAccess::ChannelPutRequester::putDone()
|
|
- epics::pvAccess::ChannelRPCRequester::requestDone()
|
|
- epics::pvAccess::ChannelPutGetRequester::putGetDone()
|
|
- epics::pvAccess::ChannelPutGetRequester::getPutDone()
|
|
- epics::pvAccess::ChannelPutGetRequester::getGetDone()
|
|
- epics::pvAccess::ChannelProcessRequester::processDone()
|
|
- epics::pvAccess::ChannelArrayRequester::putArrayDone()
|
|
- epics::pvAccess::ChannelArrayRequester::getArrayDone()
|
|
- epics::pvAccess::ChannelArrayRequester::getLengthDone()
|
|
- epics::pvAccess::ChannelArrayRequester::setLengthDone()
|
|
|
|
@subsubsection providers_client_operations_monitor 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.
|
|
|
|
@subsection provides_client_ownership 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.
|
|
|
|
@dotfile client_ownership.dot Client implicit relationships
|
|
|
|
- Channel holds weak ref. to ChannelProvider
|
|
- ChannelProvider holds weak ref. to Channel
|
|
- Channel holds weak ref. to ChannelRequester
|
|
- Channel holds weak refs to all Operations
|
|
- Operations hold weak refs to the corresponding Requester, and Channel
|
|
|
|
@subsection provides_client_examples Client Examples
|
|
|
|
- @ref examples_getme
|
|
- @ref examples_monitorme
|
|
|
|
|
|
@section providers_server Server Role
|
|
*/
|
|
|
|
#endif /* PROVIDERS_H */
|