diff --git a/src/util/CoroutineGroup.cpp b/src/util/CoroutineGroup.cpp index 271486078..d8aafd0a3 100644 --- a/src/util/CoroutineGroup.cpp +++ b/src/util/CoroutineGroup.cpp @@ -41,10 +41,16 @@ CoroutineGroup::~CoroutineGroup() ASSERT(childrenCounter_ == 0, "CoroutineGroup is destroyed without waiting for child coroutines to finish"); } +bool +CoroutineGroup::canSpawn() const +{ + return not maxChildren_.has_value() or childrenCounter_ < *maxChildren_; +} + bool CoroutineGroup::spawn(boost::asio::yield_context yield, std::function fn) { - if (maxChildren_.has_value() && childrenCounter_ >= *maxChildren_) + if (not canSpawn()) return false; ++childrenCounter_; diff --git a/src/util/CoroutineGroup.hpp b/src/util/CoroutineGroup.hpp index 7fc5aa077..0654d1991 100644 --- a/src/util/CoroutineGroup.hpp +++ b/src/util/CoroutineGroup.hpp @@ -54,6 +54,14 @@ class CoroutineGroup { */ ~CoroutineGroup(); + /** + * @brief Check if a new coroutine can be spawned (i.e. there is space for a new coroutine in the group) + * + * @return true If a new coroutine can be spawned. false if the maximum number of coroutines has been reached + */ + bool + canSpawn() const; + /** * @brief Spawn a new coroutine in the group * diff --git a/src/web/ng/impl/ConnectionHandler.cpp b/src/web/ng/impl/ConnectionHandler.cpp index c321874c4..4a92e6a4a 100644 --- a/src/web/ng/impl/ConnectionHandler.cpp +++ b/src/web/ng/impl/ConnectionHandler.cpp @@ -218,21 +218,21 @@ ConnectionHandler::parallelRequestResponseLoop(Connection& connection, boost::as closeConnectionGracefully &= closeGracefully; break; } - - bool const spawnSuccess = tasksGroup.spawn( - yield, // spawn on the same strand - [this, &stop, &closeConnectionGracefully, &connection, request = std::move(expectedRequest).value()]( - boost::asio::yield_context innerYield - ) mutable { - auto maybeCloseConnectionGracefully = processRequest(connection, request, innerYield); - if (maybeCloseConnectionGracefully.has_value()) { - stop = true; - closeConnectionGracefully &= maybeCloseConnectionGracefully.value(); + if (tasksGroup.canSpawn()) { + bool const spawnSuccess = tasksGroup.spawn( + yield, // spawn on the same strand + [this, &stop, &closeConnectionGracefully, &connection, request = std::move(expectedRequest).value()]( + boost::asio::yield_context innerYield + ) mutable { + auto maybeCloseConnectionGracefully = processRequest(connection, request, innerYield); + if (maybeCloseConnectionGracefully.has_value()) { + stop = true; + closeConnectionGracefully &= maybeCloseConnectionGracefully.value(); + } } - } - ); - - if (not spawnSuccess) { + ); + ASSERT(spawnSuccess, "The coroutine was expected to be spawned"); + } else { connection.send( Response{ boost::beast::http::status::too_many_requests, diff --git a/tests/common/util/TestHttpClient.cpp b/tests/common/util/TestHttpClient.cpp index e691d13b3..22a9f2c00 100644 --- a/tests/common/util/TestHttpClient.cpp +++ b/tests/common/util/TestHttpClient.cpp @@ -33,10 +33,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include diff --git a/tests/unit/util/CoroutineGroupTests.cpp b/tests/unit/util/CoroutineGroupTests.cpp index 4cddb10c5..88ddd9312 100644 --- a/tests/unit/util/CoroutineGroupTests.cpp +++ b/tests/unit/util/CoroutineGroupTests.cpp @@ -165,3 +165,17 @@ TEST_F(CoroutineGroupTests, TooManyCoroutines) callback3_.Call(); }); } + +TEST_F(CoroutineGroupTests, CanSpawn) +{ + EXPECT_CALL(callback1_, Call); + + runSpawn([this](boost::asio::yield_context yield) { + CoroutineGroup group{yield, 1}; + EXPECT_TRUE(group.canSpawn()); + group.spawn(yield, [&group, this](boost::asio::yield_context) { + callback1_.Call(); + EXPECT_FALSE(group.canSpawn()); + }); + }); +}