Skip to content

Commit 68e592c

Browse files
authored
Merge pull request #336 from Enmk/fix_issue_335_no_connection_attempts
Fixed issue #335 by making at least one connection attempt
2 parents 911ce93 + dc76dac commit 68e592c

File tree

4 files changed

+128
-9
lines changed

4 files changed

+128
-9
lines changed

clickhouse/client.cpp

+5-2
Original file line numberDiff line numberDiff line change
@@ -385,14 +385,17 @@ void Client::Impl::ResetConnectionEndpoint() {
385385
}
386386

387387
void Client::Impl::CreateConnection() {
388-
for (size_t i = 0; i < options_.send_retries;)
388+
// make sure to try to connect to each endpoint at least once even if `options_.send_retries` is 0
389+
const size_t max_attempts = (options_.send_retries ? options_.send_retries : 1);
390+
for (size_t i = 0; i < max_attempts;)
389391
{
390392
try
391393
{
394+
// Try to connect to each endpoint before throwing exception.
392395
ResetConnectionEndpoint();
393396
return;
394397
} catch (const std::system_error&) {
395-
if (++i == options_.send_retries)
398+
if (++i >= max_attempts)
396399
{
397400
throw;
398401
}

clickhouse/columns/string.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ ColumnString::ColumnString(const std::vector<std::string>& data)
173173
for (const auto & s : data) {
174174
AppendUnsafe(s);
175175
}
176-
};
176+
}
177177

178178
ColumnString::ColumnString(std::vector<std::string>&& data)
179179
: ColumnString()

ut/client_ut.cpp

+115-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,42 @@
11
#include <clickhouse/client.h>
22

3+
#include "clickhouse/base/socket.h"
34
#include "readonly_client_test.h"
45
#include "connection_failed_client_test.h"
6+
#include "ut/utils_comparison.h"
57
#include "utils.h"
8+
#include "ut/roundtrip_column.h"
9+
#include "ut/value_generators.h"
610

711
#include <gtest/gtest.h>
812

13+
#include <memory>
914
#include <optional>
15+
#include <ostream>
16+
#include <string_view>
1017
#include <thread>
1118
#include <chrono>
1219

1320
using namespace clickhouse;
1421

22+
namespace clickhouse {
23+
std::ostream & operator << (std::ostream & ostr, const Endpoint & endpoint) {
24+
return ostr << endpoint.host << ":" << endpoint.port;
25+
}
26+
}
27+
28+
template <typename T>
29+
std::shared_ptr<T> createTableWithOneColumn(Client & client, const std::string & table_name, const std::string & column_name)
30+
{
31+
auto col = std::make_shared<T>();
32+
const auto type_name = col->GetType().GetName();
33+
34+
client.Execute("DROP TEMPORARY TABLE IF EXISTS " + table_name + ";");
35+
client.Execute("CREATE TEMPORARY TABLE IF NOT EXISTS " + table_name + "( " + column_name + " " + type_name + " )");
36+
37+
return col;
38+
}
39+
1540
// Use value-parameterized tests to run same tests with different client
1641
// options.
1742
class ClientCase : public testing::TestWithParam<ClientOptions> {
@@ -27,13 +52,9 @@ class ClientCase : public testing::TestWithParam<ClientOptions> {
2752
template <typename T>
2853
std::shared_ptr<T> createTableWithOneColumn(Block & block)
2954
{
30-
auto col = std::make_shared<T>();
31-
const auto type_name = col->GetType().GetName();
32-
33-
client_->Execute("DROP TEMPORARY TABLE IF EXISTS " + table_name + ";");
34-
client_->Execute("CREATE TEMPORARY TABLE IF NOT EXISTS " + table_name + "( " + column_name + " " + type_name + " )");
55+
auto col = ::createTableWithOneColumn<T>(*client_, table_name, column_name);
3556

36-
block.AppendColumn("test_column", col);
57+
block.AppendColumn(column_name, col);
3758

3859
return col;
3960
}
@@ -1352,3 +1373,91 @@ INSTANTIATE_TEST_SUITE_P(ResetConnectionClientTest, ResetConnectionTestCase,
13521373
.SetRetryTimeout(std::chrono::seconds(1))
13531374
}
13541375
));
1376+
1377+
struct CountingSocketFactoryAdapter : public SocketFactory {
1378+
1379+
using ConnectRequests = std::vector<std::pair<ClientOptions, Endpoint>>;
1380+
1381+
SocketFactory & socket_factory;
1382+
ConnectRequests & connect_requests;
1383+
1384+
CountingSocketFactoryAdapter(SocketFactory & socket_factory, ConnectRequests& connect_requests)
1385+
: socket_factory(socket_factory)
1386+
, connect_requests(connect_requests)
1387+
{}
1388+
1389+
std::unique_ptr<SocketBase> connect(const ClientOptions& opts, const Endpoint& endpoint) {
1390+
connect_requests.emplace_back(opts, endpoint);
1391+
1392+
return socket_factory.connect(opts, endpoint);
1393+
}
1394+
1395+
void sleepFor(const std::chrono::milliseconds& duration) {
1396+
return socket_factory.sleepFor(duration);
1397+
}
1398+
1399+
size_t GetConnectRequestsCount() const {
1400+
return connect_requests.size();
1401+
}
1402+
1403+
};
1404+
1405+
TEST(SimpleClientTest, issue_335) {
1406+
// Make sure Client connects to server even with ClientOptions.SetSendRetries(0)
1407+
auto vals = MakeStrings();
1408+
auto col = std::make_shared<ColumnString>(vals);
1409+
1410+
CountingSocketFactoryAdapter::ConnectRequests connect_requests;
1411+
std::unique_ptr<SocketFactory> wrapped_socket_factory = std::make_unique<NonSecureSocketFactory>();
1412+
std::unique_ptr<SocketFactory> socket_factory = std::make_unique<CountingSocketFactoryAdapter>(*wrapped_socket_factory, connect_requests);
1413+
1414+
Client client(ClientOptions(LocalHostEndpoint)
1415+
.SetSendRetries(0), // <<=== crucial for reproducing https://github.com/ClickHouse/clickhouse-cpp/issues/335
1416+
std::move(socket_factory));
1417+
1418+
EXPECT_EQ(1u, connect_requests.size());
1419+
EXPECT_TRUE(CompareRecursive(vals, *RoundtripColumnValuesTyped(client, col)));
1420+
1421+
connect_requests.clear();
1422+
1423+
client.ResetConnection();
1424+
EXPECT_EQ(1u, connect_requests.size());
1425+
EXPECT_TRUE(CompareRecursive(vals, *RoundtripColumnValuesTyped(client, col)));
1426+
1427+
connect_requests.clear();
1428+
1429+
client.ResetConnectionEndpoint();
1430+
EXPECT_EQ(1u, connect_requests.size());
1431+
EXPECT_TRUE(CompareRecursive(vals, *RoundtripColumnValuesTyped(client, col)));
1432+
}
1433+
1434+
TEST(SimpleClientTest, issue_335_reconnects_count) {
1435+
// Make sure that client attempts to connect to each endpoint at least once.
1436+
CountingSocketFactoryAdapter::ConnectRequests connect_requests;
1437+
std::unique_ptr<SocketFactory> wrapped_socket_factory = std::make_unique<NonSecureSocketFactory>();
1438+
std::unique_ptr<SocketFactory> socket_factory = std::make_unique<CountingSocketFactoryAdapter>(*wrapped_socket_factory, connect_requests);
1439+
1440+
const std::vector<Endpoint> endpoints = {
1441+
Endpoint{"foo-invalid-hostname", 1234},
1442+
Endpoint{"bar-invalid-hostname", 4567},
1443+
};
1444+
1445+
EXPECT_ANY_THROW(
1446+
Client(ClientOptions()
1447+
.SetEndpoints(endpoints)
1448+
.SetSendRetries(0), // <<=== crucial for reproducing https://github.com/ClickHouse/clickhouse-cpp/issues/335
1449+
std::move(socket_factory));
1450+
);
1451+
1452+
EXPECT_EQ(endpoints.size(), connect_requests.size());
1453+
// make sure there was an attempt to connect to each endpoint at least once.
1454+
for (const auto & endpoint : endpoints)
1455+
{
1456+
auto p = std::find_if(connect_requests.begin(), connect_requests.end(), [&endpoint](const auto & connect_request) {
1457+
return connect_request.second == endpoint;
1458+
});
1459+
1460+
EXPECT_TRUE(connect_requests.end() != p)
1461+
<< "\tThere was no attempt to connect to endpoint " << endpoint;
1462+
}
1463+
}

ut/roundtrip_column.h

+7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
#pragma once
22

33
#include <clickhouse/columns/column.h>
4+
#include <memory>
45

56
namespace clickhouse {
67
class Client;
78
}
89

910
clickhouse::ColumnRef RoundtripColumnValues(clickhouse::Client& client, clickhouse::ColumnRef expected);
11+
12+
template <typename T>
13+
auto RoundtripColumnValuesTyped(clickhouse::Client& client, std::shared_ptr<T> expected_col)
14+
{
15+
return RoundtripColumnValues(client, expected_col)->template As<T>();
16+
}

0 commit comments

Comments
 (0)