EPICS PVAccess Semantics

EPICS v4 Working Group, Working Draft, 21-Nov-2013

Latest version:
monitor.html
This version:
pvAccessSemantics_20131121.html
Previous version:
None
Editors:
Marty Kraimer BNL
Matej Sekoranja, CosyLab

Abstract

This document describes the recommended semantics for code that implements a ChannelProvider. This means all of the following interfaces: ChannelProvider, Channel, ChannelGet, ChannelPut, ChannelPutGet, ChannelArray, ChannelRPC, and Monitor. It also describes the interface ChannelAccess. These semantics are implemented by the following:

pvAccessJava and pvAccessCPP
These have the same semantics. They provide network access to a server, i.e. they implement remote providers.
pvIOCJava and pvDatabaseCPP
These have the similar semantics. They implement a local provider.

Implementations for other data sources should provide similar semantics.

This product is available via an open source license

This product are all part of the V4 implementation of Experimental Physics and Industrial Control System.

Status of this Document

This is the 21-Nov-2013 version of the the description of pvAccess semantics.

This is an alpha version of this documentation. The next step is for Matej to provide descriptions for things that Marty did not know how to describe. These are places that start with "???Matej". Unless others have generic comments, they should wait to give specific comments until Matej has a chance to edit this documemt.

Table of Contents

Introduction

Brief Overview

PVAccess is network support for PVData.

This document describes the semantics for the following:

PVAccess
ChannelAccess
A singleton class that for registering channel providers and for locating providers.
???Matej This name should be changed. Perhaps to PVAccess?
ClientFactory
Creates context for client.
ServerFactory
Creates contect for servers.
ChannelProvider
A channel provider finds and created channels.
Channel
A channel creates some combination of channelGet, ..., monitor.
ChannelGet
Get data from server.
ChannelPut
Put data to server.
ChannelPutGet
Put data, process, and get data from server as synchronous operation.
ChannelArray
Get/put a sub-array.
ChannelRPC
Like ChannelPutGet but for each request the introspaction interface for both the put and get can change.
Monitor
Monitor data changes in the server.

The description is somewhat language independent but code fragments are shown with Java syntax. The C++ implementation has similar semantics.

This document assumes some knowledge of:

pvData
This document assumes that the reader is familiar with PVData: pvDataJava
pvAccess
It also assumes that the reader has some familiarity with pvAccess: pvAccessJava
pvAccess network protocol
A remote provider must implement the pvAccess network protocol. See: pvAccess_Protocol_Specification.html
pvRequest
The Channel create methods have an argument pvRequest. In the section that desctribes ChannelGet, ChannelPut, etc. there is a brief explaination about details of what each create method expects. But this requires that the reader have a understanding of how pvRequest works and how a client can create a pvRequest. A longer description of pvRequest is given in. pvRequest.html

Definitions

client
Code that calls the ChannelProvider and Channel methods.
provider
A provider is anything that implements ChannelProvider and all associated interfaces, e. g. Channel, ChannelGet, ..., Monitor. There are two types of provider:
local provider
The provider that communicates with a server to get and put data.
remote provider
The provider that accesses the server via the network. This is implemented by pvAccess itself. It consists of two components: client and server, The client side implements ChannelProvider by making network calls to the server side. If the pvAccess network protocol is used then the server side is a client of the local provider. For other network protocols, e. g. channel access, the server side depends on the protocol and the server implementation.
server
A server has the data accessed by clients. A server is accessed by the local provider. The server code makes it's data available as pvData.
PVDatabase and PVRecord
Both pvDatabaseCPP and pvIOCJava provide a complete local provider implementation Both also implement a PVDatabase, which consists of a set of PVRecords. The names of the records become the channel names for the local provider. The local provider creates top level PVStructures which hold copies of an arbitrary subset of the fields in a PVRecord. Data is transfered between the top level structures and the associated PVRecord. pvDatabaseCPP and pvIOCJava both implement the concept of smart records, i. e. code can be attached to a record and the client can ask a record to process.

Note that a client can connect either to a local or remote provider. Other than having to specify the provider name when calling channelAccess.getProvider("name") The client sees exactly the same API. If the client connects to the local provider than no network access is involved.

PVStructure Locking issues

Multiple Threads Requires Locking

pvAccess transfers data between a client and server and must take care of locking since multiple threads are involved. The pvAccess data is normally a top level PVStructure. A local provider transfers data between the PVStructure and a PVRecord. A remote provider transfers data between a PVStructure and the network.

remote provider

For all channel types except Monitor, i. e. ChannelGet,ChannelPut,...,ChannelRPC the client can only have one request at a time outstanding. For example ChannelGet. When the client issues a ChannelGet::get the client can not issue another ChannelGet::get until the first request completes. Thus the rules are simple. The remote provider owns the PVStructure between the time the ChannelGet::get is issued and the remote provider calls ChannelGetRequester::getDone. The client owns the PVStructure at all other times.

