Merge ~epics-core/epics-base/+git/database:putf-pact into ~epics-core/epics-base/+git/epics-base:7.0

Proposed by mdavidsaver
Status: Merged
Merged at revision: 632d1f45c84f7a6425b30acc1a24b438ae42a58f
Proposed branch: ~epics-core/epics-base/+git/database:putf-pact
Merge into: ~epics-core/epics-base/+git/epics-base:7.0
Diff against target: 345 lines (+139/-23)
7 files modified
modules/database/src/ioc/db/dbAccess.c (+2/-2)
modules/database/src/ioc/db/dbCommonPvt.h (+13/-0)
modules/database/src/ioc/db/dbDbLink.c (+35/-9)
modules/database/src/ioc/db/dbNotify.c (+7/-10)
modules/database/src/ioc/dbStatic/dbStaticRun.c (+1/-1)
modules/database/test/std/rec/asyncproctest.c (+43/-1)
modules/database/test/std/rec/asyncproctest.db (+38/-0)
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
Review via email: mp+362207@code.launchpad.net

Description of the change

Attempt to address #1809570 by removing the warning, and fixing dbNotify.

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

Not tested, but this looks good to me.

review: Approve
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Added proposed fix for the second part of lp:1809570.

This adds dbCommonPvt::procThread to track the thread which is currently processing a record.
This is set, and cleared, by processTarget(). This is called during dbPutLink() for a DB_LINK.

procThread will either be NULL, or contain the epicsThreadId for the current thread. Any other value is a logic error (missing dbScanLock(), or maybe incorrect lockset calculation).

The logic in processTarget() checks both the source record and destination record. procThread for the source will be NULL when the first link in a record chain is handled. Subsequent links in a chain will find source procThread==self. For the target record, procThread will be NULL, unless a loop would be closed.

The possible states of PACT and procThread on entry to processTarget() are

!PACT && !procThread - Idle record
!PACT && procThread - Logic error?
PACT && !procThread - Waiting for driver/rset to complete
PACT & procThread - process() in progress on this thread.

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

What does the "set" part of your new variable names stand for? They don't read very obviously to me, especially out of the context of this bug-fix. The srcset/dstset variable is true if the psrc/pdst record has no procThread ID set, but I don't get any feel for what this means (weset in the previous iteration had a similar problem). Can you find something more meaningful?

You're still putting assert() statements in code that runs frequently inside the IOC. You may be more certain than last time that these assertions should never fire, but that makes IOCs more likely to die in the event of a memory error. I would prefer that these conditions just generate a warning (and maybe set procThread to whatever it's supposed to be if that's possible) without stopping the IOC thread. Brittle IOC code doesn't contribute to 98+% beam availability.

This doesn't look like it should have any problems with the synapps busy record, but have you tried it?

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Added additional handling for self link case (psrc==pdst).

> ... Can you find something more meaningful?

Would a comment like the following add sufficient meaning for you? If not, then I'm going to need more input.

/* decide if this call will manage procThread for the src and/or dst records */

> You're still putting assert() statements in code that runs frequently inside the IOC. ...

Yes, and I will continue to do so. Warnings don't always get reported. Not every user is as attentive as Mark. Travis certainly isn't.

> ... I would prefer that these conditions just generate a warning ...

Let's hold off on this until the change has been verified. CI doesn't grok warnings.

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

Should we also have a separate test case for an INP self link that is marked PP? Your latest commit isn't symmetrical between src and dst, and I'm not certain whether this handles the INP case as well — an extra test would dispel that concern.

Regarding the names, just putting the verb before the noun helps a little with comprehension, so setsrc would be one step better. Looking deeper, these variables are controlling whether the code lower down should mark the src/dst record and then unmark it again after record processing, so instead of "set" the verb could be "mark". If I've read it right though that mark is actually saying "my thread currently owns this record" so maybe the verb should be "claim" so claimsrc/claimSrc/claim_src.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Pushed name change, and additional tests.

> ... I'm not certain whether this handles the INP case as well ...

