configurable timeout (with $EPICS_PVA_CONN_TMO)

This commit is contained in:
Michael Davidsaver
2021-01-14 17:11:37 -08:00
parent e5b21535ab
commit da004bc54b
9 changed files with 108 additions and 17 deletions
+8
View File
@@ -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;
+7
View File
@@ -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:
+6 -2
View File
@@ -24,7 +24,8 @@ Connection::Connection(const std::shared_ptr<Context::Pvt>& 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<sockaddr*>(&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());
}
}
+68
View File
@@ -8,9 +8,13 @@
#include <vector>
#include <string>
#include <sstream>
#include <limits>
#include <cmath>
#include <dbDefs.h>
#include <osiSock.h>
#include <epicsMath.h>
#include <epicsStdlib.h>
#include <epicsString.h>
#include <pvxs/log.h>
@@ -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<std::string>& 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<double>(val);
if(!std::isfinite(temp)
|| temp<0.0
|| temp>double(std::numeric_limits<time_t>::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<std::string, std::string>& defs;
bool useenv;
@@ -153,6 +183,26 @@ void removeDups(std::vector<std::string>& 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<time_t>::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<std::string, std::string>& 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()<<tcpTimeout/tmoScale;
}
void Config::expand()
@@ -246,6 +301,8 @@ void Config::expand()
removeDups(interfaces);
removeDups(beaconDestinations);
removeDups(ignoreAddrs);
enforceTimeout(tcpTimeout);
}
std::ostream& operator<<(std::ostream& strm, const Config& conf)
@@ -273,6 +330,8 @@ std::ostream& operator<<(std::ostream& strm, const Config& conf)
strm<<indent{}<<"EPICS_PVAS_BROADCAST_PORT="<<conf.udp_port<<'\n';
strm<<indent{}<<"EPICS_PVA_CONN_TMO="<<conf.tcpTimeout/tmoScale<<'\n';
return strm;
}
@@ -308,6 +367,10 @@ void _fromDefs(Config& self, const std::map<std::string, std::string>& defs, boo
if(pickone({"EPICS_PVA_INTF_ADDR_LIST"})) {
split_addr_into(pickone.name.c_str(), self.interfaces, pickone.val, 0);
}
if(pickone({"EPICS_PVA_CONN_TMO"})) {
parse_timeout(self.tcpTimeout, pickone.name, pickone.val);
}
}
Config& Config::applyEnv()
@@ -328,6 +391,7 @@ void Config::updateDefs(defs_t& defs) const
defs["EPICS_PVA_AUTO_ADDR_LIST"] = autoAddrList ? "YES" : "NO";
defs["EPICS_PVA_ADDR_LIST"] = join_addr(addressList);
defs["EPICS_PVA_INTF_ADDR_LIST"] = join_addr(interfaces);
defs["EPICS_PVA_CONN_TMO"] = SB()<<tcpTimeout/tmoScale;
}
void Config::expand()
@@ -344,6 +408,8 @@ void Config::expand()
}
removeDups(addressList);
enforceTimeout(tcpTimeout);
}
std::ostream& operator<<(std::ostream& strm, const Config& conf)
@@ -365,6 +431,8 @@ std::ostream& operator<<(std::ostream& strm, const Config& conf)
strm<<indent{}<<"EPICS_PVA_BROADCAST_PORT="<<conf.udp_port<<'\n';
strm<<indent{}<<"EPICS_PVA_CONN_TMO="<<conf.tcpTimeout/tmoScale<<'\n';
return strm;
}
-14
View File
@@ -19,20 +19,6 @@ namespace impl {
// Also bounds the loop in ConnBase::bevRead()
constexpr size_t tcp_readahead = 0x1000u;
/* 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 every 15 seconds, but increase timeout to 40.
*/
constexpr timeval tcp_timeout{40, 0};
constexpr timeval tcp_echo_period{15, 0};
struct ConnBase
{
SockAddr peerAddr;
+4
View File
@@ -714,6 +714,10 @@ struct PVXS_API Config {
//! Whether to extend the addressList with local interface broadcast addresses. (recommended)
bool autoAddrList = true;
//! Inactivity timeout interval for TCP connections. (seconds)
//! @since UNRELEASED
double tcpTimeout = 40.0;
// compat
static inline Config from_env() { return Config{}.applyEnv(); }
+4
View File
@@ -149,6 +149,10 @@ struct PVXS_API Config {
//! Whether to populate the beacon address list automatically. (recommended)
bool auto_beacon = true;
//! Inactivity timeout interval for TCP connections. (seconds)
//! @since UNRELEASED
double tcpTimeout = 40.0;
//! Server unique ID. Only meaningful in readback via Server::config()
ServerGUID guid{};
+2 -1
View File
@@ -37,7 +37,8 @@ ServerConn::ServerConn(ServIface* iface, evutil_socket_t sock, struct sockaddr *
bufferevent_setcb(bev.get(), &bevReadS, &bevWriteS, &bevEventS, this);
bufferevent_set_timeouts(bev.get(), &tcp_timeout, &tcp_timeout);
timeval tmo(totv(iface->server->effective.tcpTimeout));
bufferevent_set_timeouts(bev.get(), &tmo, &tmo);
auto tx = bufferevent_get_output(bev.get());
+9
View File
@@ -251,6 +251,15 @@ public:
PVXS_API
std::ostream& operator<<(std::ostream& strm, const SockAddr& addr);
inline
timeval totv(double t)
{
timeval ret;
ret.tv_sec = t;
ret.tv_usec = (t - ret.tv_sec)*1e6;
return ret;
}
//! Scoped restore of std::ostream state (format flags, fill char, and field width)
struct Restore {
std::ostream& strm;