dbCa: fix race in scanComplete

Use a reference counter in caLink to ensure it isn't
free'd while callbacks remain in the scanOnce queue.
This commit is contained in:
Michael Davidsaver
2015-03-13 15:15:03 -04:00
parent cc3a01d460
commit c120027768
2 changed files with 29 additions and 7 deletions

View File

@@ -30,6 +30,7 @@
#include "epicsPrint.h"
#include "epicsString.h"
#include "epicsThread.h"
#include "epicsAtomic.h"
#include "epicsTime.h"
#include "errlog.h"
#include "errMdef.h"
@@ -165,11 +166,23 @@ static void addAction(caLink *pca, short link_action)
epicsEventSignal(workListEvent);
}
static void dbCaLinkFree(caLink *pca)
static void caLinkInc(caLink *pca)
{
assert(epicsAtomicGetIntT(&pca->refcount)>0);
epicsAtomicIncrIntT(&pca->refcount);
}
static void caLinkDec(caLink *pca)
{
int cnt;
dbCaCallback callback;
struct link *plinkPutCallback = 0;
cnt = epicsAtomicDecrIntT(&pca->refcount);
assert(cnt>=0);
if(cnt>0)
return;
if (pca->chid) {
ca_clear_channel(pca->chid);
--dbca_chan_count;
@@ -216,7 +229,7 @@ void dbCaShutdown(void)
epicsMutexMustLock(workListLock);
while((pca=(caLink*)ellGet(&workList))!=NULL) {
if(pca->link_action&CA_CLEAR_CHANNEL) {
dbCaLinkFree(pca);
caLinkDec(pca);
}
}
epicsMutexUnlock(workListLock);
@@ -275,6 +288,7 @@ void dbCaAddLinkCallback(struct link *plink,
assert(!plink->value.pv_link.pvt);
pca = (caLink *)dbCalloc(1, sizeof(caLink));
pca->refcount = 1;
pca->lock = epicsMutexMustCreate();
pca->plink = plink;
pca->pvname = epicsStrDup(plink->value.pv_link.pvname);
@@ -639,21 +653,28 @@ static void scanComplete(void *raw, dbCommon *prec)
{
caLink *pca = raw;
epicsMutexMustLock(pca->lock);
if(pca->scanningOnce==0)
if(!pca->plink) {
/* IOC shutdown or link re-targeted. Do nothing. */
} else if(pca->scanningOnce==0) {
errlogPrintf("dbCa.c complete callback w/ scanningOnce==0\n");
else if(--pca->scanningOnce){
} else if(--pca->scanningOnce){
/* another scan is queued */
if(scanOnceCallback(prec, scanComplete, raw)) {
errlogPrintf("dbCa.c failed to re-queue scanOnce\n");
}
}
epicsMutexUnlock(pca->lock);
caLinkDec(pca);
}
/* must be called with pca->lock held */
static void scanLinkOnce(dbCommon *prec, caLink *pca) {
if(pca->scanningOnce==0 && scanOnceCallback(prec, scanComplete, pca)) {
errlogPrintf("dbCa.c failed to queue scanOnce\n");
if(pca->scanningOnce==0) {
caLinkInc(pca);
if(scanOnceCallback(prec, scanComplete, pca)) {
caLinkDec(pca);
errlogPrintf("dbCa.c failed to queue scanOnce\n");
}
}
if(pca->scanningOnce<5)
pca->scanningOnce++;
@@ -953,7 +974,7 @@ static void dbCaTask(void *arg)
if (link_action & CA_CLEAR_CHANNEL) --removesOutstanding;
epicsMutexUnlock(workListLock); /* Give back immediately */
if (link_action & CA_CLEAR_CHANNEL) { /* This must be first */
dbCaLinkFree(pca);
caLinkDec(pca);
/* No alarm is raised. Since link is changing so what? */
continue; /* No other link_action makes sense */
}

View File

@@ -39,6 +39,7 @@
typedef struct caLink
{
ELLNODE node;
int refcount;
epicsMutexId lock;
struct link *plink;
char *pvname;