The remote provider must have a mutex that it uses to synchronize access to many of it's methods. For example ChannelGet must synchronize access to get and getDone. Moinitors are more complex because data may arrive while the client is using previously returned data. In this case the remote provider keeps a bounded queue of montor elements. At any given time the server transfer data from the network to a monitor element it owns, and the client owns a single monitor element. In order to make this work the queue must have a size of at least two. More details about monitors are given below.

local provider

The semantics are similer to those for remote provider except now the data is tranfered between a PVStructure and a PVRecord. This is always done with the record locked.

PVAccess

This section describes singleton classes that are implemented by PVAccess itself. They should not be extended or re-implemented except, possibly, for another language, for example C#. Languages like python or mathlab should only provide wrappers for the C++ or Java implementation.

ChannelAccess

ChannelAccess is implemented by pvAccess itself, i. e. there is a single implementation. It is not meant to be extended. ChannelAccess supports an arbitrary number of ChannelProviders but each must have a unique name. When a process starts each provider must register itself with ChannelAccess. Once a provider is registered then clients can connect to the provider by calling ChannelAccess. In Java the following class is provided:

public class ChannelAccessFactory {
    public static ChannelAccess getChannelAccess();
    public static void registerChannelProviderFactory(ChannelProviderFactory channelProviderFactory);
    public static void unregisterChannelProviderFactory(ChannelProviderFactory channelProviderFactory);
}
where
getChannelAccess
Get the singleton class that implements ChannelAccess
registerChannelProviderFactory
Register a channelProvider
unregisterChannelProviderFactory
Unregister a channelProvider

For pvAccessCPP the three methods are external functions.

In Java the class definition for ChannelAccess is:

interface ChannelAccess {
    ChannelProvider getProvider(String providerName);
    ChannelProvider createProvider(String providerName);
    String[] getProviderNames();
}
where
getProvider
This is called to get a ChannelProvider.
createProvider
This is an internal detail of how providers are started when a process starts.
???Matej needs better description.
getProviderNames
Get all the provider names.

The following providers are currently supported:

pva
Communicates with server via pvAccess protocol.
ca
Communicates with server via the channel access protocol as defined by epics base,
rpcService
Communicate with a server via the channelRPC interface.
local
Provides access to the PVDatabase This is implemented on both pvIOCJava and pvDatabaseCPP.
pvaSrv
This makes iocCore records accessable via the pvAccess network protocol. This can be run on any ioc based on iocCore.

???Matej pvAccessJava has name caV3 instead of ca.

The pvAccess client and server each need a context, i. e. threads. These are discussed in the next two sections.

ClientFactory

In Java the context is created and terminated by the following methods:

    ClientFactory.start();  // before any client calls are issued
    ... // until process terminates
    ClientFactory.stop(); // This will destroy all objects created by ClientFactory

In C++ the context is created and terminated by the following methods:

    ClientFactory::start(); // before any client calls are issued.
    ... // until process terminates
    ClientFactory::stop(); // This will destroy all objects created by ClientFactory

ServerFactory

In Java the context is created and terminated by the following methods:

    ... // until process terminates
    ??? 

???Matej

How can server be rundown and destroyed?
Why is this not like C++

In C++ the context is created and terminated by the following methods:

    ServerContext::shared_pointer pvaServer =
        startPVAServer(PVACCESS_ALL_PROVIDERS,0,true,true);
    ... // until process terminates
    pvaServer->shutdown();
    epicsThreadSleep(1.0);
    pvaServer->destroy();

The C++ description of startPVAServer is:

extern ServerContext::shared_pointer startPVAServer(
        epics::pvData::String const & providerNames = PVACCESS_ALL_PROVIDERS,
        int timeToRun = 0,
        bool runInSeparateThread = false,
        bool printInfo = false);
where
providerNames
A String that has a comma separated set of provider names. The value "<all>" means all registered providers.
timeToRun
How long to run. A value of 0 means run forever.
runInSeparateThread
Should server be run in separate thread or under the thread that call startPVAServer.
printInfo
Should information about the server side of the network connection be displayed.

???Matej

timeToRun
This is an int Why?
epicsThreadSleep
How long to sleep? Why is this needed?

ChannelProvider

Client Interface

As mentioned in the previous section several implementations of ChannelProvider already exist. Providers for other data sources can also be implemented. The only requirment is a complete implementation of the ChannelProvider and Channel interfaces. Each implementation implements some combination of ChannelGet, ChannelPut, ChannelPutGet, ChannelArray, ChannelRPC, and Monitor. These all optional. For all that are not implemented the corresponding create method in Channel returns a Status saying that it is not implemented.

The ChannelProvider interface is:

