Skip to content

Commit

Permalink
Merge pull request #171 from exzombie/destructible_ports
Browse files Browse the repository at this point in the history
Destructible ports
  • Loading branch information
MarkRivers authored Nov 5, 2024
2 parents 9519475 + 30b8c87 commit 0ba95cd
Show file tree
Hide file tree
Showing 10 changed files with 854 additions and 146 deletions.
5 changes: 4 additions & 1 deletion asyn/asynDriver/asynDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ typedef enum {
typedef enum {
asynExceptionConnect,asynExceptionEnable,asynExceptionAutoConnect,
asynExceptionTraceMask,asynExceptionTraceIOMask,asynExceptionTraceInfoMask,
asynExceptionTraceFile,asynExceptionTraceIOTruncateSize
asynExceptionTraceFile,asynExceptionTraceIOTruncateSize,
asynExceptionShutdown
} asynException;

#define ASYN_EXCEPTION_STRINGS \
Expand Down Expand Up @@ -93,6 +94,7 @@ typedef struct asynInterface{
/*registerPort attributes*/
#define ASYN_MULTIDEVICE 0x0001
#define ASYN_CANBLOCK 0x0002
#define ASYN_DESTRUCTIBLE 0x0004

/*standard values for asynUser.reason*/
#define ASYN_REASON_SIGNAL -1
Expand Down Expand Up @@ -156,6 +158,7 @@ typedef struct asynManager {
asynInterface *pasynInterface,
asynInterface **ppPrev);
asynStatus (*enable)(asynUser *pasynUser,int yesNo);
asynStatus (*shutdownPort)(asynUser *pasynUser);
asynStatus (*autoConnect)(asynUser *pasynUser,int yesNo);
asynStatus (*isConnected)(asynUser *pasynUser,int *yesNo);
asynStatus (*isEnabled)(asynUser *pasynUser,int *yesNo);
Expand Down
195 changes: 191 additions & 4 deletions asyn/asynDriver/asynManager.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <epicsTimer.h>
#include <cantProceed.h>
#include <epicsAssert.h>
#include <epicsExit.h>

#include <epicsExport.h>
#include "asynDriver.h"
Expand Down Expand Up @@ -136,6 +137,7 @@ typedef struct interfaceNode {

typedef struct dpCommon { /*device/port common fields*/
BOOL enabled;
BOOL defunct;
BOOL connected;
BOOL autoConnect;
BOOL autoConnectActive;
Expand Down Expand Up @@ -308,6 +310,7 @@ static asynStatus exceptionDisconnect(asynUser *pasynUser);
static asynStatus interposeInterface(const char *portName, int addr,
asynInterface *pasynInterface,asynInterface **ppPrev);
static asynStatus enable(asynUser *pasynUser,int yesNo);
static asynStatus shutdownPort(asynUser *pasynUser);
static asynStatus autoConnectAsyn(asynUser *pasynUser,int yesNo);
static asynStatus isConnected(asynUser *pasynUser,int *yesNo);
static asynStatus isEnabled(asynUser *pasynUser,int *yesNo);
Expand Down Expand Up @@ -365,6 +368,7 @@ static asynManager manager = {
exceptionDisconnect,
interposeInterface,
enable,
shutdownPort,
autoConnectAsyn,
isConnected,
isEnabled,
Expand Down Expand Up @@ -513,6 +517,7 @@ static void dpCommonInit(port *pport,device *pdevice,BOOL autoConnect)
pdpCommon = &pport->dpc;
}
pdpCommon->enabled = TRUE;
pdpCommon->defunct = FALSE;
pdpCommon->connected = FALSE;
pdpCommon->autoConnect = autoConnect;
ellInit(&pdpCommon->interposeInterfaceList);
Expand Down Expand Up @@ -1031,6 +1036,10 @@ static void reportPrintPort(printPortArgs *pprintPortArgs)
for(i=asynQueuePriorityLow; i<=asynQueuePriorityConnect; i++)
nQueued += ellCount(&pport->queueList[i]);
pdpc = &pport->dpc;
if (pdpc->defunct) {
fprintf(fp,"%s destroyed\n", pport->portName);
goto done;
}
fprintf(fp,"%s multiDevice:%s canBlock:%s autoConnect:%s\n",
pport->portName,
((pport->attributes&ASYN_MULTIDEVICE) ? "Yes" : "No"),
Expand Down Expand Up @@ -1112,6 +1121,8 @@ static void reportPrintPort(printPortArgs *pprintPortArgs)
if(pasynCommon) {
pasynCommon->report(drvPvt,fp,details);
}

done:
#ifdef CYGWIN32
/* This is a (hopefully) temporary fix for a problem with POSIX threads on Cygwin.
* If a thread is very short-lived, which this report thread will be if the amount of
Expand Down Expand Up @@ -1312,9 +1323,17 @@ static asynStatus connectDevice(asynUser *pasynUser,
const char *portName, int addr)
{
userPvt *puserPvt = asynUserToUserPvt(pasynUser);
port *pport = locatePort(portName);
port *pport;
device *pdevice;

if(!portName) {
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
"asynManager:connectDevice no port name provided");
return asynError;
}

pport = locatePort(portName);

if(!pport) {
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
"asynManager:connectDevice port %s not found",portName);
Expand Down Expand Up @@ -1460,20 +1479,36 @@ static asynInterface *findInterface(asynUser *pasynUser,
"asynManager:findInterface: not connected");
return 0;
}

epicsMutexMustLock(pport->asynManagerLock);

if (pport->dpc.defunct) {
epicsMutexUnlock(pport->asynManagerLock);
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
"asynManager:findInterface: port shut down");
return 0;
}

if(interposeInterfaceOK) {
if(pdevice) {
pinterfaceNode = locateInterfaceNode(
&pdevice->dpc.interposeInterfaceList, interfaceType,FALSE);
if(pinterfaceNode) return(pinterfaceNode->pasynInterface);
if(pinterfaceNode) goto retif;
}
pinterfaceNode = locateInterfaceNode(
&pport->dpc.interposeInterfaceList, interfaceType,FALSE);
if(pinterfaceNode) return(pinterfaceNode->pasynInterface);
if(pinterfaceNode) goto retif;
}
pinterfaceNode = locateInterfaceNode(
&pport->interfaceList,interfaceType,FALSE);
if(pinterfaceNode) return(pinterfaceNode->pasynInterface);
if(pinterfaceNode) goto retif;

epicsMutexUnlock(pport->asynManagerLock);
return 0;

retif:
epicsMutexUnlock(pport->asynManagerLock);
return pinterfaceNode->pasynInterface;
}

static asynStatus queueRequest(asynUser *pasynUser,
Expand Down Expand Up @@ -1748,8 +1783,20 @@ static asynStatus lockPort(asynUser *pasynUser)
"asynManager::lockPort not connected\n");
return asynError;
}

asynPrint(pasynUser,ASYN_TRACE_FLOW,"%s lockPort\n", pport->portName);

epicsMutexMustLock(pport->asynManagerLock);
if (findDpCommon(puserPvt)->defunct) {
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
"asynManager:lockPort: port has been shut down");
epicsMutexUnlock(pport->asynManagerLock);
return asynDisabled;
}
epicsMutexUnlock(pport->asynManagerLock);

epicsMutexMustLock(pport->synchronousLock);

if(pport->pasynLockPortNotify) {
pport->pasynLockPortNotify->lock(
pport->lockPortNotifyPvt,pasynUser);
Expand All @@ -1767,6 +1814,7 @@ static asynStatus unlockPort(asynUser *pasynUser)
"asynManager::unlockPort not connected\n");
return asynError;
}

asynPrint(pasynUser,ASYN_TRACE_FLOW, "%s unlockPort\n", pport->portName);
if(pport->pasynLockPortNotify) {
asynStatus status;
Expand Down Expand Up @@ -1796,6 +1844,16 @@ static asynStatus queueLockPort(asynUser *pasynUser)
return asynError;
}
asynPrint(pasynUser,ASYN_TRACE_FLOW, "%s asynManager::queueLockPort locking port\n", pport->portName);

epicsMutexMustLock(pport->asynManagerLock);
if(!pport->dpc.enabled) {
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
"port %s disabled",pport->portName);
epicsMutexUnlock(pport->asynManagerLock);
return asynDisabled;
}
epicsMutexUnlock(pport->asynManagerLock);

if (pport->attributes & ASYN_CANBLOCK) { /* Asynchronous driver */
plockPortPvt = epicsThreadPrivateGet(pport->queueLockPortId);
if (!plockPortPvt) {
Expand Down Expand Up @@ -1965,6 +2023,25 @@ static asynStatus getPortName(asynUser *pasynUser,const char **pportName)
return asynSuccess;
}

static void destroyPortDriver(void *portName) {
asynStatus status;
asynUser *pasynUser = pasynManager->createAsynUser(0, 0);
status = pasynManager->connectDevice(pasynUser, portName, 0);
if(status != asynSuccess) {
printf("%s\n", pasynUser->errorMessage);
pasynManager->freeAsynUser(pasynUser);
return;
}
status = pasynManager->shutdownPort(pasynUser);
if(status != asynSuccess) {
printf("%s\n", pasynUser->errorMessage);
}
status = pasynManager->freeAsynUser(pasynUser);
if(status != asynSuccess) {
printf("%s\n", pasynUser->errorMessage);
}
}

static asynStatus registerPort(const char *portName,
int attributes,int autoConnect,
unsigned int priority,unsigned int stackSize)
Expand Down Expand Up @@ -2016,6 +2093,9 @@ static asynStatus registerPort(const char *portName,
epicsMutexMustLock(pasynBase->lock);
ellAdd(&pasynBase->asynPortList,&pport->node);
epicsMutexUnlock(pasynBase->lock);
if (attributes & ASYN_DESTRUCTIBLE) {
epicsAtExit(destroyPortDriver, (void *)pport->portName);
}
return asynSuccess;
}

Expand Down Expand Up @@ -2150,11 +2230,83 @@ static asynStatus enable(asynUser *pasynUser,int yesNo)
"asynManager:enable not connected");
return asynError;
}

epicsMutexMustLock(pport->asynManagerLock);

if (pdpCommon->defunct) {
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
"asynManager:enable: port has been shut down");
epicsMutexUnlock(pport->asynManagerLock);
return asynDisabled;
}

pdpCommon->enabled = (yesNo ? 1 : 0);

epicsMutexUnlock(pport->asynManagerLock);

exceptionOccurred(pasynUser,asynExceptionEnable);
return asynSuccess;
}

static asynStatus shutdownPort(asynUser *pasynUser)
{
userPvt *puserPvt = asynUserToUserPvt(pasynUser);
port *pport = puserPvt->pport;
dpCommon *pdpCommon = findDpCommon(puserPvt);
interfaceNode *pinterfaceNode;

if (!pport || !pdpCommon) {
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
"asynManager:shutdownPort: not connected");
return asynError;
}

epicsMutexMustLock(pdpCommon->pport->asynManagerLock);

if (pdpCommon->defunct) {
epicsMutexUnlock(pdpCommon->pport->asynManagerLock);
return asynSuccess;
}

if (!(pport->attributes & ASYN_DESTRUCTIBLE)) {
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
"asynManager:shutdownPort: port does not support shutting down");
epicsMutexUnlock(pdpCommon->pport->asynManagerLock);
return asynError;
}