It does. 'src' and 'dst' in the link API is always about the direction of control flow, not data flow.

> This doesn't look like it should have any problems with the synapps busy record, but have you tried it?

Yes, it it appears to work from my (superficial) testing with AD's sim detector.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

@anj, does your "Approve" stand? Should I merge?

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

@anj, ping...

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

With the observation that the assertions are still present and I hope they never fire, this looks good to me, thanks!

review: Approve
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Let it not be said that I ignore ANJ. I've replaced the assertions with errlog'd warnings. Let's hope they are promptly reported if they appear.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c
2index af396c7..be81d22 100644
3--- a/modules/database/src/ioc/db/dbAccess.c
4+++ b/modules/database/src/ioc/db/dbAccess.c
5@@ -684,7 +684,7 @@ finish:
6 void dbInitEntryFromAddr(struct dbAddr *paddr, DBENTRY *pdbentry)
7 {
8 struct dbCommon *prec = paddr->precord;
9- dbCommonPvt *ppvt = CONTAINER(prec, dbCommonPvt, common);
10+ dbCommonPvt *ppvt = dbRec2Pvt(prec);
11
12 memset((char *)pdbentry,'\0',sizeof(DBENTRY));
13
14@@ -698,7 +698,7 @@ void dbInitEntryFromAddr(struct dbAddr *paddr, DBENTRY *pdbentry)
15
16 void dbInitEntryFromRecord(struct dbCommon *prec, DBENTRY *pdbentry)
17 {
18- dbCommonPvt *ppvt = CONTAINER(prec, dbCommonPvt, common);
19+ dbCommonPvt *ppvt = dbRec2Pvt(prec);
20
21 memset((char *)pdbentry,'\0',sizeof(DBENTRY));
22
23diff --git a/modules/database/src/ioc/db/dbCommonPvt.h b/modules/database/src/ioc/db/dbCommonPvt.h
24index 3dfce8b..eda8b18 100644
25--- a/modules/database/src/ioc/db/dbCommonPvt.h
26+++ b/modules/database/src/ioc/db/dbCommonPvt.h
27@@ -1,14 +1,27 @@
28 #ifndef DBCOMMONPVT_H
29 #define DBCOMMONPVT_H
30
31+#include <compilerDependencies.h>
32+#include <dbDefs.h>
33 #include "dbCommon.h"
34
35+struct epicsThreadOSD;
36+
37 /** Base internal additional information for every record
38 */
39 typedef struct dbCommonPvt {
40 struct dbRecordNode *recnode;
41
42+ /* Thread which is currently processing this record */
43+ struct epicsThreadOSD* procThread;
44+
45 struct dbCommon common;
46 } dbCommonPvt;
47
48+static EPICS_ALWAYS_INLINE
49+dbCommonPvt* dbRec2Pvt(struct dbCommon *prec)
50+{
51+ return CONTAINER(prec, dbCommonPvt, common);
52+}
53+
54 #endif // DBCOMMONPVT_H
55diff --git a/modules/database/src/ioc/db/dbDbLink.c b/modules/database/src/ioc/db/dbDbLink.c
56index ce0110a..0da9271 100644
57--- a/modules/database/src/ioc/db/dbDbLink.c
58+++ b/modules/database/src/ioc/db/dbDbLink.c
59@@ -56,7 +56,7 @@
60 #include "dbAddr.h"
61 #include "dbBase.h"
62 #include "dbBkpt.h"
63-#include "dbCommon.h"
64+#include "dbCommonPvt.h"
65 #include "dbConvertFast.h"
66 #include "dbConvert.h"
67 #include "db_field_log.h"
68@@ -386,8 +386,11 @@ static long processTarget(dbCommon *psrc, dbCommon *pdst)
69 {
70 char context[40] = "";
71 int trace = dbAccessDebugPUTF && *dbLockSetAddrTrace(psrc);
72+ int claim_src = dbRec2Pvt(psrc)->procThread==NULL;
73+ int claim_dst = psrc!=pdst && dbRec2Pvt(pdst)->procThread==NULL;
74 long status;
75 epicsUInt8 pact = psrc->pact;
76+ epicsThreadId self = epicsThreadGetIdSelf();
77
78 psrc->pact = TRUE;
79
80@@ -406,14 +409,11 @@ static long processTarget(dbCommon *psrc, dbCommon *pdst)
81 printf("%s: '%s' -> '%s' with PUTF=%u\n",
82 context, psrc->name, pdst->name, psrc->putf);
83
84- if (pdst->putf)
85- errlogPrintf("Warning: '%s.PUTF' found true with PACT false\n",
86- pdst->name);
87-
88 pdst->putf = psrc->putf;
89 }
90- else if (psrc->putf) {
91- /* The dst record is busy (awaiting async reprocessing) and
92+ else if (psrc->putf && claim_dst) {
93+ /* The dst record is busy (awaiting async reprocessing),
94+ * not being processed recursively by us, and
95 * we were originally triggered by a call to dbPutField(),
96 * so we mark the dst record for reprocessing once the async
97 * completion is over.
98@@ -426,17 +426,43 @@ static long processTarget(dbCommon *psrc, dbCommon *pdst)
99 pdst->rpro = TRUE;
100 }
101 else {
102- /* The dst record is busy, but we weren't triggered by a call
103- * to dbPutField(). Do nothing.
104+ /* The dst record is busy, but either is being processed recursively,
105+ * or wasn't triggered by a call to dbPutField(). Do nothing.
106 */
107 if (trace)
108 printf("%s: '%s' -> Active '%s', done\n",
109 context, psrc->name, pdst->name);
110 }
111
112+ if(claim_src) {
113+ dbRec2Pvt(psrc)->procThread = self;
114+ }
115+ if(claim_dst) {
116+ dbRec2Pvt(pdst)->procThread = self;
117+ }
118+
119+ if(dbRec2Pvt(psrc)->procThread!=self ||
120+ dbRec2Pvt(pdst)->procThread!=self) {
121+ errlogPrintf("Logic Error: processTarget 1 from %p, %s(%p) -> %s(%p)\n",
122+ self, psrc->name, dbRec2Pvt(psrc), pdst->name, dbRec2Pvt(pdst));
123+ }
124+
125 status = dbProcess(pdst);
126
127 psrc->pact = pact;
128
129+ if(dbRec2Pvt(psrc)->procThread!=self ||
130+ dbRec2Pvt(pdst)->procThread!=self) {
131+ errlogPrintf("Logic Error: processTarget 2 from %p, %s(%p) -> %s(%p)\n",
132+ self, psrc->name, dbRec2Pvt(psrc), pdst->name, dbRec2Pvt(pdst));
133+ }
134+
135+ if(claim_src) {
136+ dbRec2Pvt(psrc)->procThread = NULL;
137+ }
138+ if(claim_dst) {
139+ dbRec2Pvt(pdst)->procThread = NULL;
140+ }
141+
142 return status;
143 }
144diff --git a/modules/database/src/ioc/db/dbNotify.c b/modules/database/src/ioc/db/dbNotify.c
145index c2420af..794672a 100644
146--- a/modules/database/src/ioc/db/dbNotify.c
147+++ b/modules/database/src/ioc/db/dbNotify.c
148@@ -86,12 +86,6 @@ typedef struct notifyGlobal {
149
150 static notifyGlobal *pnotifyGlobal = 0;
151
152-/*Local routines*/
153-static void notifyInit(processNotify *ppn);
154-static void notifyCleanup(processNotify *ppn);
155-static void restartCheck(processNotifyRecord *ppnr);
156-static void callDone(dbCommon *precord,processNotify *ppn);
157-static void processNotifyCommon(processNotify *ppn,dbCommon *precord);
158 static void notifyCallback(CALLBACK *pcallback);
159
160 #define ellSafeAdd(list,listnode) \
161@@ -210,7 +204,7 @@ static void callDone(dbCommon *precord, processNotify *ppn)
162 return;
163 }
164
165-static void processNotifyCommon(processNotify *ppn,dbCommon *precord)
166+static void processNotifyCommon(processNotify *ppn, dbCommon *precord, int first)
167 {
168 notifyPvt *pnotifyPvt = (notifyPvt *) ppn->pnotifyPvt;
169 int didPut = 0;
170@@ -256,6 +250,9 @@ static void processNotifyCommon(processNotify *ppn,dbCommon *precord)
171 doProcess = 1;
172
173 if (doProcess) {
174+ if (first) {
175+ precord->putf = TRUE;
176+ }
177 ppn->wasProcessed = 1;
178 precord->ppn = ppn;
179 ellSafeAdd(&pnotifyPvt->waitList, &precord->ppnr->waitNode);
180@@ -298,7 +295,7 @@ static void notifyCallback(CALLBACK *pcallback)
181 return;
182 }
183 if(pnotifyPvt->state == notifyRestartCallbackRequested) {
184- processNotifyCommon(ppn, precord);
185+ processNotifyCommon(ppn, precord, 0);
186 return;
187 }
188 /* All done. Clean up and call userCallback */
189@@ -382,7 +379,7 @@ void dbProcessNotify(processNotify *ppn)
190 precord->ppnr->precord = precord;
191 ellInit(&precord->ppnr->restartList);
192 }
193- processNotifyCommon(ppn, precord);
194+ processNotifyCommon(ppn, precord, 1);
195 }
196
197 void dbNotifyCancel(processNotify *ppn)
198@@ -582,7 +579,7 @@ static void tpnThread(void *pvt)
199 processNotify *ppn = (processNotify *) ptpnInfo->ppn;
200
201 dbProcessNotify(ppn);
202- epicsEventWait(ptpnInfo->callbackDone);
203+ epicsEventMustWait(ptpnInfo->callbackDone);
204 dbNotifyCancel(ppn);
205 epicsEventDestroy(ptpnInfo->callbackDone);
206 dbChannelDelete(ppn->chan);
207diff --git a/modules/database/src/ioc/dbStatic/dbStaticRun.c b/modules/database/src/ioc/dbStatic/dbStaticRun.c
208index d3817ff..46cbf98 100644
209--- a/modules/database/src/ioc/dbStatic/dbStaticRun.c
210+++ b/modules/database/src/ioc/dbStatic/dbStaticRun.c
211@@ -179,7 +179,7 @@ long dbFreeRecord(DBENTRY *pdbentry)
212 if(!pdbRecordType) return(S_dbLib_recordTypeNotFound);
213 if(!precnode) return(S_dbLib_recNotFound);
214 if(!precnode->precord) return(S_dbLib_recNotFound);
215- free(CONTAINER(precnode->precord, dbCommonPvt, common));
216+ free(dbRec2Pvt(precnode->precord));
217 precnode->precord = NULL;
218 return(0);
219 }
220diff --git a/modules/database/test/std/rec/asyncproctest.c b/modules/database/test/std/rec/asyncproctest.c
221index a8b09a9..7b8fca7 100644
222--- a/modules/database/test/std/rec/asyncproctest.c
223+++ b/modules/database/test/std/rec/asyncproctest.c
224@@ -9,6 +9,7 @@
225
226 #include <testMain.h>
227 #include <dbUnitTest.h>
228+#include <errlog.h>
229 #include <dbCommon.h>
230 #include <dbAccess.h>
231 #include <epicsEvent.h>
232@@ -16,6 +17,7 @@
233 #include <iocsh.h>
234 #include "registryFunction.h"
235 #include <subRecord.h>
236+#include <dbScan.h>
237
238 epicsEventId done;
239 static int waitFor;
240@@ -28,11 +30,17 @@ long doneSubr(subRecord *prec)
241 return 0;
242 }
243
244+static
245+void dummydone(void *usr, struct dbCommon* prec)
246+{
247+ epicsEventMustTrigger(done);
248+}
249+
250 void asyncproctest_registerRecordDeviceDriver(struct dbBase *);
251
252 MAIN(asyncproctest)
253 {
254- testPlan(21);
255+ testPlan(27);
256
257 done = epicsEventMustCreate(epicsEventEmpty);
258
259@@ -99,6 +107,40 @@ MAIN(asyncproctest)
260 testdbGetFieldEqual("chain3", DBF_LONG, 7);
261 testdbGetFieldEqual("chain3.A", DBF_LONG, 2);
262
263+ testDiag("===== Chain 4 ======");
264+
265+ {
266+ dbCommon *dummy=testdbRecordPtr("chain4_dummy");
267+
268+ testdbPutFieldOk("chain4_pos.PROC", DBF_LONG, 0);
269+
270+ /* sync once queue to wait for any queued RPRO */
271+ scanOnceCallback(dummy, dummydone, NULL);
272+
273+ if (epicsEventWaitWithTimeout(done, 10.0) != epicsEventOK)
274+ testAbort("Processing timed out");
275+
276+ testdbGetFieldEqual("chain4_pos", DBF_SHORT, 1);
277+ testdbGetFieldEqual("chain4_rel", DBF_SHORT, 1);
278+ testdbGetFieldEqual("chain4_lim", DBF_SHORT, 1);
279+ }
280+
281+ testDiag("===== Chain 5 ======");
282+
283+ {
284+ dbCommon *dummy=testdbRecordPtr("chain4_dummy");
285+
286+ testdbPutFieldOk("chain5_cnt.PROC", DBF_LONG, 0);
287+
288+ /* sync once queue to wait for any queued RPRO */
289+ scanOnceCallback(dummy, dummydone, NULL);
290+
291+ if (epicsEventWaitWithTimeout(done, 10.0) != epicsEventOK)
292+ testAbort("Processing timed out");
293+
294+ testdbGetFieldEqual("chain5_cnt", DBF_SHORT, 1);
295+ }
296+
297 testIocShutdownOk();
298
299 testdbCleanup();
300diff --git a/modules/database/test/std/rec/asyncproctest.db b/modules/database/test/std/rec/asyncproctest.db
301index 352b6ca..87fe5d6 100644
302--- a/modules/database/test/std/rec/asyncproctest.db
303+++ b/modules/database/test/std/rec/asyncproctest.db
304@@ -53,3 +53,41 @@ record(sub, "done3") {
305 field(FLNK, "chain3")
306 field(TPRO, "$(TPRO=)")
307 }
308+
309+
310+# loop breaking regression
311+# should _not_ RPRO
312+record(calcout,"chain4_pos") {
313+ field(CALC, "E:=E+1;E")
314+ field(OUT,"chain4_rel.A PP")
315+ field(TPRO, "$(TPRO=)")
316+}
317+
318+record(calc,"chain4_rel") {
319+ field(CALC, "E:=E+1;E")
320+ field(FLNK,"chain4_lim")
321+ field(TPRO, "$(TPRO=)")
322+}
323+
324+record(calc,"chain4_lim") {
325+ field(CALC, "E:=E+1;E")
326+ field(INPA,"chain4_pos PP")
327+ field(INPB,"chain4_pos.HIGH PP")
328+ field(INPC,"chain4_pos.LOW PP")
329+ field(FLNK,"chain4_pos")
330+ field(TPRO, "$(TPRO=)")
331+}
332+
333+record(bo, "chain4_dummy") {
334+field(TPRO, "$(TPRO=)")
335+}
336+
337+# loop breaking regression part 2
338+# selft link should _not_ RPRO
339+record(calcout,"chain5_cnt") {
340+ field(CALC, "E:=E+1;E")
341+ field(INPA,"chain5_cnt.A PP")
342+ field(OUT,"chain5_cnt.A PP")
343+ field(FLNK,"chain5_cnt")
344+ field(TPRO, "$(TPRO=)")
345+}

Subscribers

People subscribed via source and target branches