interface ChannelProvider {
    void destroy();
    String getProviderName();
    ChannelFind channelFind(
        String channelName,
        ChannelFindRequester channelFindRequester);
    Channel createChannel(
        String channelName,
        ChannelRequester channelRequester,
        short priority);
    Channel createChannel(
        String channelName,
        ChannelRequester channelRequester,
        short priority,
        String address);
}

interface ChannelFind {
     ChannelProvider getChannelProvider();
     void cancelChannelFind();
}

ChannelProvider has the following methods:

destroy
This will destroy all objects created by the channelProvider.
getProviderName
Get an array of the provider names.
channelFind
???Matej Is ChannelFind necessary?
createChannel
Create a Channel.

The arguments for the above methods are:

channelName
The name of the channel.
channelFindRequester
See below
channelRequester
See below.
priority
???Matej provide description
address
???Matej provide description

A client that calls createChannel must implement interface ChannelRequester:

interface ChannelRequester extends Requester {
    void channelCreated(Status status, Channel channel);
    void void channelStateChange(Channel channel, ConnectionState connectionState);
}
where
channelCreated
Called when a connection to the server is created or if a server for the channelName is not found. The status indicates success or the reason for failure. channel is what was returned by createChannel.
channelStateChange
Called everytime the connection state changes, i. e. whenever a connect or disconnect occurs. connectionState is one of NEVER_CONNECTED, CONNECTED, DISCONNECTED, DESTROYED.

A client that calls channelFind must implement interface ChannelFindRequester:

interface ChannelFindRequester {
    void channelFindResult(Status status,ChannelFind channelFind,boolean wasFound);
}
where
channelFindResult

???Matej Again is this necessary>

Semantics

The channel provider must have access to a list of unique channelNames ( unique within the local network). For the local provider (pvIOCJava and pvDatabaseCPP) this is the set of recordNames in the local PVDatabase. The remote provider does a network broadcast to find a server that has the channelName.

When the client calls createChannel and the implementation can determine immediately if the channel exists (local providers normally can) then it can synchronously handle the request. If the channelName does not exists it calls ChannelRequester::channelCreated with a error status and returns null. If the channelName does exist it creates a new Channel, calls ChannelRequester::channelCreated with an OK status, and returns the newly created Channel.

If the implementation can not immediatly determine if the channelName exists (a remote provider can not unless a client has already connected to the channelName) then it creates a new Channel, does not call ChannelRequester::channelCreated, and returns the newly created Channel. The newly created Channel will at a later time call ChannelRequester::channelCreated reporting success or failure.

???Matej is the following true? If false please describe correct semantics.

The client must not call any of the channel methods until channelCreated is called with a successful status.

Channel

The Java interface is:

interface Channel extends Requester{
    void destroy();
    String getRequesterName();
    void message(String message, MessageType messageType);
    ChannelProvider getProvider();
    String getRemoteAddress();
    ConnectionState getConnectionState();
    String getChannelName();
    ChannelRequester getChannelRequester();
    boolean isConnected();
    void getField(GetFieldRequester requester,String subField);
    AccessRights getAccessRights(PVField pvField);
    ChannelProcess createChannelProcess(
        ChannelProcessRequester channelProcessRequester,
    	PVStructure pvRequest);
    ChannelGet createChannelGet(
        ChannelGetRequester channelGetRequester,
        PVStructure pvRequest);
    ChannelPut createChannelPut(
        ChannelPutRequester channelPutRequester,
        PVStructure pvRequest);
    ChannelPutGet createChannelPutGet(
        ChannelPutGetRequester channelPutGetRequester,
        PVStructure pvRequest);
    ChannelArray createChannelArray(
        ChannelArrayRequester channelArrayRequester,
        PVStructure pvRequest);
    ChannelRPC createChannelRPC(
        ChannelRPCRequester channelRPCRequester,
        PVStructure pvRequest);
    Monitor createMonitor(
        MonitorRequester monitorRequester,
        PVStructure pvRequest);

This has the methods:

destroy
This will destroy all objects created by this Channel. This means all channelGet, ..., Monitor objects. The remote provider will destroy the network connection after sending a destroy message to the remote server. After this call all code connected with the channel should make a best effort to no longer make use of the channel but also be prepared for callbacks that may occur because of race conditions caused by multiple threads and, for a remote provider, the network.
getRequesterName
???Matej give description.
message
???Matej give description.
getProvider
get the ChannelProvider that created the Channel.
getRemoteAddress
get the remote address of the Channel. A local provider will return "local"
getConnectionState
Get the current connection state. This is one of NEVER_CONNECTED, CONNECTED, DISCONNECTED, DESTROYED.
getChannelName
Get the name of this channel.
getChannelRequester
Get the ChannelRequester that created the Channel.
isConnected
Is the connection state CONNECTED.
getField
Get the introspection interface for the specified fieldName. The fieldName can "" or of the form "name.name...".
getAccessRights
Access Rights are not currently supported.
createChannelProcess
This will be described when ChannelProcess is described. If ChannelProcess is not supported then null is returned.
createChannelGet
This will be described when ChannelGet is described. If ChannelGet is not supported then null is returned.
createChannelPut
This will be described when ChannelPut is described. If ChannelPut is not supported then null is returned.
createChannelPutGet
This will be described when ChannelPutGet is described. If ChannelPutGetrocess is not supported then null is returned.
createChannelArray
This will be described when ChannelArray is described. If ChannelArray is not supported then null is returned.
createChannelRPC
This will be described when ChannelRPC is described. If ChannelRPC is not supported then null is returned.
createMonitor
This will be described when ChannelMonitor is described. If Monitor is not supported then null is returned.

ChannelProcess

Creating a ChannelProcess

A ChannelProcess is created via a call to Channel::createChannelProcess, which returns a ChannelProcess and has the API:

ChannelProcess createChannelProcess(
       ChannelProcessRequester channelProcessRequester,
	PVStructure pvRequest);
channelProcessRequester
See next section
pvRequest
current implementations ignore this argument. It is OK to just call createRequest with an argument of "".

Client Interface

ChannelProcessRequester

The client must implement the following:

interface ChannelProcessRequester extends Requester{
    void channelProcessConnect(Status status,ChannelProcess channelProcess);
    void processDone(Status status);
}
where
channelProcessConnect
This is called if the createChannelProcess fails or when the client request is connected to the server.
status
The result of the createChannelProcess request. Unless the status is OK then the client must not call the channelProcess methods.
channelProcess
The interface for the ChannelProcess.
processDone
This is called when a process request completes. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work.

ChannelProcess

The following is the ChannelProcess interface. All methods are called by the client.

