configurable timeout (with $EPICS_PVA_CONN_TMO)
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
|
||||
@@ -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
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user