/* $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 iter ( this->localChanList.first () ); while ( iter != tsDLIterBD ::eol () ) { tsDLIterBD 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 piiu ( this->iiuListIdle.first () ); while ( piiu != piiu.eol () ) { tsDLIterBD pnext = piiu.itemAfter (); piiu->suicide (); piiu = pnext; } piiu = this->iiuListRecvPending.first (); while ( piiu != piiu.eol () ) { tsDLIterBD 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 piiu ( this->iiuListIdle.first () ); while ( piiu != tsDLIterBD::eol () ) { piiu->flush (); piiu++; } piiu = this->iiuListRecvPending.first (); while ( piiu != tsDLIterBD::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 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 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 iter ( this->pudpiiu->chidList.first () ); while ( iter != tsDLIterBD::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 (); }