Files
pcas/src/db/dbLock.c
1998-05-20 20:59:46 +00:00

504 lines
15 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 tasks from simultaneously
accessing records in set, dynamically changing lock sets presents a problem.
Four problems arise:
1) Two tasks simultaneoulsy trying to change lock sets
2) Another task has successfully issued a dbScanLock and currently owns it.
3) A task is waiting for dbScanLock.
4) While lock set is being changed, a task 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 tasks in semTake for that semaphore fail.
The code in dbScanLock makes task recover.
4) The global variable changingLockSets and code in
dbScanLock and semFlush in dbLockSetGblUnlock solves
this problem.
Note that all other tasks 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 tasks.
*****************************************************************************/
#include <vxWorks.h>
#include <lstLib.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <semLib.h>
#include <tickLib.h>
#include <sysLib.h>
#include <taskLib.h>
#include "dbDefs.h"
#include "dbBase.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 SEM_ID globalLockSemId;
STATIC SEM_ID globalWaitSemid;
STATIC unsigned long id = 0;
STATIC int changingLockSets = FALSE;
typedef struct lockSet {
ELLNODE node;
ELLLIST recordList;
SEM_ID semId;
ULONG start_time;
int task_id;
dbCommon *precord;
unsigned long id;
} lockSet;
typedef struct lockRecord {
ELLNODE node;
lockSet *plockSet;
dbCommon *precord;
} lockRecord;
#define semMCoptions SEM_DELETE_SAFE|SEM_INVERSION_SAFE|SEM_Q_PRIORITY
/*private routines */
STATIC void initLockList(void)
{
ellInit(&lockList);
if((globalLockSemId = semMCreate(semMCoptions))==0) {
errMessage(0,"allocLockNode called semMCreate\n");
exit(-1);
}
if((globalWaitSemid = semBCreate(SEM_Q_FIFO,SEM_EMPTY))==0) {
errMessage(0,"allocLockNode called semBCreate\n");
exit(-1);
}
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->semId = semMCreate(semMCoptions))==0) {
errMessage(0,"allocLockNode called semMCreate\n");
exit(-1);
}
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)
{
STATUS status;
if(!lockListInitialized) initLockList();
status = semTake(globalLockSemId,WAIT_FOREVER);
if(status!=OK) {
epicsPrintf("dbLockSetGblLock failure\n");
taskSuspend(0);
}
changingLockSets = TRUE;
}
void dbLockSetGblUnlock(void)
{
taskLock();
changingLockSets = FALSE;
semFlush(globalWaitSemid);
taskUnlock();
semGive(globalLockSemId);
return;
}
void dbLockSetRecordLock(dbCommon *precord)
{
lockRecord *plockRecord = (lockRecord *)precord->lset;
lockSet *plockSet;
STATUS status;
/*Make sure that dbLockSetGblLock was called*/
if(!changingLockSets) {
epicsPrintf("dbLockSetRecordLock called before dbLockSetGblLock\n");
taskSuspend(0);
}
/*Must make sure that no other task has lock*/
if(!plockRecord) return;
plockSet = plockRecord->plockSet;
if(!plockSet) return;
if(plockSet->task_id==taskIdSelf()) return;
/*Wait for up to 1 minute*/
status = semTake(plockRecord->plockSet->semId,sysClkRateGet()*60);
if(status==OK) {
plockSet->start_time = tickGet();
plockSet->task_id = taskIdSelf();
plockSet->precord = (void *)precord;
/*give it back in case it will not be changed*/
semGive(plockRecord->plockSet->semId);
return;
}
/*Should never reach this point*/
epicsPrintf("dbLockSetRecordLock timeout caller 0x%x owner 0x%x",
taskIdSelf(),plockSet->task_id);
epicsPrintf(" record %s\n",precord->name);
return;
}
void dbScanLock(dbCommon *precord)
{
lockRecord *plockRecord;
lockSet *plockSet;
STATUS status;
if(!(plockRecord= (lockRecord *)precord->lset)) {
epicsPrintf("dbScanLock plockRecord is NULL record %s\n",
precord->name);
exit(1);
}
while(TRUE) {
if(changingLockSets) {
semTake(globalWaitSemid,WAIT_FOREVER);
continue;
}
status = semTake(plockRecord->plockSet->semId,WAIT_FOREVER);
/*semTake fails if semDelete was called while active*/
if(status==OK) break;
}
plockSet = plockRecord->plockSet;
plockSet->start_time = tickGet();
plockSet->task_id = taskIdSelf();
plockSet->precord = (void *)precord;
return;
}
void dbScanUnlock(dbCommon *precord)
{
lockRecord *plockRecord = (lockRecord *)precord->lset;
if(!plockRecord || !plockRecord->plockSet) {
epicsPrintf("dbScanUnlock plockRecord or plockRecord->plockSet NULL\n");
return;
}
semGive(plockRecord->plockSet->semId);
return;
}
unsigned long dbLockGetLockId(dbCommon *precord)
{
lockRecord *plockRecord = (lockRecord *)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 = (lockRecord *)precord->lset;
if(!plockRecord->plockSet) allocLock(plockRecord);
}
}
}
void dbLockSetMerge(dbCommon *pfirst,dbCommon *psecond)
{
lockRecord *p1lockRecord = (lockRecord *)pfirst->lset;
lockRecord *p2lockRecord = (lockRecord *)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);
if(semDelete(p2lockSet->semId)!=OK) {
errMessage(0,"dbLockSetMerge calling semDelete");
taskSuspend(0);
}
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 = (lockRecord *)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 = (lockRecord *)precord->lset;
if(!(precord->name[0])) continue;
pdbRecordType = (dbRecordType *)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);
}
if(semDelete(plockSet->semId)!=OK) {
errMessage(0,"dbLockSetSplit calling semDelete");
taskSuspend(0);
}
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 = (lockRecord *)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(semTake(plockSet->semId,NO_WAIT)==OK) {
semGive(plockSet->semId);
printf(" Not Locked\n");
} else {
lockSeconds = plockSet->start_time;
lockSeconds = (tickGet() - lockSeconds) / sysClkRateGet();
printf(" Locked %f seconds", lockSeconds);
printf(" task 0x%x",plockSet->task_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 = (dbRecordType *)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);
}