Merge lp:~matej-sekoranja/epics-base/pcas-variable-length-arrays into lp:~epics-core/epics-base/3.16

Proposed by Matej Sekoranja
Status: Rejected
Rejected by: Andrew Johnson
Proposed branch: lp:~matej-sekoranja/epics-base/pcas-variable-length-arrays
Merge into: lp:~epics-core/epics-base/3.16
Diff against target: 228 lines (+39/-26)
3 files modified
src/ca/legacy/pcas/generic/caHdrLargeArray.h (+1/-1)
src/ca/legacy/pcas/generic/casStrmClient.cc (+37/-24)
src/ca/legacy/pcas/generic/casStrmClient.h (+1/-1)
To merge this branch: bzr merge lp:~matej-sekoranja/epics-base/pcas-variable-length-arrays
Reviewer Review Type Date Requested Status
EPICS Core Developers Pending
Review via email: mp+256123@code.launchpad.net

Description of the change

This branch adds variable length array support to pcas. Changes are mostly trivial - just replace msgHeader’s count with actual getElementCount(). Fine for monitors, however
read and readNotify (and first monitor event, that calls read) need additional code in the casPV::read(const casCtx &, gdd & protoIn) method, the method pCAS programmer needs to implement.

In order to do a “read” operation a DBR is created first (protoIn variable) and then the value is then copied into the “protoIn”. “protoIn” GDD structure contains “bounds” structure that specifies array size. If “bounds” are not specified, then scalar value is assumed.
If “0” count is given by the CA client then the GDD created has no “bounds” specified and this implies a scalar GDD. Consequently “read” will always return only one element.

So, the implementation of casPV::read(const casCtx &, gdd & protoIn) needs to check if “bounds” exists and create one if necessary (getBounds() == 0 && element count > 1):

if (!protoIn.getBounds() && this->info.getElementCount() > 1)
{
    // convert to atomic
    gddBounds bds;
    bds.setSize ( this->info.getElementCount() );
    bds.setFirst ( 0u );
    protoIn.setDimension ( 1u, & bds );
}

This check cannot be done in casStrmClient.cc since only the casPV implementation knows that the value actually is.

I think this is acceptable design: if “bounds” is not provided then casPV::read implementation needs to set one (to reflect current value “bounds”) - as shown above.

To post a comment you must log in.
Revision history for this message
Andrew Johnson (anj) wrote :

What will happen if an unmodified application gets built against this version of the pcas code and a client subscribes (or does a ca_get_callback() ) to it with zero size?

This change will need documenting in the documentation/RELEASE_NOTES.html file. Since there isn't a document that describes the pcas API, all the detail of the necessary application changes should go there.

Revision history for this message
Matej Sekoranja (matej-sekoranja) wrote :

> What will happen if an unmodified application gets built against this version
> of the pcas code and a client subscribes (or does a ca_get_callback() ) to it
> with zero size?

get: only one element (scalar) will be returned
monitors: first event will return only one element (scalar), subsequent events will work as expected for "variable length array feature"

Revision history for this message
Andrew Johnson (anj) wrote :

Previously the CA client library converted monitor requests with zero size to mean all elements; the client knows the maximum array size since that information is sent out as part of the initial channel connection process. Would it be hard to change the pcas behavior so that an unmodified server application would send the max number of elements for the channel instead of just one, in both the get-callback and first monitor cases? I think that would be safer.

Revision history for this message
Matej Sekoranja (matej-sekoranja) wrote :

I need to get an indication whether casPV implementation support variable arrays or not.

I can create a method "virtual bool casPV::supports_variable_arrays() const { return false; }" with the default implementation as shown. If the method returns "false" then I will execute the following code before calling casPV::read :

if (!protoIn.getBounds() && this->info.getElementCount() > 1)
{
    // convert to atomic
    gddBounds bds;
    bds.setSize ( this->info.getElementCount() );
    bds.setFirst ( 0u );
    protoIn.setDimension ( 1u, & bds );
}

This will make code work as you've described above (using max number of elements for the PV when 0 count is requested).

OK to implement?

