diff --git a/documentation/client.rst b/documentation/client.rst index 26bf2f2..73aedff 100644 --- a/documentation/client.rst +++ b/documentation/client.rst @@ -25,6 +25,14 @@ EPICS_PVA_AUTO_ADDR_LIST EPICS_PVA_BROADCAST_PORT Default UDP port to which UDP searches will be sent. 5076 if unset. +EPICS_PVA_CONN_TMO + Inactivity timeout for TCP connections. For compatibility with pvAccessCPP + a multiplier of 4/3 is applied. So a value of 30 results in a 40 second timeout. + Prior to UNRELEASED this variable was ignored. + +.. versionadded:: UNRELEASED + Prior to UNRELEASED *EPICS_PVA_CONN_TMO* was ignored. + .. code-block:: c++ using namespace pvxs; diff --git a/documentation/server.rst b/documentation/server.rst index 3f87fcc..47d9d15 100644 --- a/documentation/server.rst +++ b/documentation/server.rst @@ -74,6 +74,13 @@ EPICS_PVAS_IGNORE_ADDR_LIST Port zero is treated as a wildcard to match any port. UDP traffic from matched addresses will be ignored with no further processing. +EPICS_PVA_CONN_TMO + Inactivity timeout for TCP connections. For compatibility with pvAccessCPP + a multiplier of 4/3 is applied. So a value of 30 results in a 40 second timeout. + +.. versionadded:: UNRELEASED + Prior to UNRELEASED *EPICS_PVA_CONN_TMO* was ignored. + .. doxygenstruct:: pvxs::server::Config :members: diff --git a/src/clientconn.cpp b/src/clientconn.cpp index 52a2799..7680d24 100644 --- a/src/clientconn.cpp +++ b/src/clientconn.cpp @@ -24,7 +24,8 @@ Connection::Connection(const std::shared_ptr& context, const SockA bufferevent_setcb(bev.get(), &bevReadS, nullptr, &bevEventS, this); // shorter timeout until connect() ? - bufferevent_set_timeouts(bev.get(), &tcp_timeout, &tcp_timeout); + timeval tmo(totv(context->effective.tcpTimeout)); + bufferevent_set_timeouts(bev.get(), &tmo, &tmo); if(bufferevent_socket_connect(bev.get(), const_cast(&peerAddr->sa), peerAddr.size())) throw std::runtime_error("Unable to begin connecting"); @@ -99,7 +100,10 @@ void Connection::bevEvent(short events) throw std::logic_error("Unable to enable BEV"); // start echo timer - if(event_add(echoTimer.get(), &tcp_echo_period)) + // tcpTimeout(40) -> 15 second echo period + // bound echo to range [1, 15] + timeval tmo(totv(std::max(1.0, std::min(15.0, context->effective.tcpTimeout*3.0/8.0)))); + if(event_add(echoTimer.get(), &tmo)) log_err_printf(io, "Server %s error starting echoTimer\n", peerName.c_str()); } } diff --git a/src/config.cpp b/src/config.cpp index 1af3766..03dfdc2 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -8,9 +8,13 @@ #include #include #include +#include +#include #include #include +#include +#include #include #include @@ -25,6 +29,14 @@ DEFINE_LOGGER(config, "pvxs.config"); namespace pvxs { namespace { + +/* Historically pvAccessCPP used $EPICS_PVA_CONN_TMO as the period + * between sending CMD_ECHO. *::Config::tcpTimeout is the actual + * inactivity timeout period. Apply a scaling factor to add a + * go from one to the other. + */ +constexpr double tmoScale = 4.0/3.0; // 40 second idle timeout / 30 configured + void split_addr_into(const char* name, std::vector& out, const std::string& inp, uint16_t defaultPort, bool required=false) { @@ -88,6 +100,24 @@ void parse_bool(bool& dest, const std::string& name, const std::string& val) } } +void parse_timeout(double& dest, const std::string& name, const std::string& val) +{ + double temp; + try { + temp = parseTo(val); + + if(!std::isfinite(temp) + || temp<0.0 + || temp>double(std::numeric_limits::max())) + throw std::out_of_range("Out of range"); + + dest = temp*tmoScale; + } catch(std::exception& e) { + log_err_printf(serversetup, "%s invalid double value : '%s'\n", + name.c_str(), val.c_str()); + } +} + struct PickOne { const std::map& defs; bool useenv; @@ -153,6 +183,26 @@ void removeDups(std::vector& addrs) addrs.end()); } +void enforceTimeout(double& tmo) +{ + /* Inactivity timeouts with PVA have a long (and growing) history. + * + * - Originally pvAccessCPP clients didn't send CMD_ECHO, and servers would never timeout. + * - Since module version 7.0.0 (in Base 7.0.3) clients send echo every 15 seconds, and + * either peer will timeout after 30 seconds of inactivity. + * - pvAccessJava clients send CMD_ECHO every 30 seconds, and timeout after 60 seconds. + * + * So this was a bug, with c++ server timeout racing with Java client echo. + * + * - As a compromise, continue to send echo at least every 15 seconds, + * and increase default timeout to 40. + */ + if(!std::isfinite(tmo) || tmo <= 0.0 || tmo >= double(std::numeric_limits::max())) + tmo = 40.0; + else if(tmo < 2.0) + tmo = 2.0; +} + } // namespace namespace server { @@ -193,6 +243,10 @@ void _fromDefs(Config& self, const std::map& defs, boo if(pickone({"EPICS_PVAS_AUTO_BEACON_ADDR_LIST", "EPICS_PVA_AUTO_ADDR_LIST"})) { parse_bool(self.auto_beacon, pickone.name, pickone.val); } + + if(pickone({"EPICS_PVA_CONN_TMO"})) { + parse_timeout(self.tcpTimeout, pickone.name, pickone.val); + } } Config& Config::applyEnv() @@ -228,6 +282,7 @@ void Config::updateDefs(defs_t& defs) const defs["EPICS_PVA_ADDR_LIST"] = defs["EPICS_PVAS_BEACON_ADDR_LIST"] = join_addr(beaconDestinations); defs["EPICS_PVA_INTF_ADDR_LIST"] = defs["EPICS_PVAS_INTF_ADDR_LIST"] = join_addr(interfaces); defs["EPICS_PVAS_IGNORE_ADDR_LIST"] = join_addr(ignoreAddrs); + defs["EPICS_PVA_CONN_TMO"] = SB()<