From b59d1c5656ca5b03ca3910617f01ccd694484cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Nogueira?= Date: Wed, 6 Nov 2024 15:58:32 -0300 Subject: [PATCH 1/2] Add getAutoConnectTimeout function. Co-authored-by: Mark Rivers --- asyn/asynDriver/asynDriver.h | 1 + asyn/asynDriver/asynManager.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/asyn/asynDriver/asynDriver.h b/asyn/asynDriver/asynDriver.h index 6157f1b9f..d37eeeaa8 100644 --- a/asyn/asynDriver/asynDriver.h +++ b/asyn/asynDriver/asynDriver.h @@ -164,6 +164,7 @@ typedef struct asynManager { asynStatus (*isEnabled)(asynUser *pasynUser,int *yesNo); asynStatus (*isAutoConnect)(asynUser *pasynUser,int *yesNo); asynStatus (*setAutoConnectTimeout)(double timeout); + asynStatus (*getAutoConnectTimeout)(double *timeout); asynStatus (*waitConnect)(asynUser *pasynUser, double timeout); /*The following are methods for interrupts*/ asynStatus (*registerInterruptSource)(const char *portName, diff --git a/asyn/asynDriver/asynManager.c b/asyn/asynDriver/asynManager.c index dc2882685..fe848cea9 100644 --- a/asyn/asynDriver/asynManager.c +++ b/asyn/asynDriver/asynManager.c @@ -316,6 +316,7 @@ static asynStatus isConnected(asynUser *pasynUser,int *yesNo); static asynStatus isEnabled(asynUser *pasynUser,int *yesNo); static asynStatus isAutoConnect(asynUser *pasynUser,int *yesNo); static asynStatus setAutoConnectTimeout(double timeout); +static asynStatus getAutoConnectTimeout(double *timeout); static asynStatus waitConnect(asynUser *pasynUser, double timeout); static asynStatus registerInterruptSource(const char *portName, asynInterface *pasynInterface, void **pasynPvt); @@ -374,6 +375,7 @@ static asynManager manager = { isEnabled, isAutoConnect, setAutoConnectTimeout, + getAutoConnectTimeout, waitConnect, registerInterruptSource, getInterruptPvt, @@ -2376,6 +2378,15 @@ static asynStatus setAutoConnectTimeout(double timeout) return asynSuccess; } +static asynStatus getAutoConnectTimeout(double *timeout) +{ + if(!pasynBase) asynInit(); + epicsMutexMustLock(pasynBase->lock); + *timeout = pasynBase->autoConnectTimeout; + epicsMutexUnlock(pasynBase->lock); + return asynSuccess; +} + static asynStatus setQueueLockPortTimeout(asynUser *pasynUser, double timeout) { userPvt *puserPvt = asynUserToUserPvt(pasynUser); From f44ddc859b92ed13f1d2c992eabde2bb55cc0fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Nogueira?= Date: Wed, 6 Nov 2024 16:00:03 -0300 Subject: [PATCH 2/2] Add TCP port connection timeout. Use poll as a reasonably portable implementation. This takes advantage of the existing FAKE_POLL implementation as well. getsockopt(SO_ERROR) on Windows uses "int", removing the requirement for any custom code for that platform. We don't add any handling for EINTR, since it would add considerably more code, and the connection can simply be retried. For that reason, poll returning -1 is always an error. --- asyn/drvAsynSerial/drvAsynIPPort.c | 54 ++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/asyn/drvAsynSerial/drvAsynIPPort.c b/asyn/drvAsynSerial/drvAsynIPPort.c index a5fd1415f..4090f633e 100644 --- a/asyn/drvAsynSerial/drvAsynIPPort.c +++ b/asyn/drvAsynSerial/drvAsynIPPort.c @@ -505,13 +505,56 @@ connectIt(void *drvPvt, asynUser *pasynUser) } } + } + +#ifdef USE_POLL + if (setNonBlock(fd, 1) < 0) { + epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize, + "Can't set %s O_NONBLOCK option: %s", + tty->IPDeviceName, strerror(SOCKERRNO)); + epicsSocketDestroy(fd); + return asynError; + } +#endif + + if (pasynUser->reason <= 0) { + /* * Connect to the remote host * If the connect fails, arrange for another DNS lookup in case the * problem is just that the device has DHCP'd itself an new number. */ if (tty->socketType != SOCK_DGRAM) { - if (connect(fd, &tty->farAddr.oa.sa, (int)tty->farAddrSize) < 0) { + int connectResult = connect(fd, &tty->farAddr.oa.sa, (int)tty->farAddrSize); + #ifdef USE_POLL + if (connectResult < 0 && ((SOCKERRNO == EWOULDBLOCK) || (SOCKERRNO == EINPROGRESS))) { + double connectTimeout; + int msConnectTimeout; + struct pollfd pollfd; + + pasynManager->getAutoConnectTimeout(&connectTimeout); + msConnectTimeout = 1000 * connectTimeout; + pollfd.fd = fd; + pollfd.events = POLLOUT; + + /* + * poll() returning 1 is the only case where connect might have been successful. + * Otherwise connectResult will remain -1. + */ + if (poll(&pollfd, 1, msConnectTimeout) == 1) { + int so_error; + socklen_t len = sizeof so_error; + + /* + * We must verify SO_ERROR to make sure the connection was successful. + */ + getsockopt(fd, SOL_SOCKET, SO_ERROR, &so_error, &len); + if (so_error == 0) + connectResult = 0; + } + } + #endif + if (connectResult < 0) { epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize, "Can't connect to %s: %s", tty->IPDeviceName, strerror(SOCKERRNO)); @@ -532,15 +575,6 @@ connectIt(void *drvPvt, asynUser *pasynUser) epicsSocketDestroy(fd); return asynError; } -#ifdef USE_POLL - if (setNonBlock(fd, 1) < 0) { - epicsSnprintf(pasynUser->errorMessage,pasynUser->errorMessageSize, - "Can't set %s O_NONBLOCK option: %s", - tty->IPDeviceName, strerror(SOCKERRNO)); - epicsSocketDestroy(fd); - return asynError; - } -#endif asynPrint(pasynUser, ASYN_TRACE_FLOW, "Opened connection OK to %s\n", tty->IPDeviceName);