Files
pcas/src/db/dbLock.c
1999-09-13 18:28:08 +00:00

485 lines
14 KiB
C
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* dbLock.c */
/* Author: Marty Kraimer Date: 12MAR96 */
/*****************************************************************
COPYRIGHT NOTIFICATION
*****************************************************************
(C) COPYRIGHT 1991 Regents of the University of California,
and the University of Chicago Board of Governors.
This software was developed under a United States Government license
described on the COPYRIGHT_Combined file included as part
of this distribution.
**********************************************************************/
/* Modification Log:
* -----------------
* .01 12MAR96 mrk Initial Implementation
*/
/************** DISCUSSION OF DYNAMIC LINK MODIFICATION **********************
Since the purpose of lock sets is to prevent multiple thread from simultaneously
accessing records in set, dynamically changing lock sets presents a problem.
Four problems arise:
1) Two threads simultaneoulsy trying to change lock sets
2) Another thread has successfully issued a dbScanLock and currently owns it.
3) A thread is waiting for dbScanLock.
4) While lock set is being changed, a thread issues a dbScanLock.
Solution:
A routine attempting to modify a link must do the following:
Call dbLockSetGblLock before modifying any link and dbLockSetGblUnlock after.
Call dbLockSetRecordLock for any record referenced during change.
Call dbLockSetSplit before changing any link that is originally a DB_LINK
Call dbLockSetMerge if changed link becomes a DB_LINK.
Discussion:
Each problem above is solved as follows:
1) dbLockGlobal solves this problem.
2) dbLockSetRecordLock solves this problem.
3) After changing lock sets original semId id deleted.
This makes all threads in semTake for that semaphore fail.
The code in dbScanLock makes thread recover.
4) The global variable changingLockSets and code in
dbScanLock and semFlush in dbLockSetGblUnlock solves
this problem.
Note that all other threads are prevented from processing records between
dbLockSetGblLock and dbLockSetGblUnlock.
dblsr may crash if executed while lock sets are being modified.
It is NOT a good idea to make it more robust by issuing dbLockSetGblLock
since this will delay all other threads.
*****************************************************************************/
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "dbDefs.h"
#include "dbBase.h"
#include "osiSem.h"
#include "osiClock.h"
#include "osiThread.h"
#include "cantProceed.h"
#include "ellLib.h"
#include "dbAccess.h"
#include "dbStaticLib.h"
#include "dbConvert.h"
#include "dbCommon.h"
#include "dbLock.h"
#include "epicsPrint.h"
#include "dbFldTypes.h"
#include "errMdef.h"
#define STATIC static
STATIC int lockListInitialized = FALSE;
STATIC ELLLIST lockList;
STATIC semId globalLock;
STATIC semId globalWait;
STATIC unsigned long id = 0;
STATIC int changingLockSets = FALSE;
typedef struct lockSet {
ELLNODE node;
ELLLIST recordList;
semId lock;
unsigned long start_time;
threadId thread_id;
dbCommon *precord;
unsigned long id;
} lockSet;
typedef struct lockRecord {
ELLNODE node;
lockSet *plockSet;
dbCommon *precord;
} lockRecord;
/*private routines */
STATIC void initLockList(void)
{
ellInit(&lockList);
if((globalLock = semMutexCreate())==0) {
cantProceed("initLockList failed calling semMutexCreate\n");
}
if((globalWait = semBinaryCreate(semEmpty))==0) {
cantProceed("initLockList failed calling semBinaryCreate\n");
}
lockListInitialized = TRUE;
}
STATIC lockSet * allocLock(lockRecord *plockRecord)
{
lockSet *plockSet;
if(!lockListInitialized) initLockList();
plockSet = dbCalloc(1,sizeof(lockSet));
ellInit(&plockSet->recordList);
plockRecord->plockSet = plockSet;
id++;
plockSet->id = id;
ellAdd(&plockSet->recordList,&plockRecord->node);
ellAdd(&lockList,&plockSet->node);
if((plockSet->lock = semMutexCreate())==0) {
cantProceed("allocLock calling semMutexCreate\n");
}
return(plockSet);
}
/*Add new lockRecord to lockSet list*/
STATIC void lockAddRecord(lockSet *plockSet,lockRecord *pnew)
{
pnew->plockSet = plockSet;
ellAdd(&plockSet->recordList,&pnew->node);
}
void dbLockSetGblLock(void)
{
if(!lockListInitialized) initLockList();
semMutexTakeAssert(globalLock);
changingLockSets = TRUE;
}
void dbLockSetGblUnlock(void)
{
threadLockContextSwitch();
changingLockSets = FALSE;
semBinaryFlush(globalWait);
threadUnlockContextSwitch();
semMutexGive(globalLock);
return;
}
void dbLockSetRecordLock(dbCommon *precord)
{
lockRecord *plockRecord = precord->lset;
lockSet *plockSet;
semTakeStatus status;
/*Make sure that dbLockSetGblLock was called*/
if(!changingLockSets) {
cantProceed("dbLockSetRecordLock called before dbLockSetGblLock\n");
}
/*Must make sure that no other thread has lock*/
if(!plockRecord) return;
plockSet = plockRecord->plockSet;
if(!plockSet) return;
if(plockSet->thread_id==threadGetIdSelf()) return;
/*Wait for up to 1 minute*/
status = semMutexTakeTimeout(plockRecord->plockSet->lock,60.0);
if(status==semTakeOK) {
plockSet->start_time = clockGetCurrentTick();
plockSet->thread_id = threadGetIdSelf();
plockSet->precord = (void *)precord;
/*give it back in case it will not be changed*/
semMutexGive(plockRecord->plockSet->lock);
return;
}
/*Should never reach this point*/
errlogPrintf("dbLockSetRecordLock timeout caller 0x%x owner 0x%x",
threadGetIdSelf(),plockSet->thread_id);
errlogPrintf(" record %s\n",precord->name);
return;
}
void dbScanLock(dbCommon *precord)
{
lockRecord *plockRecord;
lockSet *plockSet;
semTakeStatus status;
if(!(plockRecord= precord->lset)) {
epicsPrintf("dbScanLock plockRecord is NULL record %s\n",
precord->name);
threadSuspend(threadGetIdSelf());
}
while(TRUE) {
if(changingLockSets) {
semBinaryTakeAssert(globalWait);
continue;
}
status = semMutexTake(plockRecord->plockSet->lock);
/*semMutexTake fails if semMutexDestroy was called while active*/
if(status==semTakeOK) break;
}
plockSet = plockRecord->plockSet;
plockSet->start_time = clockGetCurrentTick();
plockSet->thread_id = threadGetIdSelf();
plockSet->precord = (void *)precord;
return;
}
void dbScanUnlock(dbCommon *precord)
{
lockRecord *plockRecord = precord->lset;
if(!plockRecord || !plockRecord->plockSet) {
epicsPrintf("dbScanUnlock plockRecord or plockRecord->plockSet NULL\n");
return;
}
semMutexGive(plockRecord->plockSet->lock);
return;
}
unsigned long dbLockGetLockId(dbCommon *precord)
{
lockRecord *plockRecord = precord->lset;
lockSet *plockSet;
if(!plockRecord) return(0);
plockSet = plockRecord->plockSet;
if(!plockSet) return(0);
return(plockSet->id);
}
void dbLockInitRecords(dbBase *pdbbase)
{
int link;
dbRecordType *pdbRecordType;
dbFldDes *pdbFldDes;
dbRecordNode *pdbRecordNode;
dbCommon *precord;
DBLINK *plink;
int nrecords=0;
lockRecord *plockRecord;
/*Allocate and initialize a lockRecord for each record instance*/
for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); pdbRecordType;
pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) {
nrecords += ellCount(&pdbRecordType->recList);
}
/*Allocate all of them at once */
plockRecord = dbCalloc(nrecords,sizeof(lockRecord));
for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); pdbRecordType;
pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) {
for (pdbRecordNode=(dbRecordNode *)ellFirst(&pdbRecordType->recList);
pdbRecordNode;
pdbRecordNode = (dbRecordNode *)ellNext(&pdbRecordNode->node)) {
precord = pdbRecordNode->precord;
plockRecord->precord = precord;
precord->lset = plockRecord;
plockRecord++;
}
}
for(pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList);
pdbRecordType;
pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) {
for (pdbRecordNode=(dbRecordNode *)ellFirst(&pdbRecordType->recList);
pdbRecordNode;
pdbRecordNode = (dbRecordNode *)ellNext(&pdbRecordNode->node)) {
precord = pdbRecordNode->precord;
if(!(precord->name[0])) continue;
for(link=0; link<pdbRecordType->no_links; link++) {
DBADDR *pdbAddr;
pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[link]];
plink = (DBLINK *)((char *)precord + pdbFldDes->offset);
if(plink->type != DB_LINK) continue;
pdbAddr = (DBADDR *)(plink->value.pv_link.pvt);
/* The current record is in a different lockset -IF-
* 1. Input link
* 2. Not Process Passive
* 3. Not Maximize Severity
* 4. Not An Array Operation - single element only
*/
if (pdbFldDes->field_type==DBF_INLINK
&& !(plink->value.pv_link.pvlMask&pvlOptPP)
&& !(plink->value.pv_link.pvlMask&pvlOptMS)
&& pdbAddr->no_elements<=1) continue;
dbLockSetMerge(precord,pdbAddr->precord);
}
plockRecord = precord->lset;
if(!plockRecord->plockSet) allocLock(plockRecord);
}
}
}
void dbLockSetMerge(dbCommon *pfirst,dbCommon *psecond)
{
lockRecord *p1lockRecord = pfirst->lset;
lockRecord *p2lockRecord = psecond->lset;
lockSet *p1lockSet;
lockSet *p2lockSet;
if(pfirst==psecond) return;
p1lockSet = p1lockRecord->plockSet;
p2lockSet = p2lockRecord->plockSet;
if(!p1lockSet) {
if(p2lockSet) {
lockAddRecord(p2lockSet,p1lockRecord);
return;
}
p1lockSet = allocLock(p1lockRecord);
}
if(p1lockSet == p2lockSet) return;
if(!p2lockSet) {
lockAddRecord(p1lockSet,p2lockRecord);
return;
}
/*Move entire second list to first*/
p2lockRecord = (lockRecord *)ellFirst(&p2lockSet->recordList);
while(p2lockRecord) {
p2lockRecord->plockSet = p1lockSet;
p2lockRecord = (lockRecord *)ellNext(&p2lockRecord->node);
}
ellConcat(&p1lockSet->recordList,&p2lockSet->recordList);
semMutexDestroy(p2lockSet->lock);
ellDelete(&lockList,&p2lockSet->node);
free((void *)p2lockSet);
return;
}
void dbLockSetSplit(dbCommon *psource)
{
lockSet *plockSet;
lockRecord *plockRecord;
dbCommon *precord;
int link;
dbRecordType *pdbRecordType;
dbFldDes *pdbFldDes;
DBLINK *plink;
int nrecordsInSet,i;
dbCommon **paprecord;
plockRecord = psource->lset;
if(!plockRecord) {
errMessage(-1,"dbLockSetSplit called before lockRecord allocated");
return;
}
plockSet = plockRecord->plockSet;
if(!plockSet) {
errMessage(-1,"dbLockSetSplit called without lockSet allocated");
return;
}
/*First remove all records from lock set*/
nrecordsInSet = ellCount(&plockSet->recordList);
paprecord = dbCalloc(nrecordsInSet,sizeof(dbCommon *));
for(plockRecord = (lockRecord *)ellFirst(&plockSet->recordList), i=0;
plockRecord;
plockRecord = (lockRecord *)ellNext(&plockRecord->node), i++) {
paprecord[i] = plockRecord->precord;
plockRecord->plockSet = 0;
}
/*Now recompute lock sets */
for(i=0; i<nrecordsInSet; i++) {
precord = paprecord[i];
plockRecord = precord->lset;
if(!(precord->name[0])) continue;
pdbRecordType = precord->rdes;
for(link=0; link<pdbRecordType->no_links; link++) {
DBADDR *pdbAddr;
pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[link]];
plink = (DBLINK *)((char *)precord + pdbFldDes->offset);
if(plink->type != DB_LINK) continue;
pdbAddr = (DBADDR *)(plink->value.pv_link.pvt);
if (pdbFldDes->field_type==DBF_INLINK
&& !(plink->value.pv_link.pvlMask&pvlOptPP)
&& !(plink->value.pv_link.pvlMask&pvlOptMS)
&& pdbAddr->no_elements<=1) continue;
dbLockSetMerge(precord,pdbAddr->precord);
}
if(!plockRecord->plockSet) allocLock(plockRecord);
}
semMutexDestroy(plockSet->lock);
ellDelete(&lockList,&plockSet->node);
free((void *)plockSet);
free((void *)paprecord);
}
extern struct dbBase *pdbbase;
long dblsr(char *recordname,int level)
{
int link;
DBENTRY dbentry;
DBENTRY *pdbentry=&dbentry;
long status;
dbCommon *precord;
lockSet *plockSet;
lockRecord *plockRecord;
dbRecordType *pdbRecordType;
dbFldDes *pdbFldDes;
DBLINK *plink;
if(recordname) {
dbInitEntry(pdbbase,pdbentry);
status = dbFindRecord(pdbentry,recordname);
if(status) {
printf("Record not found\n");
dbFinishEntry(pdbentry);
return(0);
}
precord = pdbentry->precnode->precord;
dbFinishEntry(pdbentry);
plockRecord = precord->lset;
if(!plockRecord) return(0);
plockSet = plockRecord->plockSet;
} else {
plockSet = (lockSet *)ellFirst(&lockList);
}
for( ; plockSet; plockSet = (lockSet *)ellNext(&plockSet->node)) {
double lockSeconds;
printf("Lock Set %lu %d members",
plockSet->id,ellCount(&plockSet->recordList));
if(semMutexTakeNoWait(plockSet->lock)==semTakeOK) {
semMutexGive(plockSet->lock);
printf(" Not Locked\n");
} else {
lockSeconds = plockSet->start_time;
lockSeconds = (clockGetCurrentTick() - lockSeconds) / clockGetRate();
printf(" Locked %f seconds", lockSeconds);
printf(" thread %p",plockSet->thread_id);
if(! plockSet->precord || !plockSet->precord->name)
printf(" NULL record or record name\n");
else
printf(" record %s\n",plockSet->precord->name);
}
if(level==0) {
if(recordname) break;
continue;
}
for(plockRecord = (lockRecord *)ellFirst(&plockSet->recordList);
plockRecord; plockRecord = (lockRecord *)ellNext(&plockRecord->node)) {
precord = plockRecord->precord;
pdbRecordType = precord->rdes;
printf("%s\n",precord->name);
if(level<=1) continue;
for(link=0; (link<pdbRecordType->no_links) ; link++) {
DBADDR *pdbAddr;
pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[link]];
plink = (DBLINK *)((char *)precord + pdbFldDes->offset);
if(plink->type != DB_LINK) continue;
pdbAddr = (DBADDR *)(plink->value.pv_link.pvt);
printf("\t%s",pdbFldDes->name);
if(pdbFldDes->field_type==DBF_INLINK) {
printf("\t INLINK");
} else if(pdbFldDes->field_type==DBF_OUTLINK) {
printf("\tOUTLINK");
} else if(pdbFldDes->field_type==DBF_FWDLINK) {
printf("\tFWDLINK");
}
printf(" %s %s",
((plink->value.pv_link.pvlMask&pvlOptPP)?" PP":"NPP"),
((plink->value.pv_link.pvlMask&pvlOptMS)?" MS":"NMS"));
printf(" %s\n",pdbAddr->precord->name);
}
}
if(recordname) break;
}
return(0);
}