From 8d403e23fcc279fd0a7c8f96e8fe079717827598 Mon Sep 17 00:00:00 2001 From: "Jonathan M. Henson" Date: Tue, 18 Jun 2019 17:57:00 -0700 Subject: [PATCH] Updated dependencies to latest versions. (#31) * Updated dependencies to latest versions. --- aws-common-runtime/CMakeLists.txt | 8 +- include/aws/crt/http/HttpConnection.h | 8 +- include/aws/crt/http/HttpConnectionManager.h | 38 +-- source/http/HttpConnection.cpp | 48 ++- source/http/HttpConnectionManager.cpp | 313 +++++-------------- tests/HttpClientConnectionManagerTest.cpp | 32 +- 6 files changed, 130 insertions(+), 317 deletions(-) diff --git a/aws-common-runtime/CMakeLists.txt b/aws-common-runtime/CMakeLists.txt index 864968ef7..80b91863b 100644 --- a/aws-common-runtime/CMakeLists.txt +++ b/aws-common-runtime/CMakeLists.txt @@ -17,7 +17,7 @@ set(AWS_DEPS_DOWNLOAD_DIR "${AWS_DEPS_BUILD_DIR}/downloads" CACHE PATH "Dependen message("install dir ${AWS_DEPS_INSTALL_DIR}") set(AWS_C_COMMON_URL "https://github.com/awslabs/aws-c-common.git") -set(AWS_C_COMMON_SHA "v0.3.11") +set(AWS_C_COMMON_SHA "v0.3.14") include(BuildAwsCCommon) if (UNIX AND NOT APPLE) @@ -27,7 +27,7 @@ if (UNIX AND NOT APPLE) endif() set(AWS_C_IO_URL "https://github.com/awslabs/aws-c-io.git") -set(AWS_C_IO_SHA "v0.3.9") +set(AWS_C_IO_SHA "v0.3.14") include(BuildAwsCIO) set(AWS_C_COMPRESSION_URL "https://github.com/awslabs/aws-c-compression.git") @@ -35,11 +35,11 @@ set(AWS_C_COMPRESSION_SHA "v0.2.1") include(BuildAwsCCompression) set(AWS_C_HTTP_URL "https://github.com/awslabs/aws-c-http.git") -set(AWS_C_HTTP_SHA "v0.2.16") +set(AWS_C_HTTP_SHA "v0.2.18") include(BuildAwsCHttp) set(AWS_C_MQTT_URL "https://github.com/awslabs/aws-c-mqtt.git") -set(AWS_C_MQTT_SHA "v0.3.7") +set(AWS_C_MQTT_SHA "v0.3.8") include(BuildAwsCMqtt) set(AWS_C_CAL_URL "https://github.com/awslabs/aws-c-cal.git") diff --git a/include/aws/crt/http/HttpConnection.h b/include/aws/crt/http/HttpConnection.h index 347152d13..49903571c 100644 --- a/include/aws/crt/http/HttpConnection.h +++ b/include/aws/crt/http/HttpConnection.h @@ -303,10 +303,10 @@ namespace Aws /** * Represents a connection from a Http Client to a Server. */ - class AWS_CRT_CPP_API HttpClientConnection final : public std::enable_shared_from_this + class AWS_CRT_CPP_API HttpClientConnection : public std::enable_shared_from_this { public: - ~HttpClientConnection(); + virtual ~HttpClientConnection() = default; HttpClientConnection(const HttpClientConnection &) = delete; HttpClientConnection(HttpClientConnection &&) = delete; HttpClientConnection &operator=(const HttpClientConnection &) = delete; @@ -350,9 +350,11 @@ namespace Aws */ static bool CreateConnection(const HttpClientConnectionOptions &connectionOptions) noexcept; - private: + protected: HttpClientConnection(aws_http_connection *m_connection, Allocator *allocator) noexcept; aws_http_connection *m_connection; + + private: Allocator *m_allocator; int m_lastError; diff --git a/include/aws/crt/http/HttpConnectionManager.h b/include/aws/crt/http/HttpConnectionManager.h index 29fedda75..8d76d241c 100644 --- a/include/aws/crt/http/HttpConnectionManager.h +++ b/include/aws/crt/http/HttpConnectionManager.h @@ -17,6 +17,8 @@ #include #include +struct aws_http_connection_manager; + namespace Aws { namespace Crt @@ -26,10 +28,10 @@ namespace Aws /** * Invoked when a connection from the pool is available. If a connection was successfully obtained * the connection shared_ptr can be seated into your own copy of connection. If it failed, errorCode - * will be non-zero. It is your responsibility to release the connection when you are finished with it. + * will be non-zero. */ using OnClientConnectionAvailable = - std::function connection, int errorCode)>; + std::function, int errorCode)>; struct HttpClientConnectionManagerOptions { @@ -54,17 +56,11 @@ namespace Aws /** * Acquires a connection from the pool. onClientConnectionAvailable will be invoked upon an available * connection. Returns true if the connection request was successfully pooled, returns false if it - * failed. On failure, onClientConnectionAvailable will not be invoked. After receiving a connection, - * you must invoke ReleaseConnection(). + * failed. On failure, onClientConnectionAvailable will not be invoked. After receiving a connection, it + * will automatically be cleaned up when your last reference to the shared_ptr is released. */ bool AcquireConnection(const OnClientConnectionAvailable &onClientConnectionAvailable) noexcept; - /** - * Releases a connection back to the pool. This will cause queued consumers to be serviced, or the - * connection will be pooled waiting on another call to AcquireConnection - */ - void ReleaseConnection(std::shared_ptr connection) noexcept; - int LastError() const noexcept { return m_lastError; } explicit operator bool() const noexcept { return m_good; } @@ -77,26 +73,20 @@ namespace Aws const HttpClientConnectionManagerOptions &connectionManagerOptions, Allocator *allocator = DefaultAllocator()) noexcept; - Vector> m_connections; - List m_pendingConnectionRequests; + aws_http_connection_manager *m_connectionManager; + Allocator *m_allocator; Io::ClientBootstrap *m_bootstrap; - size_t m_initialWindowSize; - Io::SocketOptions m_socketOptions; Io::TlsConnectionOptions m_tlsConnOptions; - String m_hostName; - uint16_t m_port; bool m_good; int m_lastError; - size_t m_maxSize; - size_t m_outstandingVendedConnections; - size_t m_pendingConnections; - std::mutex m_connectionsLock; - void onConnectionSetup(const std::shared_ptr &connection, int errorCode) noexcept; - void onConnectionShutdown(HttpClientConnection &connection, int errorCode) noexcept; - bool createConnection() noexcept; - void poolOrVendConnection(std::shared_ptr connection, bool isRelease) noexcept; + static void s_onConnectionSetup( + aws_http_connection *connection, + int errorCode, + void *userData) noexcept; + + friend class ManagedConnection; }; } // namespace Http } // namespace Crt diff --git a/source/http/HttpConnection.cpp b/source/http/HttpConnection.cpp index 98ef363a1..4f4581b2d 100644 --- a/source/http/HttpConnection.cpp +++ b/source/http/HttpConnection.cpp @@ -39,6 +39,24 @@ namespace Aws OnConnectionShutdown onConnectionShutdown; }; + class UnmanagedConnection final : public HttpClientConnection + { + public: + UnmanagedConnection(aws_http_connection *connection, Aws::Crt::Allocator *allocator) + : HttpClientConnection(connection, allocator) + { + } + + ~UnmanagedConnection() override + { + if (m_connection) + { + aws_http_connection_release(m_connection); + m_connection = nullptr; + } + } + }; + void HttpClientConnection::s_onClientConnectionSetup( struct aws_http_connection *connection, int errorCode, @@ -50,28 +68,18 @@ namespace Aws auto *callbackData = static_cast(user_data); if (!errorCode) { - Allocator *allocator = callbackData->allocator; + auto connectionObj = std::allocate_shared( + Aws::Crt::StlAllocator(), connection, callbackData->allocator); - /* Why aren't we using New...? Because the constructor is private and the C++ type system - * isn't the boss of me. */ - auto *toSeat = - static_cast(aws_mem_acquire(allocator, sizeof(HttpClientConnection))); - if (toSeat) + if (connectionObj) { - toSeat = new (toSeat) HttpClientConnection(connection, allocator); - auto sharedPtr = std::shared_ptr( - toSeat, [allocator](HttpClientConnection *connection) { Delete(connection, allocator); }); - - callbackData->connection = sharedPtr; - - callbackData->onConnectionSetup(std::move(sharedPtr), errorCode); + callbackData->connection = connectionObj; + callbackData->onConnectionSetup(std::move(connectionObj), errorCode); return; } aws_http_connection_release(connection); errorCode = aws_last_error(); - callbackData->onConnectionSetup(nullptr, errorCode); - return; } callbackData->onConnectionSetup(nullptr, errorCode); @@ -143,16 +151,6 @@ namespace Aws { } - HttpClientConnection::~HttpClientConnection() - { - if (m_connection) - { - /* TODO: invoke shutdown callback from here if it hasn't fired yet */ - aws_http_connection_release(m_connection); - m_connection = nullptr; - } - } - struct ClientStreamCallbackData { Allocator *allocator; diff --git a/source/http/HttpConnectionManager.cpp b/source/http/HttpConnectionManager.cpp index 12d788090..7d617aae1 100644 --- a/source/http/HttpConnectionManager.cpp +++ b/source/http/HttpConnectionManager.cpp @@ -15,6 +15,7 @@ #include #include +#include namespace Aws { @@ -22,6 +23,13 @@ namespace Aws { namespace Http { + struct ConnectionManagerCallbackArgs + { + ConnectionManagerCallbackArgs() = default; + OnClientConnectionAvailable m_onClientConnectionAvailable; + std::shared_ptr m_connectionManager; + }; + HttpClientConnectionManagerOptions::HttpClientConnectionManagerOptions() : bootstrap(nullptr), initialWindowSize(SIZE_MAX), port(0), maxConnections(2) { @@ -48,17 +56,10 @@ namespace Aws HttpClientConnectionManager::HttpClientConnectionManager( const HttpClientConnectionManagerOptions &connectionManagerOptions, Allocator *allocator) noexcept - : m_allocator(allocator), m_good(true), m_lastError(AWS_ERROR_SUCCESS), - m_outstandingVendedConnections(0), m_pendingConnections(0) + : m_connectionManager(nullptr), m_allocator(allocator), m_good(true), m_lastError(AWS_ERROR_SUCCESS) { - m_initialWindowSize = connectionManagerOptions.initialWindowSize; m_bootstrap = connectionManagerOptions.bootstrap; AWS_ASSERT(connectionManagerOptions.hostName.ptr && connectionManagerOptions.hostName.len); - m_hostName = - String((const char *)connectionManagerOptions.hostName.ptr, connectionManagerOptions.hostName.len); - m_maxSize = connectionManagerOptions.maxConnections; - m_port = connectionManagerOptions.port; - m_socketOptions = *connectionManagerOptions.socketOptions; if (connectionManagerOptions.tlsConnectionOptions) { @@ -70,280 +71,114 @@ namespace Aws m_good = false; } } - } - HttpClientConnectionManager::~HttpClientConnectionManager() - { - Vector> connectionsCopy = m_connections; - /* make sure all connections we know about are closed. */ - for (auto &connection : connectionsCopy) + aws_http_connection_manager_options managerOptions; + AWS_ZERO_STRUCT(managerOptions); + managerOptions.bootstrap = m_bootstrap->GetUnderlyingHandle(); + managerOptions.port = connectionManagerOptions.port; + managerOptions.max_connections = connectionManagerOptions.maxConnections; + managerOptions.socket_options = connectionManagerOptions.socketOptions; + managerOptions.initial_window_size = connectionManagerOptions.initialWindowSize; + + if (m_tlsConnOptions) { - connection->Close(); + managerOptions.tls_connection_options = + const_cast(m_tlsConnOptions.GetUnderlyingHandle()); } + managerOptions.host = connectionManagerOptions.hostName; - /* - * This would be really screwy if this ever happened. I don't even know what error to report, but in - * case someone is waiting for a connection, AT LEAST let them know so they can not deadlock. - */ - for (auto &pendingConnectionRequest : m_pendingConnectionRequests) + m_connectionManager = aws_http_connection_manager_new(allocator, &managerOptions); + + if (!m_connectionManager) { - pendingConnectionRequest(nullptr, AWS_OP_ERR); + m_lastError = aws_last_error(); + m_good = false; } } - /* Asssumption: Whoever calls this already holds the lock. */ - bool HttpClientConnectionManager::createConnection() noexcept + HttpClientConnectionManager::~HttpClientConnectionManager() { - if (m_connections.size() + m_outstandingVendedConnections + m_pendingConnections < m_maxSize) + if (m_connectionManager) { - HttpClientConnectionOptions connectionOptions; - connectionOptions.socketOptions = &m_socketOptions; - connectionOptions.port = m_port; - connectionOptions.hostName = ByteCursorFromCString(m_hostName.c_str()); - connectionOptions.bootstrap = m_bootstrap; - connectionOptions.initialWindowSize = m_initialWindowSize; - connectionOptions.allocator = m_allocator; - - std::weak_ptr weakRef(shared_from_this()); - - connectionOptions.onConnectionSetup = - [weakRef](const std::shared_ptr &connection, int errorCode) { - if (auto connManager = weakRef.lock()) - { - connManager->onConnectionSetup(connection, errorCode); - } - }; - connectionOptions.onConnectionShutdown = - [weakRef](HttpClientConnection &connection, int errorCode) { - if (auto connManager = weakRef.lock()) - { - connManager->onConnectionShutdown(connection, errorCode); - } - }; - - if (m_tlsConnOptions) - { - connectionOptions.tlsConnOptions = &m_tlsConnOptions; - } - - if (HttpClientConnection::CreateConnection(connectionOptions)) - { - ++m_pendingConnections; - return true; - } - return false; + aws_http_connection_manager_release(m_connectionManager); + m_connectionManager = nullptr; } - - return true; } - /* User wants to acquire a connection from the pool, there's a few cases: - * - * 1.) We already have a free connection, so return it to the user immediately. We don't care about the - * queued connection requests, since it is impossible for there to be a pending connection acquisition - * AND a free connection. - * - * 2.) We don't have a free connection AND we have not exceeded the pool size limits. Queue the request and - * wait for the connection setup callback to fire. That callback will invoke the pending request. - * - * 3.) We don't have a free connection AND the pool size has been reached. Queue the request, a connection - * release will pop the queue and invoke the user's callback with a connection. - */ bool HttpClientConnectionManager::AcquireConnection( const OnClientConnectionAvailable &onClientConnectionAvailable) noexcept { - OnClientConnectionAvailable userCallback; - std::shared_ptr connection(nullptr); - int callbackError = AWS_ERROR_SUCCESS; - bool retVal = true; - - { - std::lock_guard connectionsLock(m_connectionsLock); - - /* Case 1 */ - if (!m_connections.empty()) - { - connection = m_connections.back(); - m_connections.pop_back(); - ++m_outstandingVendedConnections; - userCallback = onClientConnectionAvailable; - callbackError = AWS_ERROR_SUCCESS; - retVal = true; - } - else - { - /* case 2 or 3, we don't know yet. */ - m_pendingConnectionRequests.push_back(onClientConnectionAvailable); - /* if we have available space, case 2, otherwise case 3 */ - if (!createConnection()) - { - /* case 2 and the connection creation failed, pop back, because in this rare case, we want - * to just tell the caller that our entire attempt at this failed. */ - m_pendingConnectionRequests.pop_back(); - m_lastError = aws_last_error(); - userCallback = onClientConnectionAvailable; - callbackError = m_lastError; - retVal = false; - } - } - } + auto connectionManagerCallbackArgs = Aws::Crt::New(m_allocator); - if (userCallback) + if (!connectionManagerCallbackArgs) { - userCallback(connection, callbackError); + m_lastError = aws_last_error(); + return false; } - return retVal; + connectionManagerCallbackArgs->m_connectionManager = shared_from_this(); + connectionManagerCallbackArgs->m_onClientConnectionAvailable = onClientConnectionAvailable; + + aws_http_connection_manager_acquire_connection( + m_connectionManager, s_onConnectionSetup, connectionManagerCallbackArgs); + return true; } - /* - * User is finished with a connection and wants to return it to the pool. - * - * Case 1, The connection is actually dead and can't be returned to the pool. - * Case 1.1 We don't have any pending connection acquisition requests so just let it hit the floor, it - * will get created in the next Acquire call. Case 1.2 We have pending connection acquisition requests, so - * create a new connection to replace it, let the callback's fire to notify the user and grow the pool as - * normal. - * - * Case 2, the connection is still good. - * Case 2.1 We have pending acquisitions. Just give it to the top of the queue. - * Case 2.2 We don't have pending acquisitions, push it to the pool. - */ - void HttpClientConnectionManager::poolOrVendConnection( - std::shared_ptr connection, - bool isRelease) noexcept + class ManagedConnection final : public HttpClientConnection { - OnClientConnectionAvailable userCallback; - int callbackError = AWS_ERROR_SUCCESS; - std::shared_ptr vendedConnection(nullptr); - bool poolOrVend = true; - + public: + ManagedConnection( + aws_http_connection *connection, + std::shared_ptr connectionManager) + : HttpClientConnection(connection, connectionManager->m_allocator), + m_connectionManager(std::move(connectionManager)) { - std::lock_guard connectionsLock(m_connectionsLock); - - if (isRelease) - { - --m_outstandingVendedConnections; - } - else - { - --m_pendingConnections; - } - - /* Case 1 */ - if (!connection->IsOpen()) - { - poolOrVend = false; - /* Case 1.2 */ - if (!m_pendingConnectionRequests.empty()) - { - /* if connection fails, tell the front of the pendingConnectionRequests queue - * that their connection failed so they can retry. */ - if (!createConnection()) - { - userCallback = m_pendingConnectionRequests.front(); - m_pendingConnectionRequests.pop_front(); - m_lastError = aws_last_error(); - callbackError = m_lastError; - } - } - connection = nullptr; - /* Case 1.1 */ - } + } - /* Case 2.1*/ - if (poolOrVend && !m_pendingConnectionRequests.empty()) - { - userCallback = m_pendingConnectionRequests.front(); - m_pendingConnectionRequests.pop_front(); - ++m_outstandingVendedConnections; - callbackError = AWS_ERROR_SUCCESS; - vendedConnection = std::move(connection); - } - else if (poolOrVend) + ~ManagedConnection() override + { + if (m_connection) { - /* Case 2.2 */ - m_connections.push_back(std::move(connection)); + aws_http_connection_manager_release_connection( + m_connectionManager->m_connectionManager, m_connection); + m_connection = nullptr; } } - if (userCallback) - { - userCallback(vendedConnection, callbackError); - } - } + private: + std::shared_ptr m_connectionManager; + }; - void HttpClientConnectionManager::ReleaseConnection( - std::shared_ptr connection) noexcept + void HttpClientConnectionManager::s_onConnectionSetup( + aws_http_connection *connection, + int errorCode, + void *userData) noexcept { - poolOrVendConnection(std::move(connection), true); - } + auto callbackArgs = static_cast(userData); + auto manager = std::move(callbackArgs->m_connectionManager); + auto callback = std::move(callbackArgs->m_onClientConnectionAvailable); - void HttpClientConnectionManager::onConnectionSetup( - const std::shared_ptr &connection, - int errorCode) noexcept - { - if (!errorCode && connection) + Delete(callbackArgs, manager->m_allocator); + + if (errorCode) { - poolOrVendConnection(connection, false); + callback(nullptr, errorCode); return; } - /* this only gets hit if the connection setup failed. */ - Vector> userCallbackNewConnectionFailures; - { - std::lock_guard connectionsLock(m_connectionsLock); - --m_pendingConnections; - if (!m_pendingConnectionRequests.empty()) - { - userCallbackNewConnectionFailures.push_back(std::pair( - m_pendingConnectionRequests.front(), errorCode)); - m_pendingConnectionRequests.pop_front(); - } - - /* if we had more pending connection requests than the pool size, we need to replace this - connection. Just loop until we've matched either the connectionRequest to the max amount of - connections in flight, or when run out of pending connection requests. */ - while (m_pendingConnectionRequests.size() > m_pendingConnections && - m_connections.size() + m_pendingConnections + m_outstandingVendedConnections < m_maxSize) - { - if (!createConnection()) - { - userCallbackNewConnectionFailures.push_back(std::pair( - m_pendingConnectionRequests.front(), aws_last_error())); - m_pendingConnectionRequests.pop_front(); - m_lastError = aws_last_error(); - } - } - } + auto connectionObj = std::allocate_shared( + Aws::Crt::StlAllocator(), connection, manager); - for (auto &failures : userCallbackNewConnectionFailures) + if (!connectionObj) { - failures.first(nullptr, failures.second); + callback(nullptr, AWS_ERROR_OOM); + return; } - userCallbackNewConnectionFailures.clear(); + callback(connectionObj, AWS_OP_SUCCESS); } - void HttpClientConnectionManager::onConnectionShutdown(HttpClientConnection &connection, int) noexcept - { - { - std::lock_guard connectionsLock(m_connectionsLock); - if (!m_connections.empty()) - { - auto toRemove = std::remove_if( - m_connections.begin(), m_connections.end(), [&](std::shared_ptr val) { - return val.get() == &connection; - }); - - if (toRemove != m_connections.end()) - { - m_connections.erase(toRemove); - } - } - } - } } // namespace Http } // namespace Crt } // namespace Aws diff --git a/tests/HttpClientConnectionManagerTest.cpp b/tests/HttpClientConnectionManagerTest.cpp index c2ca1ee05..267b0ed07 100644 --- a/tests/HttpClientConnectionManagerTest.cpp +++ b/tests/HttpClientConnectionManagerTest.cpp @@ -49,7 +49,7 @@ static int s_TestHttpClientConnectionManagerResourceSafety(struct aws_allocator AWS_ZERO_STRUCT(socketOptions); socketOptions.type = AWS_SOCKET_STREAM; socketOptions.domain = AWS_SOCKET_IPV4; - socketOptions.connect_timeout_ms = 1000; + socketOptions.connect_timeout_ms = 10000; Aws::Crt::Io::EventLoopGroup eventLoopGroup(0, allocator); ASSERT_TRUE(eventLoopGroup); @@ -111,10 +111,9 @@ static int s_TestHttpClientConnectionManagerResourceSafety(struct aws_allocator ASSERT_TRUE(connectionCount > 0); Vector> connectionsCpy = connections; connections.clear(); - for (auto &connection : connectionsCpy) - { - connectionManager->ReleaseConnection(connection); - } + + /* this will trigger a mutation to connections, hence the copy. */ + connectionsCpy.clear(); { std::lock_guard lockGuard(semaphoreLock); @@ -151,7 +150,7 @@ static int s_TestHttpClientConnectionWithPendingAcquisitions(struct aws_allocato AWS_ZERO_STRUCT(socketOptions); socketOptions.type = AWS_SOCKET_STREAM; socketOptions.domain = AWS_SOCKET_IPV4; - socketOptions.connect_timeout_ms = 1000; + socketOptions.connect_timeout_ms = 10000; Aws::Crt::Io::EventLoopGroup eventLoopGroup(0, allocator); ASSERT_TRUE(eventLoopGroup); @@ -215,29 +214,21 @@ static int s_TestHttpClientConnectionWithPendingAcquisitions(struct aws_allocato Vector> connectionsCpy = connections; connections.clear(); - for (auto &connection : connectionsCpy) - { - connectionManager->ReleaseConnection(connection); - } + connectionsCpy.clear(); { std::lock_guard lockGuard(semaphoreLock); /* release should have given us more connections. */ ASSERT_FALSE(connections.empty()); - connectionsCpy.clear(); connectionsCpy = connections; connections.clear(); } - for (auto &connection : connectionsCpy) - { - connectionManager->ReleaseConnection(connection); - } + connectionsCpy.clear(); { std::lock_guard lockGuard(semaphoreLock); connections.clear(); } - connectionsCpy.clear(); /* now let everything tear down and make sure we don't leak or deadlock.*/ return AWS_OP_SUCCESS; @@ -260,7 +251,7 @@ static int s_TestHttpClientConnectionWithPendingAcquisitionsAndClosedConnections AWS_ZERO_STRUCT(socketOptions); socketOptions.type = AWS_SOCKET_STREAM; socketOptions.domain = AWS_SOCKET_IPV4; - socketOptions.connect_timeout_ms = 1000; + socketOptions.connect_timeout_ms = 10000; Aws::Crt::Io::TlsContext tlsContext(tlsCtxOptions, Aws::Crt::Io::TlsMode::CLIENT, allocator); ASSERT_TRUE(tlsContext); @@ -339,17 +330,14 @@ static int s_TestHttpClientConnectionWithPendingAcquisitionsAndClosedConnections { connection->Close(); } - connectionManager->ReleaseConnection(connection); + connection.reset(); } std::unique_lock uniqueLock(semaphoreLock); semaphore.wait(uniqueLock, [&]() { return (connectionCount + connectionsFailed == totalExpectedConnections); }); /* release should have given us more connections. */ ASSERT_FALSE(connections.empty()); - for (auto &connection : connections) - { - connectionManager->ReleaseConnection(connection); - } + connections.clear(); /* now let everything tear down and make sure we don't leak or deadlock.*/ return AWS_OP_SUCCESS;