/*
* Disabling the port short-circuits queueRequest(), preventing usage of
* external asynUser instances that will shortly become dangling references.
* It is marked defunct so it cannot be re-enabled.
*/
pdpCommon->enabled = FALSE;
pdpCommon->defunct = TRUE;

/*
* Nullifying the private pointers to the driver enables trapping on
* erroneous accesses, making finding bugs easier.
*/
pport->pasynLockPortNotify = NULL;
pport->lockPortNotifyPvt = NULL;
pinterfaceNode = (interfaceNode *)ellFirst(&pport->interfaceList);
while(pinterfaceNode) {
asynInterface *pif = pinterfaceNode->pasynInterface;
pif->drvPvt = NULL;
pinterfaceNode = (interfaceNode *)ellNext(&pinterfaceNode->node);
}

epicsMutexUnlock(pdpCommon->pport->asynManagerLock);

/*
* Actual destruction of the driver is delegated to the driver itself, which
* shall implement an exception handler.
*/
exceptionOccurred(pasynUser, asynExceptionShutdown);

return asynSuccess;
}

static asynStatus autoConnectAsyn(asynUser *pasynUser,int yesNo)
{
userPvt *puserPvt = asynUserToUserPvt(pasynUser);
Expand Down Expand Up @@ -2195,7 +2347,9 @@ static asynStatus isEnabled(asynUser *pasynUser,int *yesNo)
"asynManager:isEnabled asynUser not connected to device");
return asynError;
}
epicsMutexMustLock(pdpCommon->pport->asynManagerLock);
*yesNo = pdpCommon->enabled;
epicsMutexUnlock(pdpCommon->pport->asynManagerLock);
return asynSuccess;
}