Revision history for this message
Andrew Johnson (anj) wrote :

Hi Matej,

I was hoping one of the other core developers with actual CAS experience would chime in on this question, but it doesn't look like anybody is going to, at least without some prompting. I've never written a full CAS application so I'm not ideally qualified to decide what the best adjustment of the API should be.

Here's my understanding of the issue:

Adding the variable-length-arrays feature to the CAS library should not change the behavior of an unmodified CAS application, although this branch currently does. To fix that, you are proposing adding a new virtual method to the casPV API that allows the library to ask each casPV object whether it supports variable length arrays. The default for an unmodified application would be no, in which case the CAS library will set up the protoIn gdd object that it passes into the application's PV::read() method for a zero-length read to request an array of the maximum size of the PV.

Would it be possible to always set the protoIn size to the max number of elements when handling a zero-length read? Presumably a casPV object's PV::read() method that supports variable length arrays can always make the array that it returns smaller than the request when there's less data available? If this works there should be no need for the additional method, although I admit it would reduce performance slightly (I don't think that bothers us though, we've been wanting to throw away GDD for a long time).

- Andrew

Revision history for this message
Ralph Lange (ralph-lange) wrote :

Why does the read() need to be changed in the first place?

If I remember correctly, when used with an rsrv (IOC) server, the variable length feature only applies to monitor updates. Reads and first replies always have the full size array.

Is there a requirement that the CAS implementation needs to support variable size arrays on read operations?

Revision history for this message
Andrew Johnson (anj) wrote :

No, ca_get_callback() also supports variable length arrays; ca_get() can't though because it has nowhere to return the data length. I suspect the read() operation gets called by the server library to fetch the data to be sent for monitor events as well as for get requests.

In any case, applying this change as written modifies the monitor behavior of an existing client. Start excas from a regular Base and do a camonitor on the alan PV. The first monitor event is the full length of the array (1000 elements IIRC). If you repeat that with excas built with this branch, the first monitor event is only a scalar.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> Would it be possible to always set the protoIn size to the max number of elements when handling a zero-length read?

I think this is the appropriate strategy. This way existing code would see this as a request for max size, which it can handle w/o modification. So I would propose to do the merge once this point is reached w/o any API changes. This way we can get the protocol change in place.

Revision history for this message
Bruce Hill (bhill) wrote :

Sorry I'm so late to this party. Murali asked me to take a look at this last
summer and I didn't like the need to change the PV::read() to avoid getting just
one element on the first read. Then I got busy with other stuff and forgot
about this merge proposal altogether.

I've been wanting to find a fix for the var array issue w/ our gateways for some
time and finally dug into it Monday and found a fix. I was going to push it
to launchpad only to rediscover this merge from Matej.

My initial fix was to clip the number of elements to the current array size
in casStrmClient.cc, but I couldn't figure out why m_count was still coming in
w/ the full array size even when I specified zero. Turns out I'd neglected to
bump up EPICS_PROTOCOL_MINOR_VERSION in caHdrLargeArray.h.

After comparing our solutions, which were nearly the same, I boiled it down
to the following fix which creates the gdd w/ the max array size when zero is
passed in, thus avoiding the problem of no bounds.

