s_maxIdleTime = max;
}
- bool isConnectionUsable(const std::shared_ptr<T>& conn, const struct timeval& now, const struct timeval& freshCutOff)
- {
- if (!conn->canBeReused()) {
- return false;
- }
-
- /* for connections that have not been used very recently,
- check whether they have been closed in the meantime */
- if (freshCutOff < conn->getLastDataReceivedTime()) {
- /* used recently enough, skip the check */
- return true;
- }
-
- if (conn->isUsable()) {
- return true;
- }
-
- return false;
- }
-
std::shared_ptr<T> getConnectionToDownstream(std::unique_ptr<FDMultiplexer>& mplexer, const std::shared_ptr<DownstreamState>& ds, const struct timeval& now, std::string&& proxyProtocolPayload)
{
struct timeval freshCutOff = now;
auto newConnection = std::make_shared<T>(ds, mplexer, now, std::move(proxyProtocolPayload));
if (!haveProxyProtocol) {
- d_downstreamConnections[backendId].push_front(newConnection);
+ auto& list = d_downstreamConnections[backendId];
+ if (list.size() == s_maxCachedConnectionsPerDownstream) {
+ list.pop_back();
+ }
+ list.push_front(newConnection);
}
return newConnection;
return count;
}
+ size_t count() const
+ {
+ size_t count = 0;
+ for (const auto& downstream : d_downstreamConnections) {
+ count += downstream.second.size();
+ }
+ return count;
+ }
+
bool removeDownstreamConnection(std::shared_ptr<T>& conn)
{
bool found = false;
protected:
+ bool isConnectionUsable(const std::shared_ptr<T>& conn, const struct timeval& now, const struct timeval& freshCutOff)
+ {
+ if (!conn->canBeReused()) {
+ return false;
+ }
+
+ /* for connections that have not been used very recently,
+ check whether they have been closed in the meantime */
+ if (freshCutOff < conn->getLastDataReceivedTime()) {
+ /* used recently enough, skip the check */
+ return true;
+ }
+
+ if (conn->isUsable()) {
+ return true;
+ }
+
+ return false;
+ }
+
static size_t s_maxCachedConnectionsPerDownstream;
static uint16_t s_cleanupInterval;
static uint16_t s_maxIdleTime;
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#define BOOST_TEST_DYN_LINK
+#define BOOST_TEST_NO_MAIN
+
+#include <boost/test/unit_test.hpp>
+
+#include "dnsdist-tcp-downstream.hh"
+
+class MockupConnection
+{
+public:
+ MockupConnection(const std::shared_ptr<DownstreamState>&, std::unique_ptr<FDMultiplexer>&, const struct timeval&, std::string&&)
+ {
+ }
+
+ bool canBeReused() const
+ {
+ return d_reusable;
+ }
+
+ bool isUsable() const
+ {
+ return d_usable;
+ }
+
+ bool willBeReusable(bool) const
+ {
+ return d_reusable;
+ }
+
+ void setReused()
+ {
+ }
+
+ struct timeval getLastDataReceivedTime() const
+ {
+ return d_lastDataReceivedTime;
+ }
+
+ bool isIdle() const
+ {
+ return d_idle;
+ }
+
+ void stopIO()
+ {
+ }
+
+ struct timeval d_lastDataReceivedTime{0, 0};
+ bool d_reusable{true};
+ bool d_usable{true};
+ bool d_idle{false};
+};
+
+BOOST_AUTO_TEST_SUITE(test_dnsdist_connections_cache)
+
+BOOST_AUTO_TEST_CASE(test_ConnectionsCache)
+{
+ DownstreamConnectionsManager<MockupConnection> manager;
+ const size_t maxConnPerDownstream = 5;
+ const uint16_t cleanupInterval = 1;
+ const uint16_t maxIdleTime = 5;
+ manager.setMaxCachedConnectionsPerDownstream(maxConnPerDownstream);
+ manager.setCleanupInterval(cleanupInterval);
+ manager.setMaxIdleTime(maxIdleTime);
+
+ auto mplexer = std::unique_ptr<FDMultiplexer>(FDMultiplexer::getMultiplexerSilent());
+ auto downstream1 = std::make_shared<DownstreamState>(ComboAddress("192.0.2.1"));
+ auto downstream2 = std::make_shared<DownstreamState>(ComboAddress("192.0.2.2"));
+ struct timeval now;
+ gettimeofday(&now, nullptr);
+
+ auto conn = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+ BOOST_REQUIRE(conn != nullptr);
+ BOOST_CHECK_EQUAL(manager.count(), 1U);
+
+ /* since the connection can be reused, we should get the same one */
+ {
+ auto conn1 = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+ BOOST_CHECK(conn.get() == conn1.get());
+ BOOST_CHECK_EQUAL(manager.count(), 1U);
+ }
+
+ /* if we mark it non-usable, we should get a new one */
+ conn->d_usable = false;
+ auto conn2 = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+ BOOST_CHECK(conn.get() != conn2.get());
+ BOOST_CHECK_EQUAL(manager.count(), 2U);
+
+ /* since the second connection can be reused, we should get it */
+ {
+ auto conn3 = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+ BOOST_CHECK(conn3.get() == conn2.get());
+ BOOST_CHECK_EQUAL(manager.count(), 2U);
+ }
+
+ /* different downstream so different connection */
+ auto differentConn = manager.getConnectionToDownstream(mplexer, downstream2, now, std::string());
+ BOOST_REQUIRE(differentConn != nullptr);
+ BOOST_CHECK(differentConn.get() != conn.get());
+ BOOST_CHECK(differentConn.get() != conn2.get());
+ BOOST_CHECK_EQUAL(manager.count(), 3U);
+ {
+ /* but we should be able to reuse it */
+ auto sameConn = manager.getConnectionToDownstream(mplexer, downstream2, now, std::string());
+ BOOST_CHECK(sameConn.get() == differentConn.get());
+ BOOST_CHECK_EQUAL(manager.count(), 3U);
+ }
+
+ struct timeval later = now;
+ later.tv_sec += cleanupInterval + 1;
+
+ /* mark the second connection as no longer usable */
+ conn2->d_usable = false;
+ /* first one as well but still fresh so it will not get checked */
+ conn->d_usable = true;
+ conn->d_lastDataReceivedTime = later;
+ /* third one is usable but idle for too long */
+ differentConn->d_idle = true;
+ differentConn->d_lastDataReceivedTime = later;
+ differentConn->d_lastDataReceivedTime.tv_sec -= (maxIdleTime + 1);
+
+ /* we should not do an actual cleanup attempt since the last cleanup was done recently */
+ manager.cleanupClosedConnections(now);
+ BOOST_CHECK_EQUAL(manager.count(), 3U);
+
+ manager.cleanupClosedConnections(later);
+ BOOST_CHECK_EQUAL(manager.count(), 1U);
+
+ /* mark the remaining conn as non-usable, to get new ones */
+ conn->d_usable = false;
+ conn->d_lastDataReceivedTime.tv_sec = 0;
+
+ std::vector<std::shared_ptr<MockupConnection>> conns = { conn };
+ while (conns.size() < maxConnPerDownstream) {
+ auto newConn = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+ newConn->d_usable = false;
+ conns.push_back(newConn);
+ BOOST_CHECK_EQUAL(manager.count(), conns.size());
+ }
+
+ /* if we add a new one, the oldest should get expunged */
+ auto newConn = manager.getConnectionToDownstream(mplexer, downstream1, now, std::string());
+ BOOST_CHECK_EQUAL(manager.count(), maxConnPerDownstream);
+
+ {
+ /* mark all connections as not usable anymore */
+ for (auto& c : conns) {
+ c->d_usable = false;
+ }
+
+ /* except the last one */
+ newConn->d_usable = true;
+
+ BOOST_CHECK_EQUAL(manager.count(), maxConnPerDownstream);
+ later.tv_sec += cleanupInterval + 1;
+ manager.cleanupClosedConnections(later);
+ BOOST_CHECK_EQUAL(manager.count(), 1U);
+ }
+
+ conns.clear();
+ auto cleared = manager.clear();
+ BOOST_CHECK_EQUAL(cleared, 1U);
+}
+
+BOOST_AUTO_TEST_SUITE_END();