    interface ChannelProcess extends Destroyable {
    void process(boolean lastRequest);
    void destroy()
}

where

process
Request that the server process. lastRequest is true if this is the last process request. The client must not make another process request until ChannelProcessRequester::processDone is called.
destroy
Destroy the channelProcess. This terminates the connection to the server. Client and implementation should make the best effort to no longer make any use of the channelProcess but both must be aware that methods could still be called because of timing issues.

Life Cycle

The life cycle for channelProcess is:

createChannelProcess        client
channelProcessConnect       provider
repeat indefinitely
    process                 client
    processDone             provider
destroy                     client or implementation

ChannelGet

Creating a ChannelGet

A ChannelGet is created via a call to Channel::createChannelGet, which returns a ChannelGet and has the API:

ChannelGet createChannelGet(
       ChannelGetRequester channelGetRequester,
       PVStructure pvRequest);
channelGetRequester
See next section
pvRequest
The argument to createRequest has the form "record[options]field(fields)". If "" is given it is that same as "field(value)" Most implementations will accept a record option "process=value" where value is true or false with the default being false. fields specifies the set of fields desired. See the description of pvRequest for details. Most implementation ignore all field options.

Client Interface

ChannelGetRequester

The client must implement the following:

interface ChannelGetRequester extends Requester {
    void channelGetConnect(
        Status status,
        ChannelGet channelGet,
        PVStructure pvStructure,
        BitSet bitSet);
    void getDone(Status status);
}
where
channelGetConnect
This is called if the createChannelGet fails or when the client request is connected to the server.
status
The result of the createChannelGet request. If status is not success than the pvStructure and bitSet arguments are null. Unless the status is OK then the client must not call the channelGet methods.
channelGet
The interface for the ChannelGet.
pvStructure
The PVStructure that the implementation creates. It has the data returned for each successful get request. The implementation owns this while a get request is outstanding and the client otherwise.
bitSet
This shows which fields have changed value since the last get request. The first get will show that all fields have changed, i. e. bitSet will have the value {0} which says the the top level structure has changed. The implementation owns this while a get request is outstanding and the client otherwise.
getDone
This is called when a get request completes. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work.

ChannelGet

The following is the ChannelGet interface. All methods are called by the client.

