Skip to content

Commit 9c8e0d4

Browse files
authored
Fix off-by-one error in connection pooling introduced with asio certificate changes in 2.10.4 (#920)
* Fix off-by-one error introduced in connection pool stack. This got introduced when the connection pool queue was changed into a stack to reuse hot connections. * Make connection_pool_stack testable and add tests.
1 parent 943c6f8 commit 9c8e0d4

File tree

5 files changed

+120
-48
lines changed

5 files changed

+120
-48
lines changed

Release/src/CMakeLists.txt

+4-3
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ set(SOURCES
1616
${HEADERS_DETAILS}
1717
pch/stdafx.h
1818
http/client/http_client.cpp
19-
http/client/http_client_msg.cpp
2019
http/client/http_client_impl.h
21-
http/common/internal_http_helpers.h
20+
http/client/http_client_msg.cpp
21+
http/common/connection_pool_helpers.h
22+
http/common/http_compression.cpp
2223
http/common/http_helpers.cpp
2324
http/common/http_msg.cpp
24-
http/common/http_compression.cpp
25+
http/common/internal_http_helpers.h
2526
http/listener/http_listener.cpp
2627
http/listener/http_listener_msg.cpp
2728
http/listener/http_server_api.cpp

Release/src/http/client/http_client_asio.cpp

+2-43
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <sstream>
1818

1919
#include "../common/internal_http_helpers.h"
20+
#include "../common/connection_pool_helpers.h"
2021
#include "cpprest/asyncrt_utils.h"
2122

2223
#if defined(__clang__)
@@ -345,48 +346,6 @@ class asio_connection
345346
bool m_closed;
346347
};
347348

348-
class connection_pool_stack
349-
{
350-
public:
351-
// attempts to acquire a connection from the deque; returns nullptr if no connection is
352-
// available
353-
std::shared_ptr<asio_connection> try_acquire() CPPREST_NOEXCEPT
354-
{
355-
const size_t oldConnectionsSize = m_connections.size();
356-
if (m_highWater > oldConnectionsSize)
357-
{
358-
m_highWater = oldConnectionsSize;
359-
}
360-
361-
if (oldConnectionsSize == 0)
362-
{
363-
return nullptr;
364-
}
365-
366-
auto result = std::move(m_connections.back());
367-
m_connections.pop_back();
368-
return result;
369-
}
370-
371-
// releases `released` back to the connection pool
372-
void release(std::shared_ptr<asio_connection>&& released)
373-
{
374-
m_connections.push_back(std::move(released));
375-
}
376-
377-
bool free_stale_connections() CPPREST_NOEXCEPT
378-
{
379-
m_connections.erase(m_connections.begin(), m_connections.begin() + m_highWater);
380-
const size_t connectionsSize = m_connections.size();
381-
m_highWater = connectionsSize;
382-
return (connectionsSize != 0);
383-
}
384-
385-
private:
386-
size_t m_highWater = 0;
387-
std::vector<std::shared_ptr<asio_connection>> m_connections;
388-
};
389-
390349
/// <summary>Implements a connection pool with adaptive connection removal</summary>
391350
/// <remarks>
392351
/// Every 30 seconds, the lambda in `start_epoch_interval` fires, triggering the
@@ -501,7 +460,7 @@ class asio_connection_pool final : public std::enable_shared_from_this<asio_conn
501460
}
502461

