Merge ~epics-core/epics-base/+git/database:putf-pact into ~epics-core/epics-base/+git/epics-base:7.0
- Git
- lp:~epics-core/epics-base/+git/database
- putf-pact
- Merge into 7.0
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) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andrew Johnson | Approve | ||
Review via email: mp+362207@code.launchpad.net |
Commit message
Description of the change
Attempt to address #1809570 by removing the warning, and fixing dbNotify.
mdavidsaver (mdavidsaver) wrote : | # |
Added proposed fix for the second part of lp:1809570.
This adds dbCommonPvt:
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.
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?
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.
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/
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.
mdavidsaver (mdavidsaver) wrote : | # |
@anj, does your "Approve" stand? Should I merge?
mdavidsaver (mdavidsaver) wrote : | # |
@anj, ping...
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!
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
1 | diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c |
2 | index 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 | |
23 | diff --git a/modules/database/src/ioc/db/dbCommonPvt.h b/modules/database/src/ioc/db/dbCommonPvt.h |
24 | index 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 |
55 | diff --git a/modules/database/src/ioc/db/dbDbLink.c b/modules/database/src/ioc/db/dbDbLink.c |
56 | index 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 | } |
144 | diff --git a/modules/database/src/ioc/db/dbNotify.c b/modules/database/src/ioc/db/dbNotify.c |
145 | index 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); |
207 | diff --git a/modules/database/src/ioc/dbStatic/dbStaticRun.c b/modules/database/src/ioc/dbStatic/dbStaticRun.c |
208 | index 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 | } |
220 | diff --git a/modules/database/test/std/rec/asyncproctest.c b/modules/database/test/std/rec/asyncproctest.c |
221 | index 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(); |
300 | diff --git a/modules/database/test/std/rec/asyncproctest.db b/modules/database/test/std/rec/asyncproctest.db |
301 | index 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 | +} |
Not tested, but this looks good to me.