    interface ChannelGet extends Destroyable {
    void get(boolean lastRequest);
    void destroy();
}

where

get
Get data from that the server. lastRequest is true if this is the last process request. The client must not make another get request until ChanneGetsRequester::getDone is called.
destroy
Destroy the channelGet. This terminates the connection to the server. Client and implementation should make the best effort to no longer make any use of the channelGet but both must be aware that methods could still be called because of timing issues.

Life Cycle

The life cycle for channelGet is:

createChannelGet        client
channelGetConnect       provider
repeat indefinitely
    get                 client
    getDone             provider
destroy                 client or implementation

ChannelPut

Creating a ChannelPut

A ChannelPut is created via a call to Channel::createChannelPut, which returns a ChannelPut and has the API:

ChannelPut createChannelPut(
       ChannelPutRequester channelPutRequester,
       PVStructure pvRequest);
channelPutRequester
See next section
pvRequest
The argument to createRequest has the form "record[options]field(fields)". If "" is given it is that same as "field(value)" Most implementations will accept a record option "process=value" where value is true or false with the default being true. fields specifies the set of fields desired. See the description of pvRequest for details. Most implementation ignore all field options.

Client Interface

ChannelPutRequester

The client must implement the following:

interface ChannelPutRequester extends Requester {
    void channelPutConnect(
       Status status,
       ChannelPut channelPut,
       PVStructure pvStructure,
       BitSet bitSet);
    void putDone(Status status);
    void getDone(Status status);
}
where
channelPutConnect
This is called if the createChannelPut fails or when the client request is connected to the server.
status
The result of the createChannelPut request. If status is not success than the pvStructure and bitSet arguments are null. Unless the status is OK then the client must not call the channelPut methods.
channelPut
The interface for the ChannelPut.
pvStructure
The PVStructure that the implementation creates. It hold the data returned by each successful put request. The implementation owns this while a get request is outstanding and the client otherwise.
bitSet
This shows which fields have changed value since the last put request. The client must set bits for all fields that it wants to change in the next put request. A call to bitSet.set(0) sets the value to {0}, which says the the top level structure has changed. This means to send all fields. The implementation owns this while a get request is outstanding and the client otherwise.
putDone
This is called when a put request completes. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work.
getDone
This is called when a get request completes. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work. This method does not change the value of bitSet.

ChannelPut

The following is the ChannelPut interface. All methods are called by the client.

interface ChannelPut extends Destroyable {
    void put(boolean lastRequest);
    void get();
    void destroy();
}

where

put
Put data from that the server. lastRequest is true if this is the last process request. The client must not make another put or get request until ChannePutRequester::putDone is called.
get
Get the current data from the server. This request will never process. The client must not make another put or get request until ChannePutRequester::getDone is called.
destroy
Destroy the channelPut. This terminates the connection to the server. Client and implementation should make the best effort to no longer make any use of the channelPut but both must be aware that methods could still be called because of timing issues.

Life Cycle

The life cycle for channelPut is:

createChannelPut        client
channelPutConnect       provider
repeat indefinitely
    at most one of
       put
          put           client
          putDone       provider
       get
          get           client
          getDone       provider
destroy                 client or implementation

ChannelPutGet

Creating a ChannelPutGet

A ChannelPutGet is created via a call to Channel::createChannelPutGet, which returns a ChannelPutGet and has the API:

ChannelPutGet createChannelPutGet(
       ChannelPutGetRequester channelPutGetRequester,
       PVStructure pvRequest);
channelPutGetRequester
See next section
pvRequest
The argument to createRequest has the form "record[options]putField(fields)getField(fields)". The recommended convention is "putField(argument)getField(result)".
Most implementations will accept a record option "process=value" where value is true or false with the default being true. fields specifies the set of fields desired. There is one set for the data to send to the server and another for the data returned from the server. See the description of pvRequest for details. Most implementation ignore all field options.
???Marty make "" mean "putField(argument)getField(result)".

Client Interface

ChannelPutGetRequester

The client must implement the following:

interface ChannelPutGetRequester extends Requester {
    void channelPutGetConnect(
        Status status,
        ChannelPutGet channelPutGet,
        PVStructure pvPutStructure,
        PVStructure pvGetStructure);
    void putGetDone(Status status);
    void getPutDone(Status status);
    void getGetDone(Status status);
}
where
channelPutGetConnect
This is called if the createChannelPutGet fails or when the client request is connected to the server.
status
The result of the createChannelPutGet request. If status is not success than the pvStructure arguments are null. Unless the status is OK then the client must not call the channelPutGet methods.
channelPutGet
The interface for the ChannelPutGet.
pvPutStructure
The pvStructure that the implementation creates. It has data to send to the server. It also has the data returned by a getPut request. The implementation owns the data while a request is outstanding and the client otherwise.
pvGetStructure
The pvStructure that the implementation creates. It has the data returned as the result of a putGet request. It also has the data returned by a getGet request. The implementation owns the data while a request is outstanding and the client otherwise.
putGetDone
This is called when a putGet request completes. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work.
getPutDone
This is called when a getPut request completes. The pvPutStructure has the returned data. This is called by the client to get the current put data from the server. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work.
getGetDone
This is called when a getGet request completes. The pvGetStructure has the returned data. This is called by the client to get the current get data from the server. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work.

ChannelPutGet

The following is the ChannelPutGet interface. All methods are called by the client.

interface ChannelPutGet extends Destroyable {
    void putGet(boolean lastRequest);
    void getPut();
    void getGet();
    void destroy();
}

where

putGet
Put and get data to/from the server. lastRequest is true if this is the last process request. The client must not make another putGet or getPut or getGet request until ChannePutGetRequester::putGetDone is called.
getPut
Get the current data for pvPutStructure from the server. This request will never process. The client must not make another putGet or getPut or getGet request until ChannePutGetRequester::getPutDone is called.
getGet
Get the current data for pvGetStructure from the server. This request will never process. The client must not make another putGet or getPut or getGet request until ChannePutGetRequester::getGetDone is called.
destroy
Destroy the channelPutGet. This terminates the connection to the server. Client and implementation should make the best effort to no longer make any use of the channelPutGet but both must be aware that methods could still be called because of timing issues.

Life Cycle

The life cycle for channelPutGet is:

createChannelPutGet        client
channelPutGetConnect       provider
repeat indefinitely
    at most one of
       putGet
          putGet           client
          putGetDone       provider
       getPut
          getPut           client
          getPutDone       provider
       getGet
          getGet           client
          getGetDone       provider
destroy                    client or implementation

ChannelArray

Creating a ChannelArray

A ChannelArray is created via a call to Channel::createChannelArray, which returns a ChannelArray and has the API:

ChannelArray createChannelArray(
       ChannelArrayRequester channelArrayRequester,
       PVStructure pvRequest);
channelArrayRequester
See next section
pvRequest
The argument to createRequest should be "fieldName" where fieldName is on the form field.field.... Specifing "" is the same as "value". The field selected must be an array field or an error status will be returned by channelArrayConnect.
???Marty make it also Ok to specify "value(fieldName)"

Client Interface

ChannelArrayRequester

The client must implement the following:

interface ChannelArrayRequester extends Requester {
    void channelArrayConnect(
        Status status,
        ChannelArray channelArray,
        PVArray pvArray);
    void putArrayDone(Status status);
    void getArrayDone(Status status);
}
where
channelArrayConnect
This is called if the createChannelArray fails or when the client request is connected to the server.
status
The result of the createChannelArray request. If status is not success than the pvArray argument is null. Unless the status is OK then the client must not call the channelArray methods.
channelArray
The interface for the ChannelArray.
pvArray
The PVArray that the implementation creates. It has the data to send to the server and the data returned from the server. The implementation owns the data while a request is outstanding and the client otherwise.
putArrayDone
This is called when a putArray request completes. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work.
getArrayDone
This is called when a getArray request completes. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work.
setLengthDone
This is called when a setLength request completes. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work.

ChannelArray

The following is the ChannelArray interface. All methods are called by the client.

interface ChannelArray extends Destroyable {
    void putArray(boolean lastRequest, int offset, int count);
    void getArray(boolean lastRequest, int offset, int count);
    void setLength(boolean lastRequest, int length, int capacity);
    void destroy();
}

where

putArray
Modify a sub-array of the server. No processing is involved. The client must not make another request until ChanneArrayRequester::putArrayDone is called.
getArray
Get a sub-array from the server. No processing is involved. The client must not make another request until ChanneArrayRequester::getArrayDone is called.
setLength
Set the length and capacity of the server array. The client must not make another request until ChanneArrayRequester::setLengthDone
???Marty On C++ the arguments should be size_t. This requires a change in the pva network protocol.
destroy
Destroy the channelArray. This terminates the connection to the server. Client and implementation should make the best effort to no longer make any use of the channelArray but both must be aware that methods could still be called because of timing issues.

Life Cycle

The life cycle for channelArray is:

createChannelArray           client
channelArrayConnect          provider
repeat indefinitely
    at most one of
       putArray
          putArray           client
          putArrayDone       provider
       getArray
          getArray           client
          getArrayDone       provider
       setLength
          setLength          client
          setLengthDone      provider
destroy                      client or implementation

ChannelRPC

Creating a ChannelRPC

A ChannelRPC is created via a call to Channel::createChannelRPC, which returns a ChannelRPC and has the API:

ChannelRPC createChannelRPC(
       ChannelRPCRequester channelRPCRequester,
       PVStructure pvRequest);
channelRPCRequester
See next section
pvRequest
Server implementation dependent. Most current implementations ignore this argument. If so it is OK to just call createRequest with an argument of "".
??? Is this true?

Client Interface

ChannelRPCRequester

The client must implement the following:

interface ChannelRPCRequester extends Requester {
    void channelRPCConnect(Status status,ChannelRPC channelRPC);
    void requestDone(Status status,PVStructure pvResponse);
}
where
channelRPCConnect
This is called if the createChannelRPC fails or when the client request is connected to the server.
status
The result of the createChannelRPC request. If status is not success than the other arguments are null. Unless the status is OK then the client must not call the channelRPC methods.
channelRPC
The interface for the ChannelRPC.
requestDone
The request is done. This is always called with no locks held. The pvResponse is the data returned by the server. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work.

ChannelRPC

The following is the ChannelRPC interface. All methods are called by the client.

interface ChannelRPC extends Destroyable {
    void request(PVStructure pvArgument,boolean lastRequest);
    void destroy();
}

where

request
Send a request to the server. pvArgument is the data to send. lastRequest is true if this will be the last request. The server will always process. The client must not make another request until ChanneRPCRequester::requestDone is called.
destroy
Destroy the channelRPC. This terminates the connection to the server. Client and implementation should make the best effort to no longer make any use of the channelRPC but both must be aware that methods could still be called because of timing issues.

Life Cycle

The life cycle for channelRPC is:

createChannelRPC           client
channelRPCConnect          provider
repeat indefinitely
    request                client
    requestDone            provider
destroy                    client or implementation

Monitor

Creating a monitor

A monitor is created via a call to Channel::createMonitor, which has the API:

Monitor createMonitor(
    MonitorRequester MonitorRequester,
    PVStructure pvRequest);
where
MonitorRequester
See next section
pvRequest
The pvRequest options. For the call to createRequest the string has the form
record[options]field(fields)
The possible record options are:
queueSize=size
The size for the monitorElement queue. The default is 2. If a value of less than 2 is specified that it will become 2.
periodicRate=rate
This is supported by the local provider in pvIOCJava. It means to post monitors at a maximum rate specified by rate.
provider specific
A provider may support additional options.
field specifies the subset of the fields the client wants to monitor. An individual field can specify options if the server honors them. For example the local provider for pvIOCJava supports the options:
algorithm=name
Specifies the name of an algorithm for determining when monitors are issued. Depending on the algorithm there may be additions options.

Client Interface

MonitorRequester

The client must implement the following:

interface MonitorRequester extends Requester{
    void monitorConnect(Status status,Monitor monitor,Structure structure);
    void monitorEvent((Monitor monitor);
    void unlisten();
}
where
monitorConnect
This is called if the createMonitor fails or when the client request is connected to the server.
status
The result of the createMonitor request. If status is not success than the other arguments are null. Unless the status is OK then the client can not call the monitor methods.
monitor
The interface for the Monitor.
structure
The introspection interface for the data that will be returned for each monitor event.
monitorEvent
This is called when a monitor occurs. The client must call monitor.poll to get data for the monitor. See below. The client must not block in this method. If it needs to perform an operation that will block it should arrange for another thread the do the work. It is OK to call poll and release from this method.
unlisten
The server has issued an unlisten request. The client should make a best effort to stop using the monitor. The implementation must realize, however, that the client may make some calls between this method being called and the client cleaning up.

Monitor

The following is the Monitor interface. All methods are called by the client.

interface Monitor extends Destroyable {
    Status start();
    Status stop();
    MonitorElement poll();
    void release(MonitorElement monitorElement);
}

where

start
Start monitoring. If status.isOK is true then the request is successful. Another status means either that the caller has made an illegal call or the monitor is in an invalid state. For example the channel may have disconnected.
The client should not call this until monitorConnect has been called with a successful status. The implementation will, however, be prepared for illegal calls.
stop
Stop monitoring. If status.isOK is true then the request is successful. Another status means either that the caller has made an illegal call or the monitor is in an invalid state. For example the channel may have disconnected.
The client should not call this until start has been called with a successful status. After stop is issued the client should not call poll until it has again called start. The implementation will, however, be prepared for illegal calls.
poll
Poll for monitor event. Null is returned when no more events are available. The client must call this method in order to get monitor data.
The client must call release for the monitorElement returned by poll before making another call to poll. The implementation must not modify the monitorElement between calls to poll and release.
release
When the client has processed the monitor event returned by poll the client must call release before again calling poll. It is OK for the client to block between calls to poll and release with the realization that it may miss events.

Life Cycle

The life cycle for monitors is:

createMonitor                           client
monitorConnected                        provider
start                                   client
    repeat indefinitely
        monitorEvent                    provider
        repeat until null is returned
            poll                        client
            client specific
            release                     client
    stop or destroy                     client

MonitorElement

A monitor element has three fields:

pvStructure
changedBitSet
overrunBitSet

where

pvStructure
The data structure.
changedBitSet
A bitset which has a bit set for each field of the data structure which has changed since the last monitor.
overrunBitSet
A bitset which has a bit set for each field of the data structure which has changed more than once since the last monitor.

The monitorElement belongs to the client between calls to poll and release, otherwise it belongs to provider.

Implementation

The most important guarantee is that a monitorElement must not be modfied by the implementation between client calls to poll and release. But the provider might receive new data between the calls so it must have somewhere to save the data. It is OK if overruns occur but the changedBitSet and overrunBitSet must reflect the latest new data.

Except for specialized implementations like a Periodic Monitor, which are not discussed in this section, the implementation must keep a bounded queue of monitorElements. At any given time at most one of the elements belongs to the client. The implementation must use another element for new data. Thus a monitorElement queue of at least two monitorElements must be created.

The client provider and local provider have similar implementations. The main difference is that the local provider receives new data via method dataChanged and the client via method response. Thus the following methods are involved:

dataChanged or response
newly arrived data. This must not modify a monitorElement that belongs to the client. If free monitor elements are available then a new monitorElement should be taken from the queue and used.
poll
client call to get a monitorElement
release
client call to release monitorElement returned by call to poll

Important: The above methods must all take a lock that uses a common mutex.

The following applies to both client a local providers and, except for syntax, both Java and C++. If a channel provider is created for other data sources it should have similar semantics.

The Monitor implementation , call it MonitorImp, creates an auxillary class named MultipleElementQueue. This class extends the Monitor interface but only takes care of the monitorElement queue. The MonitorImp, calls the methods of MultipleElementQueue.

MultipleElementQueue has the following private varibles:

monitorQueue
This is a bounded queue of monitorElements. pvData provides the implementation of a bounded queue with the proper semantics.
changedBitSet
overrunBitSet
An implementation creates one of each. For the local implementation these are the bitSets given to pvCopy. For the remote implementation they are used to deserialize the bitSets that are received over the network.
queueIsFull
If this is true when new data arrives then the new data is used to update latestMonitorElement. If it is false when new data arrives a free monitorElement is taken from the queue. If after getting a free element there are no more free queue elements queueIsFull is set true and the latestMonitorEement is set equal to the newly acquired monitorElement.
latestMonitorEement
The monitorElement into which new data is stored if queueIsFull is true.

The Monitor methods are implemented as follows:

start
Clears the changed and overrun bitSets. Sets queuIsFull false. The local implementation calls PVCopyMonitor::startMonitoring.
stop
Nothing to do.
poll
The Java implementation for remote client is:
public MonitorElement poll()
{
    synchronized(this) {
      	return monitorQueue.getUsed();
    }
}
release
The Java implementation for remote client is:
public void release(MonitorElement currentElement)
{
    synchronized(this) {
        if(queueIsFull) {
             MonitorElement monitorElement = latestMonitorElement;
             PVStructure pvStructure = monitorElement.getPVStructure();
             bitSetUtil.compress(monitorElement.getChangedBitSet(),pvStructure);
             bitSetUtil.compress(monitorElement.getOverrunBitSet(),pvStructure);
             queueIsFull = false;
             latestMonitorElement = null;
        }
        monitorQueue.releaseUsed(currentElement);
    }
}
response or dataChanged
The Java implementation for remote client is:
public void response(Transport transport, ByteBuffer payloadBuffer)
{
    synchronized (this)
    {
        if (queueIsFull) {
            MonitorElement monitorElement = latestMonitorElement;
            PVStructure pvStructure = monitorElement.getPVStructure();
            changedBitSet.deserialize(payloadBuffer, transport);
            pvStructure.deserialize(
                 payloadBuffer,
                 transport,
                 changedBitSet);
            overrunBitSet.deserialize(payloadBuffer, transport);
            monitorElement.getChangedBitSet().or(changedBitSet);
            monitorElement.getOverrunBitSet().or(changedBitSet);
            changedBitSet.clear();
            overrunBitSet.clear();
            return;
        }
        MonitorElement monitorElement = monitorQueue.getFree();
        if(monitorElement==null) {
             throw new IllegalStateException(
                 "RealQueue::dataChanged() logic error");
        }
        if(monitorQueue.getNumberFree()==0){
             queueIsFull = true;
             latestMonitorElement = monitorElement;
        }
        PVStructure pvStructure = monitorElement.getPVStructure();
        changedBitSet.deserialize(payloadBuffer, transport);
        pvStructure.deserialize(
              payloadBuffer,
              transport,
              changedBitSet);
        overrunBitSet.deserialize(payloadBuffer, transport);
        bitSetUtil.compress(changedBitSet,pvStructure);
        bitSetUtil.compress(overrunBitSet,pvStructure);
        monitorElement.getChangedBitSet().clear();
        monitorElement.getChangedBitSet().or(changedBitSet);
        monitorElement.getOverrunBitSet().clear();
        monitorElement.getOverrunBitSet().or(overrunBitSet);
        changedBitSet.clear();
        overrunBitSet.clear();
        monitorQueue.setUsed(monitorElement);

   }
   // NOTE that monitorEvent is called with no lock held
   callback.monitorEvent(this);
}

Note that receive, poll, and release all synchronize, i. e. do not allow simultaneous access to each other. Since receive deserializes it may block waiting for network packets. Note also that receive calls monitorEvent with no lock held.