503462
std::mutex m_lock;
504-
std::map<std::string, connection_pool_stack> m_connections;
463+
std::map<std::string, connection_pool_stack<asio_connection>> m_connections;
505464
bool m_is_timer_running;
506465
boost::asio::deadline_timer m_pool_epoch_timer;
507466
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#pragma once
2+
3+
#include "cpprest/details/cpprest_compat.h"
4+
#include <stddef.h>
5+
#include <memory>
6+
#include <vector>
7+
8+
namespace web
9+
{
10+
namespace http
11+
{
12+
namespace client
13+
{
14+
namespace details
15+
{
16+
17+
template<class ConnectionIsh>
18+
class connection_pool_stack
19+
{
20+
public:
21+
// attempts to acquire a connection from the deque; returns nullptr if no connection is
22+
// available
23+
std::shared_ptr<ConnectionIsh> try_acquire() CPPREST_NOEXCEPT
24+
{
25+
const size_t oldConnectionsSize = m_connections.size();
26+
if (oldConnectionsSize == 0)
27+
{
28+
m_staleBefore = 0;
29+
return nullptr;
30+
}
31+
32+
auto result = std::move(m_connections.back());
33+
m_connections.pop_back();
34+
const size_t newConnectionsSize = m_connections.size();
35+
if (m_staleBefore > newConnectionsSize)
36+
{
37+
m_staleBefore = newConnectionsSize;
38+
}
39+
40+
return result;
41+
}
42+
43+
// releases `released` back to the connection pool
44+
void release(std::shared_ptr<ConnectionIsh>&& released)
45+
{
46+
m_connections.push_back(std::move(released));
47+
}
48+
49+
bool free_stale_connections() CPPREST_NOEXCEPT
50+
{
51+
assert(m_staleBefore <= m_connections.size());
52+
m_connections.erase(m_connections.begin(), m_connections.begin() + m_staleBefore);
53+
const size_t connectionsSize = m_connections.size();
54+
m_staleBefore = connectionsSize;
55+
return (connectionsSize != 0);
56+
}
57+
58+
private:
59+
std::vector<std::shared_ptr<ConnectionIsh>> m_connections;
60+
size_t m_staleBefore = 0;
61+
};
62+
63+
} // details
64+
} // client
65+
} // http
66+
} // web

Release/tests/functional/http/client/CMakeLists.txt

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ set(SOURCES
22
authentication_tests.cpp
33
building_request_tests.cpp
44
client_construction.cpp
5+
compression_tests.cpp
6+
connection_pool_tests.cpp
57
connections_and_errors.cpp
68
header_tests.cpp
9+
http_client_fuzz_tests.cpp
710
http_client_tests.cpp
811
http_methods_tests.cpp
912
multiple_requests.cpp
@@ -20,8 +23,6 @@ set(SOURCES
2023
response_stream_tests.cpp
2124
status_code_reason_phrase_tests.cpp
2225
to_string_tests.cpp
23-
http_client_fuzz_tests.cpp
24-
compression_tests.cpp
2526
)
2627

2728
add_casablanca_test(httpclient_test SOURCES)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include "stdafx.h"
2+
#include <memory>
3+
#include "../../../src/http/common/connection_pool_helpers.h"
4+
5+
using namespace web::http::client::details;
6+
7+
SUITE(connection_pooling) {
8+
TEST(empty_returns_nullptr) {
9+
connection_pool_stack<int> connectionStack;
10+
VERIFY_ARE_EQUAL(connectionStack.try_acquire(), std::shared_ptr<int>{});
11+
}
12+
13+
static int noisyCount = 0;
14+
struct noisy {
15+
noisy() = delete;
16+
noisy(int) { ++noisyCount; }
17+
noisy(const noisy&) = delete;
18+
noisy(noisy&&) { ++noisyCount; }
19+
noisy& operator=(const noisy&) = delete;
20+
noisy& operator=(noisy&&) = delete;
21+
~noisy() { --noisyCount; }
22+
};
23+
24+
TEST(cycled_connections_survive) {
25+
connection_pool_stack<noisy> connectionStack;
26+
VERIFY_ARE_EQUAL(0, noisyCount);
27+
connectionStack.release(std::make_shared<noisy>(42));
28+
connectionStack.release(std::make_shared<noisy>(42));
29+
connectionStack.release(std::make_shared<noisy>(42));
30+
VERIFY_ARE_EQUAL(3, noisyCount);
31+
VERIFY_IS_TRUE(connectionStack.free_stale_connections());
32+
auto tmp = connectionStack.try_acquire();
33+
VERIFY_ARE_NOT_EQUAL(tmp, std::shared_ptr<noisy>{});
34+
connectionStack.release(std::move(tmp));
35+
VERIFY_ARE_EQUAL(tmp, std::shared_ptr<noisy>{});
36+
tmp = connectionStack.try_acquire();
37+
VERIFY_ARE_NOT_EQUAL(tmp, std::shared_ptr<noisy>{});
38+
connectionStack.release(std::move(tmp));
39+
VERIFY_IS_TRUE(connectionStack.free_stale_connections());
40+
VERIFY_ARE_EQUAL(1, noisyCount);
41+
VERIFY_IS_FALSE(connectionStack.free_stale_connections());
42+
VERIFY_ARE_EQUAL(0, noisyCount);
43+
VERIFY_IS_FALSE(connectionStack.free_stale_connections());
44+
}
45+
};

0 commit comments

Comments
 (0)