Files
epics-base/src/ca/cac.cpp
Jeff Hill 47729fed41 added preemptive callback control
reinstalled flow control
2000-06-22 23:59:51 +00:00

909 lines
21 KiB
C++

/* $Id$
*
* L O S A L A M O S
* Los Alamos National Laboratory
* Los Alamos, New Mexico 87545
*
* Copyright, 1986, The Regents of the University of California.
*
* Author: Jeff Hill
*/
#include "osiProcess.h"
#include "osiSigPipeIgnore.h"
#include "iocinf.h"
#include "inetAddrID_IL.h"
#include "bhe_IL.h"
extern "C" void cacRecursionLockExitHandler ()
{
if ( cacRecursionLock ) {
threadPrivateDelete ( cacRecursionLock );
cacRecursionLock = 0;
}
}
static void cacInitRecursionLock ( void * dummy )
{
cacRecursionLock = threadPrivateCreate ();
if ( cacRecursionLock ) {
atexit ( cacRecursionLockExitHandler );
}
}
//
// cac::cac ()
//
cac::cac ( bool enablePreemptiveCallbackIn ) :
beaconTable ( 1024 ),
endOfBCastList ( 0 ),
ioTable ( 1024 ),
chanTable ( 1024 ),
sgTable ( 128 ),
fdRegFunc ( 0 ),
fdRegArg ( 0 ),
pndrecvcnt ( 0 ),
enablePreemptiveCallback ( enablePreemptiveCallbackIn )
{
long status;
static threadOnceId once = OSITHREAD_ONCE_INIT;
unsigned abovePriority;
threadOnce ( &once, cacInitRecursionLock, 0);
if ( cacInitRecursionLock == 0 ) {
throwWithLocation ( caErrorCode (ECA_ALLOCMEM) );
}
if ( ! osiSockAttach () ) {
throwWithLocation ( caErrorCode (ECA_INTERNAL) );
}
{
threadBoolStatus tbs;
unsigned selfPriority = threadGetPrioritySelf ();
tbs = threadLowestPriorityLevelAbove ( selfPriority, &abovePriority);
if ( tbs != tbsSuccess ) {
abovePriority = selfPriority;
}
}
this->pTimerQueue = new osiTimerQueue ( abovePriority );
if ( ! this->pTimerQueue ) {
throwWithLocation ( caErrorCode (ECA_ALLOCMEM) );
}
ellInit (&this->ca_taskVarList);
ellInit (&this->putCvrtBuf);
ellInit (&this->fdInfoFreeList);
ellInit (&this->fdInfoList);
this->ca_printf_func = errlogVprintf;
this->pudpiiu = NULL;
this->ca_exception_func = ca_default_exception_handler;
this->ca_exception_arg = NULL;
this->ca_number_iiu_in_fc = 0u;
this->readSeq = 0u;
this->ca_io_done_sem = semBinaryCreate(semEmpty);
if (!this->ca_io_done_sem) {
throwWithLocation ( caErrorCode (ECA_ALLOCMEM) );
}
this->ca_blockSem = semBinaryCreate(semEmpty);
if (!this->ca_blockSem) {
semBinaryDestroy (this->ca_io_done_sem);
throwWithLocation ( caErrorCode (ECA_ALLOCMEM) );
}
installSigPipeIgnore ();
{
char tmp[256];
size_t len;
osiGetUserNameReturn gunRet;
gunRet = osiGetUserName ( tmp, sizeof (tmp) );
if ( gunRet != osiGetUserNameSuccess ) {
tmp[0] = '\0';
}
len = strlen (tmp) + 1;
this->ca_pUserName = (char *) malloc ( len );
if ( ! this->ca_pUserName ) {
semBinaryDestroy (this->ca_io_done_sem);
semBinaryDestroy (this->ca_blockSem);
throwWithLocation ( caErrorCode (ECA_ALLOCMEM) );
}
strncpy (this->ca_pUserName, tmp, len);
}
/* record the host name */
this->ca_pHostName = localHostName();
if ( ! this->ca_pHostName ) {
semBinaryDestroy (this->ca_io_done_sem);
semBinaryDestroy (this->ca_blockSem);
free (this->ca_pUserName);
throwWithLocation ( caErrorCode (ECA_ALLOCMEM) );
}
this->programBeginTime = osiTime::getCurrent ();
status = envGetDoubleConfigParam (&EPICS_CA_CONN_TMO, &this->ca_connectTMO);
if (status) {
this->ca_connectTMO = CA_CONN_VERIFY_PERIOD;
ca_printf (
"EPICS \"%s\" float fetch failed\n",
EPICS_CA_CONN_TMO.name);
ca_printf (
"Setting \"%s\" = %f\n",
EPICS_CA_CONN_TMO.name,
this->ca_connectTMO);
}
this->ca_server_port =
envGetInetPortConfigParam (&EPICS_CA_SERVER_PORT, CA_SERVER_PORT);
//
// unfortunately, this must be created her in the
// constructor, and not on demand (only when it is needed)
// because the enable reference count must be
// maintained whenever this object exists.
//
this->pProcThread = new processThread ( this );
if ( ! this->pProcThread ) {
throwWithLocation ( caErrorCode (ECA_ALLOCMEM) );
}
else if ( this->enablePreemptiveCallback ) {
// only after this->pProcThread is valid
this->enableCallbackPreemption ();
}
}
/*
* cac::~cac ()
*
* releases all resources alloc to a channel access client
*/
cac::~cac ()
{
this->enableCallbackPreemption ();
//
// destroy local IO channels
//
this->defaultMutex.lock ();
tsDLIterBD <cacLocalChannelIO> iter ( this->localChanList.first () );
while ( iter != tsDLIterBD <cacLocalChannelIO> ::eol () ) {
tsDLIterBD <cacLocalChannelIO> pnext = iter.itemAfter ();
iter->destroy ();
iter = pnext;
}
this->defaultMutex.unlock ();
//
// make certain that process thread isnt deleting
// tcpiiu objects at the same that this thread is
//
delete this->pProcThread;
//
// shutdown all tcp connections and wait for threads to exit
//
this->iiuListMutex.lock ();
tsDLIterBD <tcpiiu> piiu ( this->iiuListIdle.first () );
while ( piiu != piiu.eol () ) {
tsDLIterBD <tcpiiu> pnext = piiu.itemAfter ();
piiu->suicide ();
piiu = pnext;
}
piiu = this->iiuListRecvPending.first ();
while ( piiu != piiu.eol () ) {
tsDLIterBD <tcpiiu> pnext = piiu.itemAfter ();
piiu->suicide ();
piiu = pnext;
}
this->iiuListMutex.unlock ();
this->defaultMutex.lock ();
//
// shutdown udp and wait for threads to exit
//
if ( this->pudpiiu ) {
if ( ! this->enablePreemptiveCallback ) {
if ( this->fdRegFunc ) {
( *this->fdRegFunc )
( this->fdRegArg, this->pudpiiu->getSock (), FALSE );
}
}
delete this->pudpiiu;
}
/* remove put convert block free list */
ellFree ( &this->putCvrtBuf );
/* reclaim sync group resources */
this->sgTable.destroyAllEntries ();
/* free select context lists */
ellFree ( &this->fdInfoFreeList );
ellFree ( &this->fdInfoList );
/*
* free user name string
*/
if ( this->ca_pUserName ) {
free ( this->ca_pUserName );
}
/*
* free host name string
*/
if ( this->ca_pHostName ) {
free ( this->ca_pHostName );
}
this->beaconTable.destroyAllEntries ();
semBinaryDestroy ( this->ca_io_done_sem );
semBinaryDestroy ( this->ca_blockSem );
osiSockRelease ();
delete this->pTimerQueue;
}
void cac::safeDestroyNMIU (unsigned id)
{
this->defaultMutex.lock ();
baseNMIU *pIOBlock = this->ioTable.lookup (id);
if ( pIOBlock ) {
pIOBlock->destroy ();
}
this->defaultMutex.unlock ();
}
void cac::processRecvBacklog ()
{
tcpiiu *piiu;
while ( 1 ) {
int status;
unsigned bytesToProcess;
this->iiuListMutex.lock ();
piiu = this->iiuListRecvPending.get ();
if ( ! piiu ) {
this->iiuListMutex.unlock ();
break;
}
piiu->recvPending = false;
this->iiuListIdle.add (*piiu);
this->iiuListMutex.unlock ();
if ( piiu->state == iiu_disconnected ) {
delete piiu;
continue;
}
char *pProto = (char *) cacRingBufferReadReserveNoBlock
(&piiu->recv, &bytesToProcess);
while ( pProto ) {
status = piiu->post_msg (pProto, bytesToProcess);
if ( status == ECA_NORMAL ) {
cacRingBufferReadCommit (&piiu->recv, bytesToProcess);
cacRingBufferReadFlush (&piiu->recv);
}
else {
delete piiu;
}
pProto = (char *) cacRingBufferReadReserveNoBlock
(&piiu->recv, &bytesToProcess);
}
}
}
/*
* cac::flush ()
*/
void cac::flush ()
{
/*
* set the push pending flag on all virtual circuits
*/
this->iiuListMutex.lock ();
tsDLIterBD<tcpiiu> piiu ( this->iiuListIdle.first () );
while ( piiu != tsDLIterBD<tcpiiu>::eol () ) {
piiu->flush ();
piiu++;
}
piiu = this->iiuListRecvPending.first ();
while ( piiu != tsDLIterBD<tcpiiu>::eol () ) {
piiu->flush ();
piiu++;
}
this->iiuListMutex.unlock ();
}
/*
*
* set pending IO count back to zero and
* send a sync to each IOC and back. dont
* count reads until we recv the sync
*
*/
void cac::cleanUpPendIO ()
{
nciu *pchan;
this->defaultMutex.lock ();
this->readSeq++;
this->pndrecvcnt = 0u;
if ( this->pudpiiu ) {
tsDLIter <nciu> iter ( this->pudpiiu->chidList );
while ( ( pchan = iter () ) ) {
pchan->connectTimeoutNotify ();
}
}
this->defaultMutex.unlock ();
}
unsigned cac::connectionCount () const
{
unsigned count;
this->iiuListMutex.lock ();
count = this->iiuListIdle.count () + this->iiuListRecvPending.count ();
this->iiuListMutex.unlock ();
return count;
}
void cac::show (unsigned level) const
{
this->defaultMutex.lock ();
if ( this->pudpiiu ) {
this->pudpiiu->show (level);
}
this->iiuListMutex.lock ();
tsDLIterConstBD <tcpiiu> piiu ( this->iiuListIdle.first () );
while ( piiu != piiu.eol () ) {
piiu->show (level);
piiu++;
}
piiu = this->iiuListRecvPending.first ();
while ( piiu != piiu.eol () ) {
piiu->show (level);
piiu++;
}
this->iiuListMutex.unlock ();
this->defaultMutex.unlock ();
}
void cac::installIIU ( tcpiiu &iiu )
{
this->iiuListMutex.lock ();
iiu.recvPending = false;
this->iiuListIdle.add (iiu);
this->iiuListMutex.unlock ();
this->defaultMutex.lock ();
if ( ! this->enablePreemptiveCallback && this->fdRegFunc ) {
( * this->fdRegFunc )
( (void *) this->fdRegArg, iiu.getSock (), TRUE );
}
this->defaultMutex.unlock ();
}
void cac::signalRecvActivityIIU (tcpiiu &iiu)
{
bool change;
this->iiuListMutex.lock ();
if ( iiu.recvPending ) {
change = false;
}
else {
this->iiuListIdle.remove (iiu);
this->iiuListRecvPending.add (iiu);
iiu.recvPending = true;
change = true;
}
this->iiuListMutex.unlock ();
//
// wakeup after unlock improves performance
//
if (change) {
this->recvActivity.signal ();
}
}
void cac::removeIIU (tcpiiu &iiu)
{
this->iiuListMutex.lock ();
if ( iiu.recvPending ) {
this->iiuListRecvPending.remove (iiu);
}
else {
this->iiuListIdle.remove (iiu);
}
if ( ! this->enablePreemptiveCallback ) {
if ( this->fdRegFunc ) {
(*this->fdRegFunc)
((void *)this->fdRegArg, iiu.getSock (), FALSE);
}
}
this->iiuListMutex.unlock ();
}
/*
* cac::lookupBeaconInetAddr()
*
*/
bhe * cac::lookupBeaconInetAddr (const inetAddrID &ina)
{
bhe *pBHE;
this->defaultMutex.lock ();
pBHE = this->beaconTable.lookup (ina);
this->defaultMutex.unlock ();
return pBHE;
}
/*
* cac::createBeaconHashEntry ()
*/
bhe *cac::createBeaconHashEntry (const inetAddrID &ina, const osiTime &initialTimeStamp)
{
bhe *pBHE;
this->defaultMutex.lock ();
pBHE = this->beaconTable.lookup ( ina );
if ( !pBHE ) {
pBHE = new bhe (*this, initialTimeStamp, ina);
if ( pBHE ) {
if ( this->beaconTable.add (*pBHE) < 0 ) {
pBHE->destroy ();
pBHE = 0;
}
}
}
this->defaultMutex.unlock ();
return pBHE;
}
/*
* cac::beaconNotify
*/
void cac::beaconNotify ( const inetAddrID &addr )
{
bhe *pBHE;
unsigned port;
int netChange;
if ( ! this->pudpiiu ) {
return;
}
this->defaultMutex.lock ();
/*
* look for it in the hash table
*/
pBHE = this->lookupBeaconInetAddr ( addr );
if ( pBHE ) {
netChange = pBHE->updateBeaconPeriod ( this->programBeginTime );
}
else {
/*
* This is the first beacon seen from this server.
* Wait until 2nd beacon is seen before deciding
* if it is a new server (or just the first
* time that we have seen a server's beacon
* shortly after the program started up)
*/
netChange = FALSE;
this->createBeaconHashEntry ( addr, osiTime::getCurrent () );
}
if ( ! netChange ) {
this->defaultMutex.unlock ();
return;
}
/*
* This part is needed when many machines
* have channels in a disconnected state that
* dont exist anywhere on the network. This insures
* that we dont have many CA clients synchronously
* flooding the network with broadcasts (and swamping
* out requests for valid channels).
*
* I fetch the local port number and use the low order bits
* as a pseudo random delay to prevent every one
* from replying at once.
*/
{
struct sockaddr_in saddr;
int saddr_length = sizeof(saddr);
int status;
status = getsockname ( this->pudpiiu->getSock (), (struct sockaddr *) &saddr, &saddr_length );
if ( status < 0 ) {
epicsPrintf ( "CAC: getsockname () error was \"%s\"\n", SOCKERRSTR (SOCKERRNO) );
return;
}
port = ntohs ( saddr.sin_port );
}
{
ca_real delay;
delay = ( port & CA_RECAST_PORT_MASK );
delay /= MSEC_PER_SEC;
delay += CA_RECAST_DELAY;
this->pudpiiu->searchTmr.reset ( delay );
}
/*
* set retry count of all disconnected channels
* to zero
*/
tsDLIterBD <nciu> iter ( this->pudpiiu->chidList.first () );
while ( iter != tsDLIterBD<nciu>::eol () ) {
iter->retry = 0u;
iter++;
}
this->defaultMutex.unlock ();
# if DEBUG
{
char buf[64];
ipAddrToA (pnet_addr, buf, sizeof (buf) );
printf ("new server available: %s\n", buf);
}
# endif
}
/*
* cac::removeBeaconInetAddr ()
*/
void cac::removeBeaconInetAddr (const inetAddrID &ina)
{
bhe *pBHE;
this->defaultMutex.lock ();
pBHE = this->beaconTable.remove ( ina );
this->defaultMutex.unlock ();
assert (pBHE);
}
void cac::decrementOutstandingIO (unsigned seqNumber)
{
this->defaultMutex.lock ();
if ( this->readSeq == seqNumber ) {
if ( this->pndrecvcnt > 0u ) {
this->pndrecvcnt--;
}
}
this->defaultMutex.unlock ();
if ( this->pndrecvcnt == 0u ) {
semBinaryGive (this->ca_io_done_sem);
}
}
void cac::decrementOutstandingIO ()
{
this->defaultMutex.lock ();
if ( this->pndrecvcnt > 0u ) {
this->pndrecvcnt--;
}
this->defaultMutex.unlock ();
if ( this->pndrecvcnt == 0u ) {
semBinaryGive (this->ca_io_done_sem);
}
}
void cac::incrementOutstandingIO ()
{
this->defaultMutex.lock ();
if ( this->pndrecvcnt < UINT_MAX ) {
this->pndrecvcnt++;
}
this->defaultMutex.unlock ();
}
unsigned cac::readSequence () const
{
return this->readSeq;
}
int cac::pend ( double timeout, int early )
{
int status;
void *p;
/*
* dont allow recursion
*/
p = threadPrivateGet ( cacRecursionLock );
if (p) {
return ECA_EVDISALLOW;
}
threadPrivateSet ( cacRecursionLock, &cacRecursionLock );
this->enableCallbackPreemption ();
status = this->pendPrivate ( timeout, early );
this->disableCallbackPreemption ();
threadPrivateSet ( cacRecursionLock, NULL );
return status;
}
/*
* cac::pendPrivate ()
*/
int cac::pendPrivate (double timeout, int early)
{
osiTime cur_time;
osiTime beg_time;
double delay;
this->flush ();
if ( this->pndrecvcnt == 0u && early ) {
return ECA_NORMAL;
}
if ( timeout < 0.0 ) {
if (early) {
this->cleanUpPendIO ();
}
return ECA_TIMEOUT;
}
beg_time = cur_time = osiTime::getCurrent ();
delay = 0.0;
while ( true ) {
ca_real remaining;
if ( timeout == 0.0 ) {
remaining = 60.0;
}
else{
remaining = timeout - delay;
/*
* If we are not waiting for any significant delay
* then force the delay to zero so that we avoid
* scheduling delays (which can be substantial
* on some os)
*/
if ( remaining <= CAC_SIGNIFICANT_SELECT_DELAY ) {
if ( early ) {
this->cleanUpPendIO ();
}
return ECA_TIMEOUT;
}
}
semBinaryTakeTimeout ( this->ca_io_done_sem, remaining );
if ( this->pndrecvcnt == 0 && early ) {
return ECA_NORMAL;
}
cur_time = osiTime::getCurrent ();
if ( timeout != 0.0 ) {
delay = cur_time - beg_time;
}
}
}
bool cac::ioComplete () const
{
if ( this->pndrecvcnt == 0u ) {
return true;
}
else{
return false;
}
}
void cac::installIO ( baseNMIU &io )
{
this->defaultMutex.lock ();
this->ioTable.add ( io );
this->defaultMutex.unlock ();
}
void cac::uninstallIO ( baseNMIU &io )
{
this->defaultMutex.lock ();
this->ioTable.remove ( io );
this->defaultMutex.unlock ();
}
baseNMIU * cac::lookupIO (unsigned id)
{
this->defaultMutex.lock ();
baseNMIU * pmiu = this->ioTable.lookup ( id );
this->defaultMutex.unlock ();
return pmiu;
}
void cac::installChannel (nciu &chan)
{
this->defaultMutex.lock ();
this->chanTable.add ( chan );
this->defaultMutex.unlock ();
}
void cac::uninstallChannel (nciu &chan)
{
this->defaultMutex.lock ();
this->chanTable.remove ( chan );
this->defaultMutex.unlock ();
}
nciu * cac::lookupChan (unsigned id)
{
this->defaultMutex.lock ();
nciu * pchan = this->chanTable.lookup ( id );
this->defaultMutex.unlock ();
return pchan;
}
void cac::installCASG (CASG &sg)
{
this->defaultMutex.lock ();
this->sgTable.add ( sg );
this->defaultMutex.unlock ();
}
void cac::uninstallCASG (CASG &sg)
{
this->defaultMutex.lock ();
this->sgTable.remove ( sg );
this->defaultMutex.unlock ();
}
CASG * cac::lookupCASG (unsigned id)
{
this->defaultMutex.lock ();
CASG * psg = this->sgTable.lookup ( id );
if ( psg ) {
if ( ! psg->verify () ) {
psg = 0;
}
}
this->defaultMutex.unlock ();
return psg;
}
void cac::exceptionNotify (int status, const char *pContext,
const char *pFileName, unsigned lineNo)
{
ca_signal_with_file_and_lineno ( status, pContext, pFileName, lineNo );
}
void cac::exceptionNotify (int status, const char *pContext,
unsigned type, unsigned long count,
const char *pFileName, unsigned lineNo)
{
ca_signal_formated ( status, pFileName, lineNo, "%s type=%d count=%ld\n",
pContext, type, count );
}
void cac::registerService ( cacServiceIO &service )
{
this->services.registerService ( service );
}
bool cac::createChannelIO (const char *pName, cacChannel &chan)
{
cacLocalChannelIO *pIO;
pIO = this->services.createChannelIO ( pName, chan );
if ( ! pIO ) {
pIO = cacGlobalServiceList.createChannelIO ( pName, chan );
if ( ! pIO ) {
if ( ! this->pudpiiu ) {
this->defaultMutex.lock ();
this->pudpiiu = new udpiiu ( this );
if ( ! this->pudpiiu ) {
this->defaultMutex.unlock ();
return false;
}
if ( ! this->enablePreemptiveCallback ) {
if ( this->fdRegFunc ) {
( *this->fdRegFunc )
( this->fdRegArg, this->pudpiiu->getSock (), TRUE );
}
}
this->defaultMutex.unlock ();
}
nciu *pNetChan = new nciu ( this, chan, pName );
if ( pNetChan ) {
if ( ! pNetChan->fullyConstructed () ) {
pNetChan->destroy ();
return false;
}
else {
return true;
}
}
else {
return false;
}
}
}
this->defaultMutex.lock ();
this->localChanList.add ( *pIO );
this->defaultMutex.unlock ();
return true;
}
void cac::lock () const
{
this->defaultMutex.lock ();
}
void cac::unlock () const
{
this->defaultMutex.unlock ();
}
void cac::registerForFileDescriptorCallBack ( CAFDHANDLER *pFunc, void *pArg )
{
this->defaultMutex.lock ();
this->fdRegFunc = pFunc;
this->fdRegArg = pArg;
this->defaultMutex.unlock ();
}
void cac::enableCallbackPreemption ()
{
this->pProcThread->enable ();
}
void cac::disableCallbackPreemption ()
{
this->pProcThread->disable ();
}