=== modified file 'src/ca/legacy/pcas/generic/casStrmClient.cc'
--- src/ca/legacy/pcas/generic/casStrmClient.cc 2015-04-01 07:29:16 +0000
+++ src/ca/legacy/pcas/generic/casStrmClient.cc 2016-07-14 08:59:50 +0000
@@ -2612,10 +2612,16 @@
 {
     const caHdrLargeArray * pHdr = this->ctx.getMsg();

+ ca_uint32_t count = pHdr->m_count;
+ if ( count == 0 ) {
+ if ( this->ctx.getChannel() ) {
+ count = this->ctx.getChannel()->getPVI().nativeCount();
+ }
+ }
+
     {
         gdd * pDD = 0;
- caStatus status = createDBRDD ( pHdr->m_dataType,
- pHdr->m_count, pDD );
+ caStatus status = createDBRDD ( pHdr->m_dataType, count, pDD );
         if ( status != S_cas_success ) {
             return status;
     }

I've submitted this as a merge proposal to Matej's branch so it can be
brought in through his branch if everyone agrees.

I've tested this w/ a gateway app and it always returns the actual array size
when m_count is set to zero.

Revision history for this message
Andrew Johnson (anj) wrote :

Superseded by Micheal's pcas-fake-dynamic branch.

Unmerged revisions

12662. By Matej Sekoranja

added support for variable array length to read/readNotify and addEvent

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/ca/legacy/pcas/generic/caHdrLargeArray.h'
2--- src/ca/legacy/pcas/generic/caHdrLargeArray.h 2010-10-05 19:27:37 +0000
3+++ src/ca/legacy/pcas/generic/caHdrLargeArray.h 2015-04-14 11:26:50 +0000
4@@ -32,7 +32,7 @@
5 # include "shareLib.h"
6 #endif
7
8-static const unsigned char CA_MINOR_PROTOCOL_REVISION = 12;
9+static const unsigned char CA_MINOR_PROTOCOL_REVISION = 13;
10
11 typedef ca_uint32_t caResId;
12
13
14=== modified file 'src/ca/legacy/pcas/generic/casStrmClient.cc'
15--- src/ca/legacy/pcas/generic/casStrmClient.cc 2014-05-30 17:36:50 +0000
16+++ src/ca/legacy/pcas/generic/casStrmClient.cc 2015-04-14 11:26:50 +0000
17@@ -388,7 +388,7 @@
18 //
19 // casStrmClient::verifyRequest()
20 //
21-caStatus casStrmClient::verifyRequest ( casChannelI * & pChan )
22+caStatus casStrmClient::verifyRequest ( casChannelI * & pChan, bool allowZeroCount )
23 {
24 const caHdrLargeArray * mp = this->ctx.getMsg();
25
26@@ -411,7 +411,8 @@
27 //
28 // element count out of range ?
29 //
30- if ( mp->m_count > pChan->getPVI().nativeCount() || mp->m_count == 0u ) {
31+ if ( mp->m_count > pChan->getPVI().nativeCount() ||
32+ ( !allowZeroCount && mp->m_count == 0u ) ) {
33 return ECA_BADCOUNT;
34 }
35
36@@ -444,7 +445,7 @@
37 casChannelI * pChan;
38
39 {
40- caStatus status = this->verifyRequest ( pChan );
41+ caStatus status = this->verifyRequest ( pChan, CA_V413 ( this->minor_version_number ) );
42 if ( status != ECA_NORMAL ) {
43 if ( pChan ) {
44 return this->sendErr ( guard, mp, pChan->getCID(),
45@@ -531,11 +532,15 @@
46 pChan->getCID(), status, ECA_GETFAIL );
47 }
48
49+ ca_uint32_t count = (msg.m_count == 0) ?
50+ (ca_uint32_t)desc.getDataSizeElements() :
51+ msg.m_count;
52+
53 void * pPayload;
54 {
55- unsigned payloadSize = dbr_size_n ( msg.m_dataType, msg.m_count );
56+ unsigned payloadSize = dbr_size_n ( msg.m_dataType, count );
57 caStatus localStatus = this->out.copyInHeader ( msg.m_cmmd, payloadSize,
58- msg.m_dataType, msg.m_count, pChan->getCID (),
59+ msg.m_dataType, count, pChan->getCID (),
60 msg.m_available, & pPayload );
61 if ( localStatus ) {
62 if ( localStatus==S_cas_hugeRequest ) {
63@@ -551,21 +556,21 @@
64 // (places the data in network format)
65 //
66 int mapDBRStatus = gddMapDbr[msg.m_dataType].conv_dbr(
67- pPayload, msg.m_count, desc, pChan->enumStringTable() );
68+ pPayload, count, desc, pChan->enumStringTable() );
69 if ( mapDBRStatus < 0 ) {
70 desc.dump ();
71 errPrintf ( S_cas_badBounds, __FILE__, __LINE__, "- get with PV=%s type=%u count=%u",
72- pChan->getPVI().getName(), msg.m_dataType, msg.m_count );
73+ pChan->getPVI().getName(), msg.m_dataType, count );
74 return this->sendErrWithEpicsStatus (
75 guard, & msg, pChan->getCID(), S_cas_badBounds, ECA_GETFAIL );
76 }
77 int cacStatus = caNetConvert (
78- msg.m_dataType, pPayload, pPayload, true, msg.m_count );
79+ msg.m_dataType, pPayload, pPayload, true, count );
80 if ( cacStatus != ECA_NORMAL ) {
81 return this->sendErrWithEpicsStatus (
82 guard, & msg, pChan->getCID(), S_cas_internal, cacStatus );
83 }
84- if ( msg.m_dataType == DBR_STRING && msg.m_count == 1u ) {
85+ if ( msg.m_dataType == DBR_STRING && count == 1u ) {
86 unsigned reducedPayloadSize = strlen ( static_cast < char * > ( pPayload ) ) + 1u;
87 this->out.commitMsg ( reducedPayloadSize );
88 }
89@@ -585,7 +590,7 @@
90 casChannelI * pChan;
91
92 {
93- caStatus status = this->verifyRequest ( pChan );
94+ caStatus status = this->verifyRequest ( pChan, CA_V413 ( this->minor_version_number ) );
95 if ( status != ECA_NORMAL ) {
96 return this->readNotifyFailureResponse ( guard, * mp, status );
97 }
98@@ -655,12 +660,16 @@
99 guard, msg, ECA_GETFAIL );
100 return ecaStatus;
101 }
102+
103+ ca_uint32_t count = (msg.m_count == 0) ?
104+ (ca_uint32_t)desc.getDataSizeElements() :
105+ msg.m_count;
106
107 void *pPayload;
108 {
109- unsigned size = dbr_size_n ( msg.m_dataType, msg.m_count );
110+ unsigned size = dbr_size_n ( msg.m_dataType, count );
111 caStatus status = this->out.copyInHeader ( msg.m_cmmd, size,
112- msg.m_dataType, msg.m_count, ECA_NORMAL,
113+ msg.m_dataType, count, ECA_NORMAL,
114 msg.m_available, & pPayload );
115 if ( status ) {
116 if ( status == S_cas_hugeRequest ) {
117@@ -675,23 +684,23 @@
118 // convert gdd to db_access type
119 //
120 int mapDBRStatus = gddMapDbr[msg.m_dataType].conv_dbr ( pPayload,
121- msg.m_count, desc, pChan->enumStringTable() );
122+ count, desc, pChan->enumStringTable() );
123 if ( mapDBRStatus < 0 ) {
124 desc.dump();
125 errPrintf ( S_cas_badBounds, __FILE__, __LINE__,
126 "- get notify with PV=%s type=%u count=%u",
127- pChan->getPVI().getName(), msg.m_dataType, msg.m_count );
128+ pChan->getPVI().getName(), msg.m_dataType, count );
129 return this->readNotifyFailureResponse ( guard, msg, ECA_NOCONVERT );
130 }
131
132 int cacStatus = caNetConvert (
133- msg.m_dataType, pPayload, pPayload, true, msg.m_count );
134+ msg.m_dataType, pPayload, pPayload, true, count );
135 if ( cacStatus != ECA_NORMAL ) {
136 return this->sendErrWithEpicsStatus (
137 guard, & msg, pChan->getCID(), S_cas_internal, cacStatus );
138 }
139
140- if ( msg.m_dataType == DBR_STRING && msg.m_count == 1u ) {
141+ if ( msg.m_dataType == DBR_STRING && count == 1u ) {
142 unsigned reducedPayloadSize = strlen ( static_cast < char * > ( pPayload ) ) + 1u;
143 this->out.commitMsg ( reducedPayloadSize );
144 }
145@@ -849,11 +858,15 @@
146 casChannelI & chan, const caHdrLargeArray & msg,
147 const gdd & desc, const caStatus completionStatus )
148 {
149+ ca_uint32_t count = (msg.m_count == 0) ?
150+ (ca_uint32_t)desc.getDataSizeElements() :
151+ msg.m_count;
152+
153 void * pPayload = 0;
154 {
155- ca_uint32_t size = dbr_size_n ( msg.m_dataType, msg.m_count );
156+ ca_uint32_t size = dbr_size_n ( msg.m_dataType, count );
157 caStatus status = out.copyInHeader ( msg.m_cmmd, size,
158- msg.m_dataType, msg.m_count, ECA_NORMAL,
159+ msg.m_dataType, count, ECA_NORMAL,
160 msg.m_available, & pPayload );
161 if ( status ) {
162 if ( status == S_cas_hugeRequest ) {
163@@ -871,7 +884,7 @@
164
165 gdd * pDBRDD = 0;
166 if ( completionStatus == S_cas_success ) {
167- caStatus status = createDBRDD ( msg.m_dataType, msg.m_count, pDBRDD );
168+ caStatus status = createDBRDD ( msg.m_dataType, count, pDBRDD );
169 if ( status != S_cas_success ) {
170 caStatus ecaStatus;
171 if ( status == S_cas_badType ) {
172@@ -892,7 +905,7 @@
173 pDBRDD->unreference ();
174 errPrintf ( S_cas_noConvert, __FILE__, __LINE__,
175 "no conversion between event app type=%d and DBR type=%d Element count=%d",
176- desc.applicationType (), msg.m_dataType, msg.m_count);
177+ desc.applicationType (), msg.m_dataType, count);
178 return monitorFailureResponse ( guard, msg, ECA_NOCONVERT );
179 }
180 }
181@@ -915,14 +928,14 @@
182 }
183
184 int mapDBRStatus = gddMapDbr[msg.m_dataType].conv_dbr (
185- pPayload, msg.m_count, *pDBRDD, chan.enumStringTable() );
186+ pPayload, count, *pDBRDD, chan.enumStringTable() );
187 if ( mapDBRStatus < 0 ) {
188 pDBRDD->unreference ();
189 return monitorFailureResponse ( guard, msg, ECA_NOCONVERT );
190 }
191
192 int cacStatus = caNetConvert (
193- msg.m_dataType, pPayload, pPayload, true, msg.m_count );
194+ msg.m_dataType, pPayload, pPayload, true, count );
195 if ( cacStatus != ECA_NORMAL ) {
196 pDBRDD->unreference ();
197 return this->sendErrWithEpicsStatus (
198@@ -932,7 +945,7 @@
199 //
200 // force string message size to be the true size
201 //
202- if ( msg.m_dataType == DBR_STRING && msg.m_count == 1u ) {
203+ if ( msg.m_dataType == DBR_STRING && count == 1u ) {
204 ca_uint32_t reducedPayloadSize = strlen ( static_cast < char * > ( pPayload ) ) + 1u;
205 this->out.commitMsg ( reducedPayloadSize );
206 }
207@@ -1940,7 +1953,7 @@
208
209 casChannelI *pciu;
210 {
211- caStatus status = casStrmClient::verifyRequest ( pciu );
212+ caStatus status = casStrmClient::verifyRequest ( pciu, CA_V413 ( this->minor_version_number ) );
213 if ( status != ECA_NORMAL ) {
214 if ( pciu ) {
215 return this->sendErr ( guard, mp,
216
217=== modified file 'src/ca/legacy/pcas/generic/casStrmClient.h'
218--- src/ca/legacy/pcas/generic/casStrmClient.h 2010-08-10 21:05:46 +0000
219+++ src/ca/legacy/pcas/generic/casStrmClient.h 2015-04-14 11:26:50 +0000
220@@ -69,7 +69,7 @@
221 bool responseIsPending;
222
223 caStatus createChannel ( const char * pName );
224- caStatus verifyRequest ( casChannelI * & pChan );
225+ caStatus verifyRequest ( casChannelI * & pChan, bool allowZeroCount = false );
226 typedef caStatus ( casStrmClient :: * pCASMsgHandler )
227 ( epicsGuard < casClientMutex > & );
228 static pCASMsgHandler const msgHandlers[CA_PROTO_LAST_CMMD+1u];

Subscribers

People subscribed via source and target branches