Merge remote-tracking branch 'github/fdManager_using_poll' into PSI-7.0
This commit is contained in:
@ -38,6 +38,12 @@ record type by opening these files in a text editor intead of opening a browser
|
|||||||
and loading the HTML versions or finding and opening the files from the EPICS
|
and loading the HTML versions or finding and opening the files from the EPICS
|
||||||
Documentation site.
|
Documentation site.
|
||||||
|
|
||||||
|
### fdManager file descriptor limit removed
|
||||||
|
|
||||||
|
In order to support file descriptors above 1023, fdManager now uses
|
||||||
|
poll() instead of select() on all architectures that support it
|
||||||
|
(Linux, MacOS, Windows, newer RTEMS).
|
||||||
|
|
||||||
### Post monitors from compress record when it's reset
|
### Post monitors from compress record when it's reset
|
||||||
|
|
||||||
Writing into a compress record's `RES` field now posts a monitor event instead
|
Writing into a compress record's `RES` field now posts a monitor event instead
|
||||||
|
@ -19,39 +19,117 @@
|
|||||||
// 1) This library is not thread safe
|
// 1) This library is not thread safe
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#define instantiateRecourceLib
|
#define instantiateRecourceLib
|
||||||
#include "epicsAssert.h"
|
#include "epicsAssert.h"
|
||||||
#include "epicsThread.h"
|
#include "epicsThread.h"
|
||||||
#include "fdManager.h"
|
#include "fdManager.h"
|
||||||
#include "locationException.h"
|
#include "locationException.h"
|
||||||
|
|
||||||
using std :: max;
|
#if !defined(FDMGR_USE_POLL) && !defined(FDMGR_USE_SELECT)
|
||||||
|
#if defined(__linux__) || defined(darwin) || _WIN32_WINNT >= 0x600 || (defined(__rtems__) && !defined(RTEMS_LEGACY_STACK))
|
||||||
|
#define FDMGR_USE_POLL
|
||||||
|
#else
|
||||||
|
#define FDMGR_USE_SELECT
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef FDMGR_USE_POLL
|
||||||
|
#include <vector>
|
||||||
|
#if !defined(_WIN32)
|
||||||
|
#include <poll.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const short PollEvents[] = { // must match fdRegType
|
||||||
|
POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR,
|
||||||
|
POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR,
|
||||||
|
POLLPRI };
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#define poll WSAPoll
|
||||||
|
// Filter out PollEvents that Windows does not accept in events (only returns in revents)
|
||||||
|
#define WIN_POLLEVENT_FILTER(ev) static_cast<short>((ev) & (POLLIN | POLLOUT))
|
||||||
|
#else
|
||||||
|
// Linux, MacOS and RTEMS don't care
|
||||||
|
#define WIN_POLLEVENT_FILTER(ev) (ev)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef FDMGR_USE_SELECT
|
||||||
|
#include <algorithm>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct fdManagerPrivate {
|
||||||
|
tsDLList<fdReg> regList;
|
||||||
|
tsDLList<fdReg> activeList;
|
||||||
|
resTable<fdReg, fdRegId> fdTbl;
|
||||||
|
const double sleepQuantum;
|
||||||
|
epics::auto_ptr<epicsTimerQueuePassive> pTimerQueue;
|
||||||
|
bool processInProg;
|
||||||
|
|
||||||
|
#ifdef FDMGR_USE_POLL
|
||||||
|
std::vector<struct pollfd> pollfds;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef FDMGR_USE_SELECT
|
||||||
|
fd_set fdSets[fdrNEnums];
|
||||||
|
SOCKET maxFD;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//
|
||||||
|
// Set to fdreg when in call back
|
||||||
|
// and nill otherwise
|
||||||
|
//
|
||||||
|
volatile fdReg* pCBReg;
|
||||||
|
fdManager& owner;
|
||||||
|
|
||||||
|
explicit fdManagerPrivate(fdManager& owner);
|
||||||
|
void lazyInitTimerQueue();
|
||||||
|
};
|
||||||
|
|
||||||
|
fdManagerPrivate::fdManagerPrivate(fdManager& owner) :
|
||||||
|
sleepQuantum(epicsThreadSleepQuantum()),
|
||||||
|
processInProg(false),
|
||||||
|
pCBReg(NULL), owner(owner)
|
||||||
|
{}
|
||||||
|
|
||||||
|
inline void fdManagerPrivate::lazyInitTimerQueue()
|
||||||
|
{
|
||||||
|
if (!pTimerQueue.get()) {
|
||||||
|
pTimerQueue.reset(&epicsTimerQueuePassive::create(owner));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
epicsTimer& fdManager::createTimer()
|
||||||
|
{
|
||||||
|
priv->lazyInitTimerQueue();
|
||||||
|
return priv->pTimerQueue->createTimer();
|
||||||
|
}
|
||||||
|
|
||||||
fdManager fileDescriptorManager;
|
fdManager fileDescriptorManager;
|
||||||
|
|
||||||
const unsigned mSecPerSec = 1000u;
|
static const unsigned mSecPerSec = 1000u;
|
||||||
const unsigned uSecPerSec = 1000u * mSecPerSec;
|
#ifdef FDMGR_USE_SELECT
|
||||||
|
static const unsigned uSecPerSec = 1000u * mSecPerSec;
|
||||||
|
#endif
|
||||||
|
|
||||||
//
|
//
|
||||||
// fdManager::fdManager()
|
// fdManager::fdManager()
|
||||||
//
|
//
|
||||||
// hopefully its a reasonable guess that select() and epicsThreadSleep()
|
// hopefully its a reasonable guess that poll()/select() and epicsThreadSleep()
|
||||||
// will have the same sleep quantum
|
// will have the same sleep quantum
|
||||||
//
|
//
|
||||||
LIBCOM_API fdManager::fdManager () :
|
LIBCOM_API fdManager::fdManager() :
|
||||||
sleepQuantum ( epicsThreadSleepQuantum () ),
|
priv(new fdManagerPrivate(*this))
|
||||||
fdSetsPtr ( new fd_set [fdrNEnums] ),
|
|
||||||
pTimerQueue ( 0 ), maxFD ( 0 ), processInProg ( false ),
|
|
||||||
pCBReg ( 0 )
|
|
||||||
{
|
{
|
||||||
int status = osiSockAttach ();
|
int status = osiSockAttach();
|
||||||
assert (status);
|
assert(status);
|
||||||
|
|
||||||
for ( size_t i = 0u; i < fdrNEnums; i++ ) {
|
#ifdef FDMGR_USE_SELECT
|
||||||
FD_ZERO ( &fdSetsPtr[i] );
|
priv->maxFD = 0;
|
||||||
|
for (size_t i = 0u; i < fdrNEnums; i++) {
|
||||||
|
FD_ZERO(&priv->fdSets[i]);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -59,82 +137,138 @@ LIBCOM_API fdManager::fdManager () :
|
|||||||
//
|
//
|
||||||
LIBCOM_API fdManager::~fdManager()
|
LIBCOM_API fdManager::~fdManager()
|
||||||
{
|
{
|
||||||
fdReg *pReg;
|
fdReg* pReg;
|
||||||
|
|
||||||
while ( (pReg = this->regList.get()) ) {
|
while ((pReg = priv->regList.get())) {
|
||||||
pReg->state = fdReg::limbo;
|
pReg->state = fdReg::limbo;
|
||||||
pReg->destroy();
|
pReg->destroy();
|
||||||
}
|
}
|
||||||
while ( (pReg = this->activeList.get()) ) {
|
while ((pReg = priv->activeList.get())) {
|
||||||
pReg->state = fdReg::limbo;
|
pReg->state = fdReg::limbo;
|
||||||
pReg->destroy();
|
pReg->destroy();
|
||||||
}
|
}
|
||||||
delete this->pTimerQueue;
|
|
||||||
delete [] this->fdSetsPtr;
|
|
||||||
osiSockRelease();
|
osiSockRelease();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// fdManager::process()
|
// fdManager::process()
|
||||||
//
|
//
|
||||||
LIBCOM_API void fdManager::process (double delay)
|
LIBCOM_API void fdManager::process(double delay)
|
||||||
{
|
{
|
||||||
this->lazyInitTimerQueue ();
|
priv->lazyInitTimerQueue();
|
||||||
|
|
||||||
//
|
//
|
||||||
// no recursion
|
// no recursion
|
||||||
//
|
//
|
||||||
if (this->processInProg) {
|
if (priv->processInProg)
|
||||||
return;
|
return;
|
||||||
}
|
priv->processInProg = true;
|
||||||
this->processInProg = true;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// One shot at expired timers prior to going into
|
// One shot at expired timers prior to going into
|
||||||
// select. This allows zero delay timers to arm
|
// poll/select. This allows zero delay timers to arm
|
||||||
// fd writes. We will never process the timer queue
|
// fd writes. We will never process the timer queue
|
||||||
// more than once here so that fd activity get serviced
|
// more than once here so that fd activity get serviced
|
||||||
// in a reasonable length of time.
|
// in a reasonable length of time.
|
||||||
//
|
//
|
||||||
double minDelay = this->pTimerQueue->process(epicsTime::getCurrent());
|
double minDelay = priv->pTimerQueue->process(epicsTime::getCurrent());
|
||||||
|
|
||||||
if ( minDelay >= delay ) {
|
if (minDelay >= delay) {
|
||||||
minDelay = delay;
|
minDelay = delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ioPending = false;
|
#ifdef FDMGR_USE_POLL
|
||||||
tsDLIter < fdReg > iter = this->regList.firstIter ();
|
priv->pollfds.clear();
|
||||||
while ( iter.valid () ) {
|
#endif
|
||||||
FD_SET(iter->getFD(), &this->fdSetsPtr[iter->getType()]);
|
|
||||||
ioPending = true;
|
int ioPending = 0;
|
||||||
|
tsDLIter<fdReg> iter = priv->regList.firstIter();
|
||||||
|
while (iter.valid()) {
|
||||||
|
++ioPending;
|
||||||
|
|
||||||
|
#ifdef FDMGR_USE_POLL
|
||||||
|
#if __cplusplus >= 201100L
|
||||||
|
priv->pollfds.emplace_back(pollfd{
|
||||||
|
.fd = iter->getFD(),
|
||||||
|
.events = WIN_POLLEVENT_FILTER(PollEvents[iter->getType()])
|
||||||
|
});
|
||||||
|
#else
|
||||||
|
struct pollfd pollfd;
|
||||||
|
pollfd.fd = iter->getFD();
|
||||||
|
pollfd.events = WIN_POLLEVENT_FILTER(PollEvents[iter->getType()]);
|
||||||
|
pollfd.revents = 0;
|
||||||
|
priv->pollfds.push_back(pollfd);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef FDMGR_USE_SELECT
|
||||||
|
FD_SET(iter->getFD(), &priv->fdSets[iter->getType()]);
|
||||||
|
#endif
|
||||||
++iter;
|
++iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ioPending ) {
|
if (ioPending) {
|
||||||
|
#ifdef FDMGR_USE_POLL
|
||||||
|
if (minDelay * mSecPerSec > INT_MAX)
|
||||||
|
minDelay = INT_MAX / mSecPerSec;
|
||||||
|
|
||||||
|
int status = poll(&priv->pollfds[0], // ancient C++ has no vector.data()
|
||||||
|
ioPending, static_cast<int>(minDelay * mSecPerSec));
|
||||||
|
int i = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef FDMGR_USE_SELECT
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
tv.tv_sec = static_cast<time_t> ( minDelay );
|
tv.tv_sec = static_cast<time_t>(minDelay);
|
||||||
tv.tv_usec = static_cast<long> ( (minDelay-tv.tv_sec) * uSecPerSec );
|
tv.tv_usec = static_cast<long>((minDelay-tv.tv_sec) * uSecPerSec);
|
||||||
|
|
||||||
fd_set * pReadSet = & this->fdSetsPtr[fdrRead];
|
int status = select(priv->maxFD,
|
||||||
fd_set * pWriteSet = & this->fdSetsPtr[fdrWrite];
|
&priv->fdSets[fdrRead],
|
||||||
fd_set * pExceptSet = & this->fdSetsPtr[fdrException];
|
&priv->fdSets[fdrWrite],
|
||||||
int status = select (this->maxFD, pReadSet, pWriteSet, pExceptSet, &tv);
|
&priv->fdSets[fdrException], &tv);
|
||||||
|
#endif
|
||||||
|
|
||||||
this->pTimerQueue->process(epicsTime::getCurrent());
|
priv->pTimerQueue->process(epicsTime::getCurrent());
|
||||||
|
|
||||||
if ( status > 0 ) {
|
if (status > 0) {
|
||||||
|
|
||||||
//
|
//
|
||||||
// Look for activity
|
// Look for activity
|
||||||
//
|
//
|
||||||
iter=this->regList.firstIter ();
|
iter = priv->regList.firstIter();
|
||||||
while ( iter.valid () && status > 0 ) {
|
while (iter.valid() && status > 0) {
|
||||||
tsDLIter < fdReg > tmp = iter;
|
tsDLIter<fdReg> tmp = iter;
|
||||||
tmp++;
|
tmp++;
|
||||||
if (FD_ISSET(iter->getFD(), &this->fdSetsPtr[iter->getType()])) {
|
|
||||||
FD_CLR(iter->getFD(), &this->fdSetsPtr[iter->getType()]);
|
#ifdef FDMGR_USE_POLL
|
||||||
this->regList.remove(*iter);
|
// In a single threaded application, nothing should have
|
||||||
this->activeList.add(*iter);
|
// changed the order of regList and pollfds by now.
|
||||||
|
// But just in case...
|
||||||
|
int isave = i;
|
||||||
|
while (priv->pollfds[i].fd != iter->getFD() ||
|
||||||
|
priv->pollfds[i].events != WIN_POLLEVENT_FILTER(PollEvents[iter->getType()]))
|
||||||
|
{
|
||||||
|
errlogPrintf("fdManager: skipping (removed?) pollfd %d (expected %d)\n", priv->pollfds[i].fd, iter->getFD());
|
||||||
|
i++; // skip pollfd of removed items
|
||||||
|
if (i >= ioPending) { // skip unknown (inserted?) items
|
||||||
|
errlogPrintf("fdManager: skipping (inserted?) item %d\n", iter->getFD());
|
||||||
|
iter = tmp;
|
||||||
|
tmp++;
|
||||||
|
if (!iter.valid()) break;
|
||||||
|
i = isave;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i >= ioPending) break; // any unhandled item stays in regList for next time
|
||||||
|
|
||||||
|
if (priv->pollfds[i++].revents & PollEvents[iter->getType()]) {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef FDMGR_USE_SELECT
|
||||||
|
if (FD_ISSET(iter->getFD(), &priv->fdSets[iter->getType()])) {
|
||||||
|
FD_CLR(iter->getFD(), &priv->fdSets[iter->getType()]);
|
||||||
|
#endif
|
||||||
|
priv->regList.remove(*iter);
|
||||||
|
priv->activeList.add(*iter);
|
||||||
iter->state = fdReg::active;
|
iter->state = fdReg::active;
|
||||||
status--;
|
status--;
|
||||||
}
|
}
|
||||||
@ -145,8 +279,8 @@ LIBCOM_API void fdManager::process (double delay)
|
|||||||
// I am careful to prevent problems if they access the
|
// I am careful to prevent problems if they access the
|
||||||
// above list while in a "callBack()" routine
|
// above list while in a "callBack()" routine
|
||||||
//
|
//
|
||||||
fdReg * pReg;
|
fdReg* pReg;
|
||||||
while ( (pReg = this->activeList.get()) ) {
|
while ((pReg = priv->activeList.get())) {
|
||||||
pReg->state = fdReg::limbo;
|
pReg->state = fdReg::limbo;
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -154,45 +288,53 @@ LIBCOM_API void fdManager::process (double delay)
|
|||||||
// can detect if it was deleted
|
// can detect if it was deleted
|
||||||
// during the call back
|
// during the call back
|
||||||
//
|
//
|
||||||
this->pCBReg = pReg;
|
priv->pCBReg = pReg;
|
||||||
pReg->callBack();
|
pReg->callBack();
|
||||||
if (this->pCBReg != NULL) {
|
if (priv->pCBReg != NULL) {
|
||||||
//
|
//
|
||||||
// check only after we see that it is non-null so
|
// check only after we see that it is non-null so
|
||||||
// that we don't trigger bounds-checker dangling pointer
|
// that we don't trigger bounds-checker dangling pointer
|
||||||
// error
|
// error
|
||||||
//
|
//
|
||||||
assert (this->pCBReg==pReg);
|
assert(priv->pCBReg == pReg);
|
||||||
this->pCBReg = 0;
|
priv->pCBReg = NULL;
|
||||||
if (pReg->onceOnly) {
|
if (pReg->onceOnly) {
|
||||||
pReg->destroy();
|
pReg->destroy();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this->regList.add(*pReg);
|
priv->regList.add(*pReg);
|
||||||
pReg->state = fdReg::pending;
|
pReg->state = fdReg::pending;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ( status < 0 ) {
|
else if (status < 0) {
|
||||||
int errnoCpy = SOCKERRNO;
|
int errnoCpy = SOCKERRNO;
|
||||||
|
|
||||||
|
#ifdef FDMGR_USE_SELECT
|
||||||
// don't depend on flags being properly set if
|
// don't depend on flags being properly set if
|
||||||
// an error is returned from select
|
// an error is returned from select
|
||||||
for ( size_t i = 0u; i < fdrNEnums; i++ ) {
|
for (size_t i = 0u; i < fdrNEnums; i++) {
|
||||||
FD_ZERO ( &fdSetsPtr[i] );
|
FD_ZERO(&priv->fdSets[i]);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
//
|
//
|
||||||
// print a message if its an unexpected error
|
// print a message if it's an unexpected error
|
||||||
//
|
//
|
||||||
if ( errnoCpy != SOCK_EINTR ) {
|
if (errnoCpy != SOCK_EINTR) {
|
||||||
char sockErrBuf[64];
|
char sockErrBuf[64];
|
||||||
epicsSocketConvertErrnoToString (
|
epicsSocketConvertErrnoToString(
|
||||||
sockErrBuf, sizeof ( sockErrBuf ) );
|
sockErrBuf, sizeof(sockErrBuf));
|
||||||
fprintf ( stderr,
|
errlogPrintf("fdManager: "
|
||||||
"fdManager: select failed because \"%s\"\n",
|
#ifdef FDMGR_USE_POLL
|
||||||
sockErrBuf );
|
"poll()"
|
||||||
|
#endif
|
||||||
|
#ifdef FDMGR_USE_SELECT
|
||||||
|
"select()"
|
||||||
|
#endif
|
||||||
|
" failed because \"%s\"\n",
|
||||||
|
sockErrBuf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,9 +345,9 @@ LIBCOM_API void fdManager::process (double delay)
|
|||||||
* of select()
|
* of select()
|
||||||
*/
|
*/
|
||||||
epicsThreadSleep(minDelay);
|
epicsThreadSleep(minDelay);
|
||||||
this->pTimerQueue->process(epicsTime::getCurrent());
|
priv->pTimerQueue->process(epicsTime::getCurrent());
|
||||||
}
|
}
|
||||||
this->processInProg = false;
|
priv->processInProg = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -222,7 +364,7 @@ void fdReg::destroy()
|
|||||||
//
|
//
|
||||||
fdReg::~fdReg()
|
fdReg::~fdReg()
|
||||||
{
|
{
|
||||||
this->manager.removeReg(*this);
|
manager.removeReg(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@ -230,57 +372,62 @@ fdReg::~fdReg()
|
|||||||
//
|
//
|
||||||
void fdReg::show(unsigned level) const
|
void fdReg::show(unsigned level) const
|
||||||
{
|
{
|
||||||
printf ("fdReg at %p\n", (void *) this);
|
printf("fdReg at %p\n", this);
|
||||||
if (level>1u) {
|
if (level > 1u) {
|
||||||
printf ("\tstate = %d, onceOnly = %d\n",
|
printf("\tstate = %d, onceOnly = %d\n",
|
||||||
this->state, this->onceOnly);
|
state, onceOnly);
|
||||||
}
|
}
|
||||||
this->fdRegId::show(level);
|
fdRegId::show(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// fdRegId::show()
|
// fdRegId::show()
|
||||||
//
|
//
|
||||||
void fdRegId::show ( unsigned level ) const
|
void fdRegId::show(unsigned level) const
|
||||||
{
|
{
|
||||||
printf ( "fdRegId at %p\n",
|
printf("fdRegId at %p\n", this);
|
||||||
static_cast <const void *> ( this ) );
|
if (level > 1u) {
|
||||||
if ( level > 1u ) {
|
printf("\tfd = %"
|
||||||
printf ( "\tfd = %d, type = %d\n",
|
#if defined(_WIN32)
|
||||||
int(this->fd), this->type );
|
"I"
|
||||||
|
#endif
|
||||||
|
"d, type = %d\n",
|
||||||
|
fd, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// fdManager::installReg ()
|
// fdManager::installReg()
|
||||||
//
|
//
|
||||||
void fdManager::installReg (fdReg ®)
|
void fdManager::installReg(fdReg ®)
|
||||||
{
|
{
|
||||||
this->maxFD = max ( this->maxFD, reg.getFD()+1 );
|
#ifdef FDMGR_USE_SELECT
|
||||||
// Most applications will find that its important to push here to
|
priv->maxFD = std::max(priv->maxFD, reg.getFD()+1);
|
||||||
|
#endif
|
||||||
|
// Most applications will find that it's important to push here to
|
||||||
// the front of the list so that transient writes get executed
|
// the front of the list so that transient writes get executed
|
||||||
// first allowing incoming read protocol to find that outgoing
|
// first allowing incoming read protocol to find that outgoing
|
||||||
// buffer space is newly available.
|
// buffer space is newly available.
|
||||||
this->regList.push ( reg );
|
priv->regList.push(reg);
|
||||||
reg.state = fdReg::pending;
|
reg.state = fdReg::pending;
|
||||||
|
|
||||||
int status = this->fdTbl.add ( reg );
|
int status = priv->fdTbl.add(reg);
|
||||||
if ( status != 0 ) {
|
if (status != 0) {
|
||||||
throwWithLocation ( fdInterestSubscriptionAlreadyExits () );
|
throwWithLocation(fdInterestSubscriptionAlreadyExits());
|
||||||
}
|
}
|
||||||
|
// errlogPrintf("fdManager::adding fd %d\n", reg.getFD());
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// fdManager::removeReg ()
|
// fdManager::removeReg()
|
||||||
//
|
//
|
||||||
void fdManager::removeReg (fdReg ®In)
|
void fdManager::removeReg(fdReg ®In)
|
||||||
{
|
{
|
||||||
fdReg *pItemFound;
|
fdReg* pItemFound;
|
||||||
|
|
||||||
pItemFound = this->fdTbl.remove (regIn);
|
pItemFound = priv->fdTbl.remove(regIn);
|
||||||
if (pItemFound!=®In) {
|
if (pItemFound != ®In) {
|
||||||
fprintf(stderr,
|
errlogPrintf("fdManager::removeReg() bad fd registration object\n");
|
||||||
"fdManager::removeReg() bad fd registration object\n");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,16 +435,16 @@ void fdManager::removeReg (fdReg ®In)
|
|||||||
// signal fdManager that the fdReg was deleted
|
// signal fdManager that the fdReg was deleted
|
||||||
// during the call back
|
// during the call back
|
||||||
//
|
//
|
||||||
if (this->pCBReg == ®In) {
|
if (priv->pCBReg == ®In) {
|
||||||
this->pCBReg = 0;
|
priv->pCBReg = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (regIn.state) {
|
switch (regIn.state) {
|
||||||
case fdReg::active:
|
case fdReg::active:
|
||||||
this->activeList.remove (regIn);
|
priv->activeList.remove(regIn);
|
||||||
break;
|
break;
|
||||||
case fdReg::pending:
|
case fdReg::pending:
|
||||||
this->regList.remove (regIn);
|
priv->regList.remove(regIn);
|
||||||
break;
|
break;
|
||||||
case fdReg::limbo:
|
case fdReg::limbo:
|
||||||
break;
|
break;
|
||||||
@ -309,49 +456,55 @@ void fdManager::removeReg (fdReg ®In)
|
|||||||
}
|
}
|
||||||
regIn.state = fdReg::limbo;
|
regIn.state = fdReg::limbo;
|
||||||
|
|
||||||
FD_CLR(regIn.getFD(), &this->fdSetsPtr[regIn.getType()]);
|
#ifdef FDMGR_USE_SELECT
|
||||||
|
FD_CLR(regIn.getFD(), &priv->fdSets[regIn.getType()]);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// errlogPrintf("fdManager::removing fd %d\n", regIn.getFD());
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// fdManager::reschedule ()
|
// fdManager::reschedule()
|
||||||
// NOOP - this only runs single threaded, and therefore they can only
|
// 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
|
// add a new timer from places that will always end up in a reschedule
|
||||||
//
|
//
|
||||||
void fdManager::reschedule ()
|
void fdManager::reschedule()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
double fdManager::quantum ()
|
double fdManager::quantum()
|
||||||
{
|
{
|
||||||
return this->sleepQuantum;
|
return priv->sleepQuantum;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// lookUpFD()
|
// lookUpFD()
|
||||||
//
|
//
|
||||||
LIBCOM_API fdReg *fdManager::lookUpFD (const SOCKET fd, const fdRegType type)
|
LIBCOM_API fdReg* fdManager::lookUpFD(const SOCKET fd, const fdRegType type)
|
||||||
{
|
{
|
||||||
if (fd<0) {
|
if (fd < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
fdRegId id (fd,type);
|
fdRegId id(fd,type);
|
||||||
return this->fdTbl.lookup(id);
|
return priv->fdTbl.lookup(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// fdReg::fdReg()
|
// fdReg::fdReg()
|
||||||
//
|
//
|
||||||
fdReg::fdReg (const SOCKET fdIn, const fdRegType typIn,
|
fdReg::fdReg(const SOCKET fdIn, const fdRegType typIn,
|
||||||
const bool onceOnlyIn, fdManager &managerIn) :
|
const bool onceOnlyIn, fdManager &managerIn) :
|
||||||
fdRegId (fdIn,typIn), state (limbo),
|
fdRegId(fdIn,typIn), state(limbo),
|
||||||
onceOnly (onceOnlyIn), manager (managerIn)
|
onceOnly(onceOnlyIn), manager(managerIn)
|
||||||
{
|
{
|
||||||
|
#ifdef FDMGR_USE_SELECT
|
||||||
if (!FD_IN_FDSET(fdIn)) {
|
if (!FD_IN_FDSET(fdIn)) {
|
||||||
fprintf (stderr, "%s: fd > FD_SETSIZE ignored\n",
|
errlogPrintf("%s: fd > FD_SETSIZE ignored\n",
|
||||||
__FILE__);
|
__FILE__);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->manager.installReg (*this);
|
#endif
|
||||||
|
manager.installReg(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
template class resTable<fdReg, fdRegId>;
|
template class resTable<fdReg, fdRegId>;
|
||||||
|
@ -19,6 +19,16 @@
|
|||||||
#ifndef fdManagerH_included
|
#ifndef fdManagerH_included
|
||||||
#define fdManagerH_included
|
#define fdManagerH_included
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
namespace epics {
|
||||||
|
#if __cplusplus>=201103L
|
||||||
|
template<typename T>
|
||||||
|
using auto_ptr = std::unique_ptr<T>;
|
||||||
|
#else
|
||||||
|
using std::auto_ptr;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#include "libComAPI.h" // reset share lib defines
|
#include "libComAPI.h" // reset share lib defines
|
||||||
#include "tsDLList.h"
|
#include "tsDLList.h"
|
||||||
#include "resourceLib.h"
|
#include "resourceLib.h"
|
||||||
@ -37,27 +47,27 @@ class LIBCOM_API fdRegId
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
fdRegId (const SOCKET fdIn, const fdRegType typeIn) :
|
fdRegId(const SOCKET fdIn, const fdRegType typeIn) :
|
||||||
fd(fdIn), type(typeIn) {}
|
fd(fdIn), type(typeIn) {}
|
||||||
|
|
||||||
SOCKET getFD () const
|
SOCKET getFD() const
|
||||||
{
|
{
|
||||||
return this->fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
fdRegType getType () const
|
fdRegType getType() const
|
||||||
{
|
{
|
||||||
return this->type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator == (const fdRegId &idIn) const
|
bool operator == (const fdRegId& idIn) const
|
||||||
{
|
{
|
||||||
return this->fd == idIn.fd && this->type==idIn.type;
|
return fd == idIn.fd && type == idIn.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
resTableIndex hash () const;
|
resTableIndex hash() const;
|
||||||
|
|
||||||
virtual void show (unsigned level) const;
|
virtual void show(unsigned level) const;
|
||||||
|
|
||||||
virtual ~fdRegId() {}
|
virtual ~fdRegId() {}
|
||||||
private:
|
private:
|
||||||
@ -70,43 +80,31 @@ private:
|
|||||||
//
|
//
|
||||||
// file descriptor manager
|
// file descriptor manager
|
||||||
//
|
//
|
||||||
class fdManager : public epicsTimerQueueNotify {
|
class LIBCOM_API fdManager : public epicsTimerQueueNotify {
|
||||||
public:
|
public:
|
||||||
//
|
//
|
||||||
// exceptions
|
// exceptions
|
||||||
//
|
//
|
||||||
class fdInterestSubscriptionAlreadyExits {};
|
class fdInterestSubscriptionAlreadyExits {};
|
||||||
|
|
||||||
LIBCOM_API fdManager ();
|
fdManager();
|
||||||
LIBCOM_API virtual ~fdManager ();
|
virtual ~fdManager();
|
||||||
LIBCOM_API void process ( double delay ); // delay parameter is in seconds
|
void process(double delay); // delay parameter is in seconds
|
||||||
|
|
||||||
// returns NULL if the fd is unknown
|
// returns NULL if the fd is unknown
|
||||||
LIBCOM_API class fdReg *lookUpFD (const SOCKET fd, const fdRegType type);
|
class fdReg* lookUpFD(const SOCKET fd, const fdRegType type);
|
||||||
|
|
||||||
epicsTimer & createTimer ();
|
epicsTimer& createTimer();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
tsDLList < fdReg > regList;
|
epics::auto_ptr <struct fdManagerPrivate> priv;
|
||||||
tsDLList < fdReg > activeList;
|
|
||||||
resTable < fdReg, fdRegId > fdTbl;
|
void reschedule();
|
||||||
const double sleepQuantum;
|
double quantum();
|
||||||
fd_set * fdSetsPtr;
|
void installReg(fdReg& reg);
|
||||||
epicsTimerQueuePassive * pTimerQueue;
|
void removeReg(fdReg& reg);
|
||||||
SOCKET maxFD;
|
fdManager(const fdManager&);
|
||||||
bool processInProg;
|
fdManager& operator = (const fdManager&);
|
||||||
//
|
|
||||||
// Set to fdreg when in call back
|
|
||||||
// and nill otherwise
|
|
||||||
//
|
|
||||||
fdReg * pCBReg;
|
|
||||||
void reschedule ();
|
|
||||||
double quantum ();
|
|
||||||
void installReg (fdReg ®);
|
|
||||||
void removeReg (fdReg ®);
|
|
||||||
void lazyInitTimerQueue ();
|
|
||||||
fdManager ( const fdManager & );
|
|
||||||
fdManager & operator = ( const fdManager & );
|
|
||||||
friend class fdReg;
|
friend class fdReg;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,11 +124,11 @@ class LIBCOM_API fdReg :
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
fdReg (const SOCKET fdIn, const fdRegType type,
|
fdReg(const SOCKET fdIn, const fdRegType type,
|
||||||
const bool onceOnly=false, fdManager &manager = fileDescriptorManager);
|
const bool onceOnly=false, fdManager& manager = fileDescriptorManager);
|
||||||
virtual ~fdReg ();
|
virtual ~fdReg();
|
||||||
|
|
||||||
virtual void show (unsigned level) const;
|
virtual void show(unsigned level) const;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Called by the file descriptor manager:
|
// Called by the file descriptor manager:
|
||||||
@ -141,7 +139,7 @@ public:
|
|||||||
//
|
//
|
||||||
// fdReg::destroy() does a "delete this"
|
// fdReg::destroy() does a "delete this"
|
||||||
//
|
//
|
||||||
virtual void destroy ();
|
virtual void destroy();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum state {active, pending, limbo};
|
enum state {active, pending, limbo};
|
||||||
@ -153,32 +151,32 @@ private:
|
|||||||
// lifetime of a fdReg object if the constructor
|
// lifetime of a fdReg object if the constructor
|
||||||
// specified "onceOnly"
|
// specified "onceOnly"
|
||||||
//
|
//
|
||||||
virtual void callBack ()=0;
|
virtual void callBack() = 0;
|
||||||
|
|
||||||
unsigned char state; // state enums go here
|
unsigned char state; // state enums go here
|
||||||
unsigned char onceOnly;
|
unsigned char onceOnly;
|
||||||
fdManager &manager;
|
fdManager& manager;
|
||||||
|
|
||||||
fdReg ( const fdReg & );
|
fdReg(const fdReg&);
|
||||||
fdReg & operator = ( const fdReg & );
|
fdReg& operator = (const fdReg&);
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
// fdRegId::hash()
|
// fdRegId::hash()
|
||||||
//
|
//
|
||||||
inline resTableIndex fdRegId::hash () const
|
inline resTableIndex fdRegId::hash() const
|
||||||
{
|
{
|
||||||
const unsigned fdManagerHashTableMinIndexBits = 8;
|
const unsigned fdManagerHashTableMinIndexBits = 8;
|
||||||
const unsigned fdManagerHashTableMaxIndexBits = sizeof(SOCKET)*CHAR_BIT;
|
const unsigned fdManagerHashTableMaxIndexBits = sizeof(SOCKET) * CHAR_BIT;
|
||||||
resTableIndex hashid;
|
resTableIndex hashid;
|
||||||
|
|
||||||
hashid = integerHash ( fdManagerHashTableMinIndexBits,
|
hashid = integerHash(fdManagerHashTableMinIndexBits,
|
||||||
fdManagerHashTableMaxIndexBits, this->fd );
|
fdManagerHashTableMaxIndexBits, fd);
|
||||||
|
|
||||||
//
|
//
|
||||||
// also evenly distribute based on the type of fdRegType
|
// also evenly distribute based on the type of fdRegType
|
||||||
//
|
//
|
||||||
hashid ^= this->type;
|
hashid ^= type;
|
||||||
|
|
||||||
//
|
//
|
||||||
// the result here is always masked to the
|
// the result here is always masked to the
|
||||||
@ -187,18 +185,5 @@ inline resTableIndex fdRegId::hash () const
|
|||||||
return hashid;
|
return hashid;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void fdManager::lazyInitTimerQueue ()
|
|
||||||
{
|
|
||||||
if ( ! this->pTimerQueue ) {
|
|
||||||
this->pTimerQueue = & epicsTimerQueuePassive::create ( *this );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline epicsTimer & fdManager::createTimer ()
|
|
||||||
{
|
|
||||||
this->lazyInitTimerQueue ();
|
|
||||||
return this->pTimerQueue->createTimer ();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // fdManagerH_included
|
#endif // fdManagerH_included
|
||||||
|
|
||||||
|
@ -209,6 +209,11 @@ aslibtest_SRCS += aslibtest.c
|
|||||||
testHarness_SRCS += aslibtest.c
|
testHarness_SRCS += aslibtest.c
|
||||||
TESTS += aslibtest
|
TESTS += aslibtest
|
||||||
|
|
||||||
|
TESTPROD_HOST += fdManagerTest
|
||||||
|
fdManagerTest_SRCS += fdManagerTest.cpp
|
||||||
|
testHarness_SRCS += fdManagerTest.cpp
|
||||||
|
TESTS += fdManagerTest
|
||||||
|
|
||||||
# Perl module tests:
|
# Perl module tests:
|
||||||
TESTS += macLib
|
TESTS += macLib
|
||||||
|
|
||||||
@ -315,11 +320,6 @@ TESTPROD_HOST += buckTest
|
|||||||
buckTest_SRCS += buckTest.c
|
buckTest_SRCS += buckTest.c
|
||||||
testHarness_SRCS += buckTest.c
|
testHarness_SRCS += buckTest.c
|
||||||
|
|
||||||
#TESTPROD_HOST += fdmgrTest
|
|
||||||
fdmgrTest_SRCS += fdmgrTest.c
|
|
||||||
fdmgrTest_LIBS += ca
|
|
||||||
# FIXME: program never exits.
|
|
||||||
|
|
||||||
TESTPROD_HOST += epicsAtomicPerform
|
TESTPROD_HOST += epicsAtomicPerform
|
||||||
epicsAtomicPerform_SRCS += epicsAtomicPerform.cpp
|
epicsAtomicPerform_SRCS += epicsAtomicPerform.cpp
|
||||||
testHarness_SRCS += epicsAtomicPerform.cpp
|
testHarness_SRCS += epicsAtomicPerform.cpp
|
||||||
|
@ -54,6 +54,7 @@ int initHookTest(void);
|
|||||||
int ipAddrToAsciiTest(void);
|
int ipAddrToAsciiTest(void);
|
||||||
int macDefExpandTest(void);
|
int macDefExpandTest(void);
|
||||||
int macLibTest(void);
|
int macLibTest(void);
|
||||||
|
int fdManagerTest(void);
|
||||||
int osiSockTest(void);
|
int osiSockTest(void);
|
||||||
int ringBytesTest(void);
|
int ringBytesTest(void);
|
||||||
int ringPointerTest(void);
|
int ringPointerTest(void);
|
||||||
@ -110,6 +111,7 @@ void epicsRunLibComTests(void)
|
|||||||
runTest(ipAddrToAsciiTest);
|
runTest(ipAddrToAsciiTest);
|
||||||
runTest(macDefExpandTest);
|
runTest(macDefExpandTest);
|
||||||
runTest(macLibTest);
|
runTest(macLibTest);
|
||||||
|
runTest(fdManagerTest);
|
||||||
runTest(osiSockTest);
|
runTest(osiSockTest);
|
||||||
runTest(ringBytesTest);
|
runTest(ringBytesTest);
|
||||||
runTest(ringPointerTest);
|
runTest(ringPointerTest);
|
||||||
|
316
modules/libcom/test/fdManagerTest.cpp
Normal file
316
modules/libcom/test/fdManagerTest.cpp
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
/*************************************************************************\
|
||||||
|
* Copyright (c) 2025 Michael Davidsaver
|
||||||
|
* SPDX-License-Identifier: EPICS
|
||||||
|
* EPICS Base is distributed subject to a Software License Agreement found
|
||||||
|
* in file LICENSE that is included with this distribution.
|
||||||
|
\*************************************************************************/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <osiSock.h>
|
||||||
|
#include <fdManager.h>
|
||||||
|
#include <epicsTime.h>
|
||||||
|
#include <epicsAtomic.h>
|
||||||
|
#include <epicsThread.h>
|
||||||
|
|
||||||
|
#include <epicsUnitTest.h>
|
||||||
|
#include <testMain.h>
|
||||||
|
|
||||||
|
#if __cplusplus<201103L
|
||||||
|
# define final
|
||||||
|
# define override
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
void set_non_blocking(SOCKET sd)
|
||||||
|
{
|
||||||
|
osiSockIoctl_t yes = true;
|
||||||
|
int status = socket_ioctl ( sd,
|
||||||
|
FIONBIO, & yes);
|
||||||
|
if(status)
|
||||||
|
testFail("set_non_blocking fails : %d", SOCKERRNO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAII for epicsTimer
|
||||||
|
struct ScopedTimer {
|
||||||
|
epicsTimer& timer;
|
||||||
|
explicit
|
||||||
|
ScopedTimer(epicsTimer& t) :timer(t) {}
|
||||||
|
~ScopedTimer() { timer.destroy(); }
|
||||||
|
};
|
||||||
|
struct ScopedFDReg {
|
||||||
|
fdReg * const reg;
|
||||||
|
explicit
|
||||||
|
ScopedFDReg(fdReg* reg) :reg(reg) {}
|
||||||
|
~ScopedFDReg() { reg->destroy(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// RAII for socket
|
||||||
|
struct Socket {
|
||||||
|
SOCKET sd;
|
||||||
|
Socket() :sd(INVALID_SOCKET) {}
|
||||||
|
explicit
|
||||||
|
Socket(SOCKET sd) :sd(sd) {}
|
||||||
|
Socket(int af, int type)
|
||||||
|
:sd(epicsSocketCreate(af, type, 0))
|
||||||
|
{
|
||||||
|
if(sd==INVALID_SOCKET)
|
||||||
|
testAbort("failed to allocate socket %d %d", af, type);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
Socket(const Socket&);
|
||||||
|
Socket& operator=(const Socket&);
|
||||||
|
public:
|
||||||
|
~Socket() {
|
||||||
|
if(sd!=INVALID_SOCKET)
|
||||||
|
epicsSocketDestroy(sd);
|
||||||
|
}
|
||||||
|
void swap(Socket& o) {
|
||||||
|
std::swap(sd, o.sd);
|
||||||
|
}
|
||||||
|
osiSockAddr bind()
|
||||||
|
{
|
||||||
|
osiSockAddr addr = {0};
|
||||||
|
addr.ia.sin_family = AF_INET;
|
||||||
|
addr.ia.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
|
||||||
|
if(::bind(sd, &addr.sa, sizeof(addr)))
|
||||||
|
testAbort("Unable to bind lo : %d", SOCKERRNO);
|
||||||
|
|
||||||
|
osiSockAddr ret;
|
||||||
|
osiSocklen_t addrlen = sizeof(ret);
|
||||||
|
if(getsockname(sd, &ret.sa, &addrlen))
|
||||||
|
testAbort("Unable to getsockname : %d", SOCKERRNO);
|
||||||
|
(void)addrlen;
|
||||||
|
|
||||||
|
if(listen(sd, 1))
|
||||||
|
testAbort("Unable to listen : %d", SOCKERRNO);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DoConnect final : public epicsThreadRunable {
|
||||||
|
const SOCKET sd;
|
||||||
|
osiSockAddr to;
|
||||||
|
DoConnect(SOCKET sd, const osiSockAddr& to)
|
||||||
|
:sd(sd)
|
||||||
|
,to(to)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void run() override final {
|
||||||
|
int err = connect(sd, &to.sa, sizeof(to));
|
||||||
|
testOk(err==0, "connect() %d %d", err, SOCKERRNO);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DoAccept final : public epicsThreadRunable {
|
||||||
|
const SOCKET sd;
|
||||||
|
Socket peer;
|
||||||
|
osiSockAddr peer_addr;
|
||||||
|
explicit
|
||||||
|
DoAccept(SOCKET sd) :sd(sd) {}
|
||||||
|
void run() override final {
|
||||||
|
osiSocklen_t len(sizeof(peer_addr));
|
||||||
|
Socket temp(accept(sd, &peer_addr.sa, &len));
|
||||||
|
if(temp.sd==INVALID_SOCKET)
|
||||||
|
testFail("accept() -> %d", SOCKERRNO);
|
||||||
|
temp.swap(peer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DoRead final : public epicsThreadRunable {
|
||||||
|
const SOCKET sd;
|
||||||
|
char* buf;
|
||||||
|
unsigned buflen;
|
||||||
|
int n;
|
||||||
|
DoRead(SOCKET sd, char* buf, unsigned buflen): sd(sd), buf(buf), buflen(buflen), n(0) {}
|
||||||
|
void run() override final {
|
||||||
|
n = recv(sd, buf, buflen, 0);
|
||||||
|
if(n<0)
|
||||||
|
testFail("read() -> %d, %d", n, SOCKERRNO);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DoWriteAll final : public epicsThreadRunable {
|
||||||
|
const SOCKET sd;
|
||||||
|
const char* buf;
|
||||||
|
unsigned buflen;
|
||||||
|
DoWriteAll(SOCKET sd, const char* buf, unsigned buflen): sd(sd), buf(buf), buflen(buflen) {}
|
||||||
|
void run() override final {
|
||||||
|
unsigned nsent = 0;
|
||||||
|
while(nsent<buflen) {
|
||||||
|
int n = send(sd, nsent+buf, buflen-nsent, 0);
|
||||||
|
if(n<0) {
|
||||||
|
testFail("send() -> %d, %d", n, SOCKERRNO);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nsent += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Expire final : public epicsTimerNotify {
|
||||||
|
bool expired;
|
||||||
|
|
||||||
|
Expire() :expired(false) {}
|
||||||
|
virtual ~Expire() {}
|
||||||
|
virtual expireStatus expire(const epicsTime &) override final
|
||||||
|
{
|
||||||
|
if(!expired) {
|
||||||
|
expired = true;
|
||||||
|
testPass("expired");
|
||||||
|
} else {
|
||||||
|
testFail("re-expired?");
|
||||||
|
}
|
||||||
|
return noRestart;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Event final : public fdReg {
|
||||||
|
int* ready;
|
||||||
|
|
||||||
|
Event(fdManager& mgr, fdRegType evt, SOCKET sd, int* ready)
|
||||||
|
:fdReg(sd, evt, false, mgr)
|
||||||
|
,ready(ready)
|
||||||
|
{}
|
||||||
|
virtual ~Event() {}
|
||||||
|
|
||||||
|
virtual void callBack() override final {
|
||||||
|
epics::atomic::set(*ready, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OneShot final : public fdReg {
|
||||||
|
int *mask;
|
||||||
|
|
||||||
|
OneShot(fdManager& mgr, fdRegType evt, SOCKET sd, int *mask)
|
||||||
|
:fdReg(sd, evt, true, mgr)
|
||||||
|
,mask(mask)
|
||||||
|
{}
|
||||||
|
virtual ~OneShot() {
|
||||||
|
epics::atomic::add(*mask, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void callBack() override final {
|
||||||
|
epics::atomic::add(*mask, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void testEmpty()
|
||||||
|
{
|
||||||
|
fdManager empty;
|
||||||
|
empty.process(0.1); // ca-gateway always passes 0.01
|
||||||
|
testPass("Did nothing");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testOnlyTimer()
|
||||||
|
{
|
||||||
|
fdManager mgr;
|
||||||
|
Expire trig, never;
|
||||||
|
ScopedTimer trig_timer(mgr.createTimer()),
|
||||||
|
never_timer(mgr.createTimer());
|
||||||
|
epicsTime now(epicsTime::getCurrent());
|
||||||
|
trig_timer.timer.start(trig, now+0.1);
|
||||||
|
never_timer.timer.start(never, now+9999999.0);
|
||||||
|
mgr.process(0.2);
|
||||||
|
testOk1(trig.expired);
|
||||||
|
testOk1(!never.expired);
|
||||||
|
}
|
||||||
|
|
||||||
|
void testSockIO()
|
||||||
|
{
|
||||||
|
fdManager mgr;
|
||||||
|
Socket listener(AF_INET, SOCK_STREAM);
|
||||||
|
set_non_blocking(listener.sd);
|
||||||
|
osiSockAddr servAddr(listener.bind());
|
||||||
|
|
||||||
|
Socket client(AF_INET, SOCK_STREAM);
|
||||||
|
Socket server;
|
||||||
|
// listen() / connect()
|
||||||
|
{
|
||||||
|
int readable = 0;
|
||||||
|
Event evt(mgr, fdrRead, listener.sd, &readable);
|
||||||
|
DoConnect conn(client.sd, servAddr);
|
||||||
|
epicsThread connector(conn, "connect", 0);
|
||||||
|
connector.start();
|
||||||
|
|
||||||
|
mgr.process(5.0);
|
||||||
|
|
||||||
|
testOk1(readable);
|
||||||
|
|
||||||
|
DoAccept acc(listener.sd);
|
||||||
|
acc.run();
|
||||||
|
server.swap(acc.peer);
|
||||||
|
}
|
||||||
|
set_non_blocking(server.sd);
|
||||||
|
// writeable
|
||||||
|
{
|
||||||
|
int mask = 0;
|
||||||
|
new OneShot(mgr, fdrWrite, server.sd, &mask);
|
||||||
|
|
||||||
|
mgr.process(5.0);
|
||||||
|
|
||||||
|
testOk(mask==3, "OneShot event mask %x", mask);
|
||||||
|
}
|
||||||
|
// read
|
||||||
|
{
|
||||||
|
const char msg[] = "testing";
|
||||||
|
int readable = 0;
|
||||||
|
Event evt(mgr, fdrRead, server.sd, &readable);
|
||||||
|
DoWriteAll op(client.sd, msg, sizeof(msg)-1);
|
||||||
|
epicsThread writer(op, "writer", 0);
|
||||||
|
writer.start();
|
||||||
|
|
||||||
|
mgr.process(5.0);
|
||||||
|
|
||||||
|
testOk1(readable);
|
||||||
|
|
||||||
|
char buf[sizeof(msg)] = "";
|
||||||
|
DoRead(server.sd, buf, sizeof(buf)-1).run();
|
||||||
|
buf[sizeof(buf)-1] = '\0';
|
||||||
|
testOk(strcmp(msg, buf)==0, "%s == %s", msg, buf);
|
||||||
|
}
|
||||||
|
// timer while unreadable
|
||||||
|
{
|
||||||
|
|
||||||
|
int readable = 0;
|
||||||
|
Event evt(mgr, fdrRead, server.sd, &readable);
|
||||||
|
Expire tmo;
|
||||||
|
ScopedTimer timer(mgr.createTimer());
|
||||||
|
timer.timer.start(tmo, epicsTime::getCurrent()); // immediate
|
||||||
|
|
||||||
|
mgr.process(1.0);
|
||||||
|
|
||||||
|
testOk1(!readable);
|
||||||
|
testOk1(tmo.expired);
|
||||||
|
}
|
||||||
|
// notification on close()
|
||||||
|
{
|
||||||
|
int readable = 0;
|
||||||
|
Event evt(mgr, fdrRead, server.sd, &readable);
|
||||||
|
|
||||||
|
shutdown(client.sd, SHUT_RDWR);
|
||||||
|
//Socket().swap(client);
|
||||||
|
|
||||||
|
mgr.process(1.0);
|
||||||
|
|
||||||
|
testOk1(readable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
MAIN(fdManagerTest)
|
||||||
|
{
|
||||||
|
testPlan(13);
|
||||||
|
osiSockAttach();
|
||||||
|
testEmpty();
|
||||||
|
testOnlyTimer();
|
||||||
|
testSockIO();
|
||||||
|
osiSockRelease();
|
||||||
|
return testDone();
|
||||||
|
}
|
@ -1,139 +0,0 @@
|
|||||||
/*************************************************************************\
|
|
||||||
* Copyright (c) 2006 UChicago Argonne LLC, as Operator of Argonne
|
|
||||||
* National Laboratory.
|
|
||||||
* Copyright (c) 2002 The Regents of the University of California, as
|
|
||||||
* Operator of Los Alamos National Laboratory.
|
|
||||||
* SPDX-License-Identifier: EPICS
|
|
||||||
* EPICS BASE is distributed subject to a Software License Agreement found
|
|
||||||
* in file LICENSE that is included with this distribution.
|
|
||||||
\*************************************************************************/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
#include "fdmgr.h"
|
|
||||||
#include "epicsTime.h"
|
|
||||||
#include "epicsAssert.h"
|
|
||||||
#include "cadef.h"
|
|
||||||
|
|
||||||
#define verify(exp) ((exp) ? (void)0 : \
|
|
||||||
epicsAssert(__FILE__, __LINE__, #exp, epicsAssertAuthor))
|
|
||||||
|
|
||||||
static const unsigned uSecPerSec = 1000000;
|
|
||||||
|
|
||||||
typedef struct cbStructCreateDestroyFD {
|
|
||||||
fdctx *pfdm;
|
|
||||||
int trig;
|
|
||||||
} cbStructCreateDestroyFD;
|
|
||||||
|
|
||||||
void fdHandler (void *pArg)
|
|
||||||
{
|
|
||||||
cbStructCreateDestroyFD *pCBFD = (cbStructCreateDestroyFD *) pArg;
|
|
||||||
|
|
||||||
printf ("triggered\n");
|
|
||||||
pCBFD->trig = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void fdCreateDestroyHandler (void *pArg, int fd, int open)
|
|
||||||
{
|
|
||||||
cbStructCreateDestroyFD *pCBFD = (cbStructCreateDestroyFD *) pArg;
|
|
||||||
int status;
|
|
||||||
|
|
||||||
if (open) {
|
|
||||||
printf ("new fd = %d\n", fd);
|
|
||||||
status = fdmgr_add_callback (pCBFD->pfdm, fd, fdi_read, fdHandler, pArg);
|
|
||||||
verify (status==0);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printf ("terminated fd = %d\n", fd);
|
|
||||||
status = fdmgr_clear_callback (pCBFD->pfdm, fd, fdi_read);
|
|
||||||
verify (status==0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct cbStuctTimer {
|
|
||||||
epicsTimeStamp time;
|
|
||||||
int done;
|
|
||||||
} cbStruct;
|
|
||||||
|
|
||||||
void alarmCB (void *parg)
|
|
||||||
{
|
|
||||||
cbStruct *pCBS = (cbStruct *) parg;
|
|
||||||
epicsTimeGetCurrent (&pCBS->time);
|
|
||||||
pCBS->done = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void testTimer (fdctx *pfdm, double delay)
|
|
||||||
{
|
|
||||||
int status;
|
|
||||||
fdmgrAlarmId aid;
|
|
||||||
struct timeval tmo;
|
|
||||||
epicsTimeStamp begin;
|
|
||||||
cbStruct cbs;
|
|
||||||
double measuredDelay;
|
|
||||||
double measuredError;
|
|
||||||
|
|
||||||
epicsTimeGetCurrent (&begin);
|
|
||||||
cbs.done = 0;
|
|
||||||
tmo.tv_sec = (time_t) delay;
|
|
||||||
tmo.tv_usec = (unsigned long) ((delay - tmo.tv_sec) * uSecPerSec);
|
|
||||||
aid = fdmgr_add_timeout (pfdm, &tmo, alarmCB, &cbs);
|
|
||||||
verify (aid!=fdmgrNoAlarm);
|
|
||||||
|
|
||||||
while (!cbs.done) {
|
|
||||||
tmo.tv_sec = (time_t) delay;
|
|
||||||
tmo.tv_usec = (unsigned long) ((delay - tmo.tv_sec) * uSecPerSec);
|
|
||||||
status = fdmgr_pend_event (pfdm, &tmo);
|
|
||||||
verify (status==0);
|
|
||||||
}
|
|
||||||
|
|
||||||
measuredDelay = epicsTimeDiffInSeconds (&cbs.time, &begin);
|
|
||||||
measuredError = fabs (measuredDelay-delay);
|
|
||||||
printf ("measured delay for %lf sec was off by %lf sec (%lf %%)\n",
|
|
||||||
delay, measuredError, 100.0*measuredError/delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main (int argc, char **argv)
|
|
||||||
{
|
|
||||||
int status;
|
|
||||||
fdctx *pfdm;
|
|
||||||
cbStructCreateDestroyFD cbsfd;
|
|
||||||
struct timeval tmo;
|
|
||||||
chid chan;
|
|
||||||
|
|
||||||
pfdm = fdmgr_init ();
|
|
||||||
verify (pfdm);
|
|
||||||
|
|
||||||
SEVCHK (ca_task_initialize(), NULL);
|
|
||||||
cbsfd.pfdm = pfdm;
|
|
||||||
SEVCHK (ca_add_fd_registration (fdCreateDestroyHandler, &cbsfd), NULL);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* timer test
|
|
||||||
*/
|
|
||||||
testTimer (pfdm, 0.001);
|
|
||||||
testTimer (pfdm, 0.01);
|
|
||||||
testTimer (pfdm, 0.1);
|
|
||||||
testTimer (pfdm, 1.0);
|
|
||||||
|
|
||||||
if (argc==2) {
|
|
||||||
SEVCHK(ca_search (argv[1], &chan), NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
tmo.tv_sec = 0;
|
|
||||||
tmo.tv_usec = 100000;
|
|
||||||
cbsfd.trig = 0;
|
|
||||||
status = fdmgr_pend_event (pfdm, &tmo);
|
|
||||||
verify (status==0);
|
|
||||||
ca_poll ();
|
|
||||||
}
|
|
||||||
|
|
||||||
status = fdmgr_delete (pfdm);
|
|
||||||
verify (status==0);
|
|
||||||
|
|
||||||
printf ( "Test Complete\n" );
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
Reference in New Issue
Block a user