Expand Down Expand Up @@ -2250,7 +2404,15 @@ static asynStatus registerInterruptSource(const char *portName,
portName);
return asynError;
}

epicsMutexMustLock(pport->asynManagerLock);

if (pport->dpc.defunct) {
epicsMutexUnlock(pport->asynManagerLock);
printf("%s asynManager:registerInterrupt: port shut down", pport->portName);
return asynDisabled;
}

pinterfaceNode = locateInterfaceNode(
&pport->interfaceList,pasynInterface->interfaceType,FALSE);
if(!pinterfaceNode)
Expand Down Expand Up @@ -2293,7 +2455,16 @@ static asynStatus getInterruptPvt(asynUser *pasynUser,
"asynManager:getInterruptPvt not connected to a port");
return asynError;
}

epicsMutexMustLock(pport->asynManagerLock);

if (pport->dpc.defunct) {
epicsMutexUnlock(pport->asynManagerLock);
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
"asynManager:getInterruptPvt: port shut down");
return asynDisabled;
}

pinterfaceNode = locateInterfaceNode(
&pport->interfaceList,interfaceType,FALSE);
if(!pinterfaceNode)
Expand Down Expand Up @@ -2372,6 +2543,14 @@ static asynStatus addInterruptUser(asynUser *pasynUser,
port *pport = pinterruptBase->pport;

epicsMutexMustLock(pport->asynManagerLock);

if (pport->dpc.defunct) {
epicsMutexUnlock(pport->asynManagerLock);
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
"asynManager:assInterruptUser: port shut down");
return asynDisabled;
}

if(pinterruptNodePvt->isOnList) {
epicsMutexUnlock(pport->asynManagerLock);
epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize,
Expand Down Expand Up @@ -2406,6 +2585,14 @@ static asynStatus removeInterruptUser(asynUser *pasynUser,
port *pport = pinterruptBase->pport;

epicsMutexMustLock(pport->asynManagerLock);

if (pport->dpc.defunct) {
epicsMutexUnlock(pport->asynManagerLock);
epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize,
"asynManager:removeInterruptUser: port shut down");
return asynDisabled;
}

if(!pinterruptNodePvt->isOnList) {
epicsMutexUnlock(pport->asynManagerLock);
epicsSnprintf(pasynUser->errorMessage, pasynUser->errorMessageSize,
Expand Down
Loading

0 comments on commit 0ba95cd

Please sign in to comment.