/*************************************************************************\ * Copyright (c) 2002 The University of Chicago, as Operator of Argonne * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. * EPICS BASE Versions 3.13.7 * and higher are distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ // // File descriptor management C++ class library // (for multiplexing IO in a single threaded environment) // // Author Jeffrey O. Hill // johill@lanl.gov // 505 665 1831 // // NOTES: // 1) This library is not thread safe // // // ANSI C // #include #include #define instantiateRecourceLib #define epicsExportSharedSymbols #include "epicsAssert.h" #include "epicsThread.h" #include "tsMinMax.h" #include "fdManager.h" #include "locationException.h" epicsShareDef fdManager fileDescriptorManager; const unsigned mSecPerSec = 1000u; const unsigned uSecPerSec = 1000u * mSecPerSec; // // fdManager::fdManager() // // hopefully its a reasonable guess that select() and epicsThreadSleep() // will have the same sleep quantum // epicsShareFunc fdManager::fdManager () : sleepQuantum ( epicsThreadSleepQuantum () ), fdSetsPtr ( new fd_set [fdrNEnums] ), pTimerQueue ( 0 ), maxFD ( 0 ), processInProg ( false ), pCBReg ( 0 ) { int status = osiSockAttach (); assert (status); for ( size_t i = 0u; i < fdrNEnums; i++ ) { FD_ZERO ( &fdSetsPtr[i] ); } } // // fdManager::~fdManager() // epicsShareFunc fdManager::~fdManager() { fdReg *pReg; while ( (pReg = this->regList.get()) ) { pReg->state = fdReg::limbo; pReg->destroy(); } while ( (pReg = this->activeList.get()) ) { pReg->state = fdReg::limbo; pReg->destroy(); } delete this->pTimerQueue; delete [] this->fdSetsPtr; osiSockRelease(); } // // fdManager::process() // epicsShareFunc void fdManager::process (double delay) { this->lazyInitTimerQueue (); // // no recursion // if (this->processInProg) { return; } this->processInProg = true; // // One shot at expired timers prior to going into // select. This allows zero delay timers to arm // fd writes. We will never process the timer queue // more than once here so that fd activity get serviced // in a reasonable length of time. // double minDelay = this->pTimerQueue->process(epicsTime::getCurrent()); if ( minDelay >= delay ) { minDelay = delay; } bool ioPending = false; tsDLIter < fdReg > iter = this->regList.firstIter (); while ( iter.valid () ) { FD_SET(iter->getFD(), &this->fdSetsPtr[iter->getType()]); ioPending = true; ++iter; } if ( ioPending ) { struct timeval tv; tv.tv_sec = static_cast ( minDelay ); tv.tv_usec = static_cast ( (minDelay-tv.tv_sec) * uSecPerSec ); fd_set * pReadSet = & this->fdSetsPtr[fdrRead]; fd_set * pWriteSet = & this->fdSetsPtr[fdrWrite]; fd_set * pExceptSet = & this->fdSetsPtr[fdrException]; int status = select (this->maxFD, pReadSet, pWriteSet, pExceptSet, &tv); this->pTimerQueue->process(epicsTime::getCurrent()); if ( status > 0 ) { // // Look for activity // iter=this->regList.firstIter (); while ( iter.valid () && status > 0 ) { tsDLIter < fdReg > tmp = iter; tmp++; if (FD_ISSET(iter->getFD(), &this->fdSetsPtr[iter->getType()])) { FD_CLR(iter->getFD(), &this->fdSetsPtr[iter->getType()]); this->regList.remove(*iter); this->activeList.add(*iter); iter->state = fdReg::active; status--; } iter = tmp; } // // I am careful to prevent problems if they access the // above list while in a "callBack()" routine // fdReg * pReg; while ( (pReg = this->activeList.get()) ) { pReg->state = fdReg::limbo; // // Tag current fdReg so that we // can detect if it was deleted // during the call back // this->pCBReg = pReg; pReg->callBack(); if (this->pCBReg != NULL) { // // check only after we see that it is non-null so // that we dont trigger bounds-checker dangling pointer // error // assert (this->pCBReg==pReg); this->pCBReg = 0; if (pReg->onceOnly) { pReg->destroy(); } else { this->regList.add(*pReg); pReg->state = fdReg::pending; } } } } else if ( status < 0 ) { int errnoCpy = SOCKERRNO; // dont depend on flags being properly set if // an error is retuned from select for ( size_t i = 0u; i < fdrNEnums; i++ ) { FD_ZERO ( &fdSetsPtr[i] ); } // // print a message if its an unexpected error // if ( errnoCpy != SOCK_EINTR ) { char sockErrBuf[64]; epicsSocketConvertErrnoToString ( sockErrBuf, sizeof ( sockErrBuf ) ); fprintf ( stderr, "fdManager: select failed because \"%s\"\n", sockErrBuf ); } } } else { /* * recover from subtle differences between * windows sockets and UNIX sockets implementation * of select() */ epicsThreadSleep(minDelay); this->pTimerQueue->process(epicsTime::getCurrent()); } this->processInProg = false; return; } // // fdReg::destroy() // (default destroy method) // epicsShareFunc void fdReg::destroy() { delete this; } // // fdReg::~fdReg() // epicsShareFunc fdReg::~fdReg() { this->manager.removeReg(*this); } // // fdReg::show() // epicsShareFunc void fdReg::show(unsigned level) const { printf ("fdReg at %p\n", (void *) this); if (level>1u) { printf ("\tstate = %d, onceOnly = %d\n", this->state, this->onceOnly); } this->fdRegId::show(level); } // // fdRegId::show() // void fdRegId::show ( unsigned level ) const { printf ( "fdRegId at %p\n", static_cast ( this ) ); if ( level > 1u ) { printf ( "\tfd = %d, type = %d\n", this->fd, this->type ); } } // // fdManager::installReg () // epicsShareFunc void fdManager::installReg (fdReg ®) { this->maxFD = tsMax ( this->maxFD, reg.getFD()+1 ); // Most applications will find that its important to push here to // the front of the list so that transient writes get executed // first allowing incoming read protocol to find that outgoing // buffer space is newly available. this->regList.push ( reg ); reg.state = fdReg::pending; int status = this->fdTbl.add ( reg ); if ( status != 0 ) { throwWithLocation ( fdInterestSubscriptionAlreadyExits () ); } } // // fdManager::removeReg () // void fdManager::removeReg (fdReg ®In) { fdReg *pItemFound; pItemFound = this->fdTbl.remove (regIn); if (pItemFound!=®In) { fprintf(stderr, "fdManager::removeReg() bad fd registration object\n"); return; } // // signal fdManager that the fdReg was deleted // during the call back // if (this->pCBReg == ®In) { this->pCBReg = 0; } switch (regIn.state) { case fdReg::active: this->activeList.remove (regIn); break; case fdReg::pending: this->regList.remove (regIn); break; case fdReg::limbo: break; default: // // here if memory corrupted // assert(0); } regIn.state = fdReg::limbo; FD_CLR(regIn.getFD(), &this->fdSetsPtr[regIn.getType()]); } // // fdManager::reschedule () // NOOP - this only runs single threaded, and therefore they can only // add a new timer from places that will always end up in a reschedule // void fdManager::reschedule () { } double fdManager::quantum () { return this->sleepQuantum; } // // lookUpFD() // epicsShareFunc fdReg *fdManager::lookUpFD (const SOCKET fd, const fdRegType type) { if (fd<0) { return NULL; } fdRegId id (fd,type); return this->fdTbl.lookup(id); } // // fdReg::fdReg() // fdReg::fdReg (const SOCKET fdIn, const fdRegType typIn, const bool onceOnlyIn, fdManager &managerIn) : fdRegId (fdIn,typIn), state (limbo), onceOnly (onceOnlyIn), manager (managerIn) { if (!FD_IN_FDSET(fdIn)) { fprintf (stderr, "%s: fd > FD_SETSIZE ignored\n", __FILE__); return; } this->manager.installReg (*this); } template class resTable;