diff --git a/.github/workflows/run.sh b/.github/workflows/run.sh index e3f726b..c6a7751 100755 --- a/.github/workflows/run.sh +++ b/.github/workflows/run.sh @@ -66,7 +66,7 @@ elif [[ "$1" == "docs" ]] ; then elif [[ "$1" == "lint" ]] ; then shift 1 - ./lint.py --color=always --style=file --build-path=./build --recursive "$@" include/ + ./lint.py --color=always --style=file --exclude-tidy=*.ipp --build-path=./build --recursive "$@" include/ elif [[ "$1" == "build" ]] ; then shift 1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6177df7..b36b626 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## 1.3.0 (2022-02-16) + +### Features + +* Support pool refill strategy and implement conservative and greedy (default) - (#20) + +### Misc + +* cmake: Use system googletest library if found +* cmake: Mute googletest warning +* workflow: Check .ipp files with clang-format + + ## 1.2.0 (2021-11-24) ### Features diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a10065..a9771e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) set(STREAMCLIENT_VERSION_MAJOR "1") -set(STREAMCLIENT_VERSION_MINOR "2") +set(STREAMCLIENT_VERSION_MINOR "3") set(STREAMCLIENT_VERSION_RELEASE "0") set(STREAMCLIENT_SUMMARY "C++ library") set(STREAMCLIENT_REPOSITORY_URL "https://github.com/TinkoffCreditSystems/stream-client") @@ -25,7 +25,7 @@ set(CMAKE_CXX_STANDARD 14) # todo: upgrade to 17 set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-compound-token-split-by-macro") ## Compile as RelWithDebInfo info by default if(NOT CMAKE_BUILD_TYPE) diff --git a/README.md b/README.md index 1f1439d..48e0c4e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Language C++](https://img.shields.io/badge/language-c++-blue.svg?logo=c%2B%2B)](https://isocpp.org) [![Github releases](https://img.shields.io/github/release/TinkoffCreditSystems/stream-client.svg)](https://github.com/TinkoffCreditSystems/stream-client/releases) -[![Coverage Status](https://coveralls.io/repos/github/TinkoffCreditSystems/stream-client/badge.svg?branch=develop)](https://coveralls.io/github/TinkoffCreditSystems/stream-client?branch=develop) +[![Coverage Status](https://coveralls.io/repos/github/Tinkoff/stream-client/badge.svg?branch=develop)](https://coveralls.io/github/Tinkoff/stream-client?branch=develop) [![License](https://img.shields.io/github/license/TinkoffCreditSystems/stream-client.svg)](./LICENSE) This is a lightweight, header-only, Boost-based library providing client-side network primitives to easily organize and implement data transmission with remote endpoints. @@ -119,6 +119,11 @@ client->receive(boost::asio::buffer(&recv_data[0], send_data.size())); Represents container occupied with opened sockets. Uses [connector](#connector) to open new sockets in the background thread which is triggered once there are vacant places in the pool. User can call *get_session()* to obtain a socket from the pool and *return_session()* to give it back. +There are two strategies to refill the pool: +- greedy (`stream_client::connector::greedy_strategy`). If there are vacant places it will try to fill them with new sessions simultaneously. +- conservative (`stream_client::connector::conservative_strategy`). Will try to fill up to 2/3 of vacant places in the poll. If failed will back of for some time and retry later. Also, after failures it will create only one new session. +Both of them are defined in terms of `stream_client::connector::pool_strategy` interface, so you are free to implement new one. + Limitations: 1. Sockets that are already in the pool are not checked or maintained in any way. Hence, the pool doesn't guarantee that all sockets are opened at an arbitrary point in time due to the complexity of such checks for all supported protocols. 2. Nothing specific done with sockets upon their return within *return_session()*. Therefore, if they have or will have pending data to read, it will stay there until reading. @@ -138,6 +143,8 @@ Connection pools: * `stream_client::connector::http_pool` - pool of `stream_client::http::http_client` sockets. * `stream_client::connector::https_pool` - pool of `stream_client::http::https_client` sockets. +*All these pools are using `stream_client::connector::greedy_strategy`.* + #### Example ```c++ const std::chrono::milliseconds resolve_timeout(5000); diff --git a/include/stream-client/connector/connection_pool.hpp b/include/stream-client/connector/connection_pool.hpp index b1d0991..700f161 100644 --- a/include/stream-client/connector/connection_pool.hpp +++ b/include/stream-client/connector/connection_pool.hpp @@ -1,6 +1,7 @@ #pragma once #include "connector.hpp" +#include "pool_strategy.hpp" #include #include @@ -20,8 +21,9 @@ namespace connector { * @note Thread-safe. Single instance support concurrent operation. * * @tparam Connector Type of connector to use to create sockets. + * @tparam Strategy Type of reconnection strategy. For more info look in pool_strategy.hpp. */ -template +template > class base_connection_pool { public: @@ -312,6 +314,7 @@ class base_connection_pool /// Background routine used to maintain the pool. void watch_pool_routine(); + Strategy reconnection_; connector_type connector_; ///< Underlying connector used to establish sockets. std::size_t pool_max_size_; ///< Number of stream to keep in the @p sesson_pool_. diff --git a/include/stream-client/connector/impl/connection_pool.ipp b/include/stream-client/connector/impl/connection_pool.ipp index b8d9e57..433eb3c 100644 --- a/include/stream-client/connector/impl/connection_pool.ipp +++ b/include/stream-client/connector/impl/connection_pool.ipp @@ -3,9 +3,10 @@ namespace stream_client { namespace connector { -template +template template -base_connection_pool::base_connection_pool(std::size_t size, time_duration_type idle_timeout, ArgN&&... argn) +base_connection_pool::base_connection_pool(std::size_t size, time_duration_type idle_timeout, + ArgN&&... argn) : connector_(std::forward(argn)...) , pool_max_size_(size) , idle_timeout_(idle_timeout) @@ -14,16 +15,16 @@ base_connection_pool::base_connection_pool(std::size_t size, time_dur pool_watcher_ = std::thread([this]() { this->watch_pool_routine(); }); } -template +template template ::value>::type*> -base_connection_pool::base_connection_pool(std::size_t size, Arg1&& arg1, ArgN&&... argn) +base_connection_pool::base_connection_pool(std::size_t size, Arg1&& arg1, ArgN&&... argn) : base_connection_pool(size, time_duration_type::max(), std::forward(arg1), std::forward(argn)...) { } -template -base_connection_pool::~base_connection_pool() +template +base_connection_pool::~base_connection_pool() { watch_pool_.store(false, std::memory_order_release); if (pool_watcher_.joinable()) { @@ -31,9 +32,9 @@ base_connection_pool::~base_connection_pool() } } -template -std::unique_ptr::stream_type> -base_connection_pool::get_session(boost::system::error_code& ec, const time_point_type& deadline) +template +std::unique_ptr::stream_type> +base_connection_pool::get_session(boost::system::error_code& ec, const time_point_type& deadline) { std::unique_lock pool_lk(pool_mutex_, std::defer_lock); if (!pool_lk.try_lock_until(deadline)) { @@ -52,9 +53,10 @@ base_connection_pool::get_session(boost::system::error_code& ec, cons return session; } -template -std::unique_ptr::stream_type> -base_connection_pool::try_get_session(boost::system::error_code& ec, const time_point_type& deadline) +template +std::unique_ptr::stream_type> +base_connection_pool::try_get_session(boost::system::error_code& ec, + const time_point_type& deadline) { std::unique_lock pool_lk(pool_mutex_, std::defer_lock); if (!pool_lk.try_lock_until(deadline)) { @@ -73,8 +75,8 @@ base_connection_pool::try_get_session(boost::system::error_code& ec, return session; } -template -void base_connection_pool::return_session(std::unique_ptr&& session) +template +void base_connection_pool::return_session(std::unique_ptr&& session) { if (!session || !session->next_layer().is_open()) { return; @@ -88,11 +90,12 @@ void base_connection_pool::return_session(std::unique_ptr -bool base_connection_pool::is_connected(boost::system::error_code& ec, const time_point_type& deadline) const +template +bool base_connection_pool::is_connected(boost::system::error_code& ec, + const time_point_type& deadline) const { std::unique_lock pool_lk(pool_mutex_, std::defer_lock); if (!pool_lk.try_lock_until(deadline)) { @@ -107,8 +110,8 @@ bool base_connection_pool::is_connected(boost::system::error_code& ec return true; } -template -void base_connection_pool::watch_pool_routine() +template +void base_connection_pool::watch_pool_routine() { static const auto lock_timeout = std::chrono::milliseconds(100); @@ -137,37 +140,18 @@ void base_connection_pool::watch_pool_routine() // pool_current_size may be bigger if someone returned previous session std::size_t vacant_places = (pool_max_size_ > pool_current_size) ? pool_max_size_ - pool_current_size : 0; - // creating new sessions may be slow and we want to add them simultaneously; - // that's why we need to sync adding threads and lock pool - auto add_session = [this]() { - try { - // getting new session is time consuming operation - auto new_session = connector_.new_session(); - - // ensure only single session added at time - std::unique_lock pool_lk(pool_mutex_); - sesson_pool_.emplace_back(clock_type::now(), std::move(new_session)); - pool_lk.unlock(); - - // unblock one waiting thread - pool_cv_.notify_one(); - } catch (const boost::system::system_error& e) { - // TODO: log errors ? + if (vacant_places) { + auto append_func = [this](std::unique_ptr&& session) { + this->return_session(std::move(session)); + }; + const auto need_more = reconnection_.refill(connector_, vacant_places, append_func); + if (need_more) { + continue; } - }; - - std::list adders; - for (std::size_t i = 0; i < vacant_places; ++i) { - adders.emplace_back(add_session); - } - for (auto& a : adders) { - a.join(); } // stop cpu spooling if nothing has been added - if (vacant_places == 0) { - std::this_thread::sleep_for(std::chrono::milliseconds(50)); - } + std::this_thread::sleep_for(std::chrono::milliseconds(50)); } } diff --git a/include/stream-client/connector/impl/pool_strategy.ipp b/include/stream-client/connector/impl/pool_strategy.ipp new file mode 100644 index 0000000..7a6935a --- /dev/null +++ b/include/stream-client/connector/impl/pool_strategy.ipp @@ -0,0 +1,114 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +namespace stream_client { +namespace connector { + +template +const unsigned long conservative_strategy::kMaxBackoffMs = 10000; // 10 seconds maximum delay + +template +const unsigned long conservative_strategy::kDefaultDelayMs = 50; // 50 milliseconds is default initial delay + +template +const unsigned long conservative_strategy::kDefaultDelayMul = 3; // 3 is default delay multiplier + +template +bool greedy_strategy::refill(connector_type& connector, std::size_t vacant_places, + append_func_type append_func) +{ + // creating new sessions may be slow and we want to add them simultaneously + auto add_session = [&]() { + try { + auto new_session = connector.new_session(); + append_func(std::move(new_session)); + } catch (const boost::system::system_error& e) { + // TODO: log errors ? + } + }; + + std::list adders; + for (std::size_t i = 0; i < vacant_places; ++i) { + adders.emplace_back(add_session); + } + for (auto& a : adders) { + a.join(); + } + + return vacant_places > 0; +} + +template +conservative_strategy::conservative_strategy(unsigned long first_delay_ms, unsigned delay_multiplier) + : initial_delay_ms_(first_delay_ms) + , delay_multiplier_(delay_multiplier) + , current_delay_ms_(0) + , r_generator_(r_device_()) +{ + if (delay_multiplier_ < 1) { + throw std::runtime_error("delay multiplier should be >= 1"); + } +} + +template +bool conservative_strategy::refill(connector_type& connector, std::size_t vacant_places, + append_func_type append_func) +{ + if (clock_type::now() < wait_until_) { + return false; + } + + std::atomic_bool is_added{false}; + + // creating new sessions may be slow and we want to add them simultaneously + auto add_session = [&]() { + try { + auto new_session = connector.new_session(); + append_func(std::move(new_session)); + is_added = true; + } catch (const boost::system::system_error& e) { + // TODO: log errors ? + } + }; + + std::vector adders; + const size_t parallel = (vacant_places + 2) / 3 - 1; + if (!current_delay_ms_ && parallel > 0) { + adders.reserve(parallel); + for (std::size_t i = 0; i < parallel; ++i) { + adders.emplace_back(add_session); + } + } + add_session(); + for (auto& a : adders) { + a.join(); + } + + if (is_added) { + current_delay_ms_ = 0; + return true; + } + + if (!current_delay_ms_) { + current_delay_ms_ = initial_delay_ms_; + } else { + current_delay_ms_ *= delay_multiplier_; + } + const auto rand_val = double(r_generator_()) / r_generator_.max(); + current_delay_ms_ *= rand_val; + current_delay_ms_ = std::min(kMaxBackoffMs, current_delay_ms_); + wait_until_ = clock_type::now() + std::chrono::milliseconds(current_delay_ms_); + + return false; +} + +} // namespace connector +} // namespace stream_client diff --git a/include/stream-client/connector/pool_strategy.hpp b/include/stream-client/connector/pool_strategy.hpp new file mode 100644 index 0000000..3d00706 --- /dev/null +++ b/include/stream-client/connector/pool_strategy.hpp @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include + +namespace stream_client { +namespace connector { + +/** + * Interface for pool reconnection strategy. Used by connection_pool to refill itself. + */ +template +class pool_strategy +{ +public: + using connector_type = typename std::remove_reference::type; + using stream_type = typename connector_type::stream_type; + using append_func_type = typename std::function&& session)>; + + /// Destructor. + virtual ~pool_strategy() = default; + + /** + * Creates new sessions via @p connector and add them to the pool up to @p vacant_places times. + * + * @param connector Connector to use for new sessions. + * @param vacant_places Number of required connection to fulfill the pool. + * @param append_func This function is used to add new one connected session to pool. + * + * @returns true if all session have been successfully added. + */ + virtual bool refill(connector_type& connector, std::size_t vacant_places, append_func_type append_func) = 0; +}; + +/** + * Greedy strategy. Will refill pool completely using multiple threads. + */ +template +class greedy_strategy: public pool_strategy +{ +public: + using connector_type = typename pool_strategy::connector_type; + using stream_type = typename pool_strategy::stream_type; + using append_func_type = typename pool_strategy::append_func_type; + + greedy_strategy() = default; + + /// Destructor. + virtual ~greedy_strategy() = default; + + /** + * Adds up to @p vacant_places sessions via @p connector simultaneously. + * + * @param connector Connector to use for new sessions. + * @param vacant_places Number of required connection to fulfill the pool. + * @param append_func Function is used to add new session to the pool. + * + * @returns true if @p vacant_places > 0. + */ + virtual bool refill(connector_type& connector, std::size_t vacant_places, append_func_type append_func) override; +}; + +/** + * Conservative strategy. Will try to refill up to 2/3 of vacant places in the pool. + * If failed will back off with delay and try to add only one new session. + */ +template +class conservative_strategy: public pool_strategy +{ +public: + using connector_type = typename pool_strategy::connector_type; + using stream_type = typename pool_strategy::stream_type; + using append_func_type = typename pool_strategy::append_func_type; + + using clock_type = typename connector_type::clock_type; + using time_duration_type = typename connector_type::time_duration_type; + using time_point_type = typename connector_type::time_point_type; + + /// Maximum allowed delay is ms. + static const unsigned long kMaxBackoffMs; + static const unsigned long kDefaultDelayMs; + static const unsigned long kDefaultDelayMul; + + /** + * Creates conservative strategy with specified delay and multiplier. + * Strategy allows to add new sessions with increasing delays upon failures. + * + * @param first_delay_ms Initial delay to use. + * @param delay_multiplier Multiply delay by this number each time we fail. Should be >= 1 + */ + conservative_strategy(unsigned long first_delay_ms = kDefaultDelayMs, unsigned delay_multiplier = kDefaultDelayMul); + + /// Destructor. + virtual ~conservative_strategy() = default; + + /** + * Adds up to 2/3 of @p vacant_places sessions via @p connector simultaneously. + * On failures will set a delay and until it is passed will do nothing. + * Also, will add only one session if previously failed. + * + * Delay initially set to `first_delay_ms` and multiplied by random number in [1..`delay_multiplier`) + * interval on each fail. + * + * @param connector Connector to use for new sessions. + * @param vacant_places Number of required connection to fulfill the pool. + * @param append_func Function is used to add new session to the pool. + * + * @returns true if added at least one session; false if failed or wait time is not reached. + */ + virtual bool refill(connector_type& connector, std::size_t vacant_places, append_func_type append_func) override; + +private: + time_point_type wait_until_; + unsigned long initial_delay_ms_; + unsigned long delay_multiplier_; + unsigned long current_delay_ms_; + + std::random_device r_device_; + std::mt19937 r_generator_; +}; + +} // namespace connector +} // namespace stream_client + +#include "impl/pool_strategy.ipp" diff --git a/lint.py b/lint.py index f0baaed..dceb7a9 100755 --- a/lint.py +++ b/lint.py @@ -31,7 +31,7 @@ except ImportError: DEVNULL = open(os.devnull, "wb") -DEFAULT_EXTENSIONS = 'cc,cpp,cxx,c++,h,hh,hpp,hxx,h++' +DEFAULT_EXTENSIONS = 'cc,cpp,cxx,c++,h,hh,hpp,hxx,h++,ipp,i' DEFAULT_LINT_IGNORE = '.clang-lint-ignore' # Use of utf-8 to decode the process output. @@ -309,6 +309,13 @@ def main(): default=[], help='exclude paths matching the given glob-like pattern(s)' ' from recursive search') + parser.add_argument('-et', + '--exclude-tidy', + metavar='PATTERN', + action='append', + default=[], + help='exclude paths matching the given glob-like pattern(s)' + ' from clang-tidy analysis') parser.add_argument('--style', help='formatting style to apply (LLVM, Google, Chromium, Mozilla, WebKit)') parser.add_argument('-p', '--build-path', help='build path', default='./build') @@ -353,16 +360,25 @@ def main(): excludes = excludes_from_file(DEFAULT_LINT_IGNORE) excludes.extend(args.exclude) - - files = list_files(args.files, recursive=args.recursive, exclude=excludes, extensions=args.extensions.split(',')) - - if not files: + clang_format_files = list_files(args.files, + recursive=args.recursive, + exclude=excludes, + extensions=args.extensions.split(',')) + + clang_tidy_excludes = excludes + clang_tidy_excludes.extend(args.exclude_tidy) + clang_tidy_files = list_files(args.files, + recursive=args.recursive, + exclude=clang_tidy_excludes, + extensions=args.extensions.split(',')) + + if not clang_format_files and not clang_tidy_files: return # execute directly instead of in a pool, # less overhead, simpler stacktraces - it = itertools.chain((wrap_exceptions(run_clang_format_diff, args, file) for file in files), - (wrap_exceptions(run_clang_tidy, args, file) for file in files)) + it = itertools.chain((wrap_exceptions(run_clang_format_diff, args, file) for file in clang_format_files), + (wrap_exceptions(run_clang_tidy, args, file) for file in clang_tidy_files)) while True: try: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c460e77..898f745 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,30 +8,38 @@ else() set(TETING_TARGET_LOCAL "False") endif() -# Download and unpack googletest at configure time -configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt) -execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . - RESULT_VARIABLE result - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) -if(result) - message(FATAL_ERROR "CMake step for googletest failed: ${result}") -endif() -execute_process(COMMAND ${CMAKE_COMMAND} --build . - RESULT_VARIABLE result - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) -if(result) - message(FATAL_ERROR "Build step for googletest failed: ${result}") -endif() +# Locate GTest +find_package(GTest) +if(GTest_FOUND) + set(EXTRA_LIBS ${GTEST_LIBRARIES}) +else(GTest_FOUND) + # Download and unpack googletest at configure time + configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt) + execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) + if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") + endif() + execute_process(COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) + if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") + endif() + + # Prevent overriding the parent project's compiler/linker + # settings on Windows + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) -# Prevent overriding the parent project's compiler/linker -# settings on Windows -set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + # Add googletest directly to our build. This defines + # the gtest and gtest_main targets. + add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src + ${CMAKE_CURRENT_BINARY_DIR}/googletest-build + EXCLUDE_FROM_ALL) -# Add googletest directly to our build. This defines -# the gtest and gtest_main targets. -add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src - ${CMAKE_CURRENT_BINARY_DIR}/googletest-build - EXCLUDE_FROM_ALL) + set(EXTRA_LIBS gtest_main) +endif(GTest_FOUND) set(SSL_CERTS_FOLDER ${CMAKE_CURRENT_BINARY_DIR}) set(SSL_ROOT_KEY ssl_rootca.key) @@ -75,7 +83,7 @@ add_definitions(-DSSL_DH_PARAMS="${SSL_CERTS_FOLDER}/${SSL_DH_PARAMS}") function(add_unit_test name) add_executable(${name} ${ARGN}) - target_link_libraries(${name} ${PROJECT_NAME}::${PROJECT_NAME} gtest_main) + target_link_libraries(${name} ${PROJECT_NAME}::${PROJECT_NAME} ${EXTRA_LIBS}) add_test(NAME ${name} COMMAND $) if (TETING_TARGET_LOCAL) add_dependencies(testing ${name}) diff --git a/tests/fixtures.hpp b/tests/fixtures.hpp index 6cfdd2a..ea22a26 100644 --- a/tests/fixtures.hpp +++ b/tests/fixtures.hpp @@ -13,6 +13,7 @@ class ServerEnv: public ::testing::Test using server_type = Server; using session_type = typename server_type::session_type; using client_type = typename server_type::client_type; + using connector_type = typename server_type::connector_type; using client_pool_type = typename server_type::client_pool_type; using endpoint_type = typename server_type::endpoint_type; using protocol_type = typename server_type::protocol_type; @@ -86,7 +87,24 @@ template using TCPUDPConnectedEnv = ConnectedEnv; // test suite for TCP and UDP template -using PoolServerEnv = ServerEnv; // test suite for TCP and UDP +class GreedyPoolServerEnv: public ServerEnv +{ +public: + using connector_type = typename Server::connector_type; + using client_pool_type = + stream_client::connector::base_connection_pool>; +}; + +template +class ConservativePoolServerEnv: public ServerEnv +{ +public: + using connector_type = typename Server::connector_type; + using client_pool_type = + stream_client::connector::base_connection_pool>; +}; using TCPUDPServerTypes = ::testing::Types<::utils::tcp_server<1>, ::utils::udp_server>; using AllServerTypes = ::testing::Types<::utils::tcp_server<1>, ::utils::udp_server, ::utils::ssl_server<1>>; @@ -94,7 +112,8 @@ using PoolServerTypes = ::testing::Types<::utils::tcp_server, ::utils::udp_server, ::utils::ssl_server>; TYPED_TEST_SUITE(ServerEnv, AllServerTypes); -TYPED_TEST_SUITE(PoolServerEnv, PoolServerTypes); +TYPED_TEST_SUITE(ConservativePoolServerEnv, PoolServerTypes); +TYPED_TEST_SUITE(GreedyPoolServerEnv, PoolServerTypes); TYPED_TEST_SUITE(ConnectedEnv, AllServerTypes); TYPED_TEST_SUITE(TCPUDPConnectedEnv, TCPUDPServerTypes); diff --git a/tests/pool.cpp b/tests/pool.cpp index 6ce1159..c3ebf25 100644 --- a/tests/pool.cpp +++ b/tests/pool.cpp @@ -3,22 +3,19 @@ #include -TYPED_TEST(PoolServerEnv, PoolConnect) +template +void start_pool_test(ServerEnv& env) { - using server_session_type = typename TestFixture::session_type; - using client_pool_type = typename TestFixture::client_pool_type; - using protocol_type = typename TestFixture::protocol_type; - using client_type = typename TestFixture::client_type; - const size_t pool_size = 10; std::vector> future_sessions; for (size_t i = 0; i < pool_size; ++i) { - future_sessions.emplace_back(this->server.get_session()); + future_sessions.emplace_back(env.server.get_session()); } std::unique_ptr clients_pool; ASSERT_NO_THROW({ - clients_pool = std::make_unique(pool_size, this->host, std::to_string(this->port), + clients_pool = std::make_unique(pool_size, env.host, std::to_string(env.port), std::chrono::seconds(1), std::chrono::seconds(1), std::chrono::seconds(1)); }); @@ -53,6 +50,24 @@ TYPED_TEST(PoolServerEnv, PoolConnect) } } +TYPED_TEST(GreedyPoolServerEnv, PoolConnect) +{ + using server_session_type = typename TestFixture::session_type; + using client_pool_type = typename TestFixture::client_pool_type; + using protocol_type = typename TestFixture::protocol_type; + using client_type = typename TestFixture::client_type; + start_pool_test(*this); +} + +TYPED_TEST(ConservativePoolServerEnv, PoolConnect) +{ + using server_session_type = typename TestFixture::session_type; + using client_pool_type = typename TestFixture::client_pool_type; + using protocol_type = typename TestFixture::protocol_type; + using client_type = typename TestFixture::client_type; + start_pool_test(*this); +} + int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/tests/utils/echo_server.hpp b/tests/utils/echo_server.hpp index c99ddf7..223b186 100644 --- a/tests/utils/echo_server.hpp +++ b/tests/utils/echo_server.hpp @@ -373,6 +373,7 @@ class tcp_server: public tcp_base_server { public: using client_type = ::stream_client::tcp_client; + using connector_type = ::stream_client::connector::tcp_connector; using client_pool_type = ::stream_client::connector::tcp_pool; using tcp_base_server::tcp_base_server; @@ -383,6 +384,7 @@ class http_server: public tcp_base_server { public: using client_type = ::stream_client::http::http_client; + using connector_type = ::stream_client::connector::http_connector; using client_pool_type = ::stream_client::connector::http_pool; using tcp_base_server::tcp_base_server; @@ -393,6 +395,7 @@ class ssl_server: public echo_server { public: using client_type = ::stream_client::ssl::ssl_client; + using connector_type = utils::ssl_connector; using client_pool_type = ::stream_client::connector::base_connection_pool; ssl_server(const endpoint_type& endpoint) @@ -451,6 +454,7 @@ class udp_server: public echo_server { public: using client_type = ::stream_client::udp_client; + using connector_type = ::stream_client::connector::udp_connector; using client_pool_type = ::stream_client::connector::udp_pool; udp_server(const endpoint_type& endpoint)