mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
fix: Data race in new webserver (#1926)
There was a data race inside `CoroutineGroup` because internal timer was used from multiple threads in the methods `asyncWait()` and `onCoroutineComplete()`. Changing `registerForeign()` to spawn to the same `yield_context` fixes the problem because now the timer is accessed only from the same coroutine which has an internal strand. During debugging I also added websocket support for `request_gun` tool.
This commit is contained in:
@@ -56,13 +56,15 @@ CoroutineGroup::spawn(boost::asio::yield_context yield, std::function<void(boost
|
||||
}
|
||||
|
||||
std::optional<std::function<void()>>
|
||||
CoroutineGroup::registerForeign()
|
||||
CoroutineGroup::registerForeign(boost::asio::yield_context yield)
|
||||
{
|
||||
if (isFull())
|
||||
return std::nullopt;
|
||||
|
||||
++childrenCounter_;
|
||||
return [this]() { onCoroutineCompleted(); };
|
||||
// It is important to spawn onCoroutineCompleted() to the same coroutine as will be calling asyncWait().
|
||||
// timer_ here is not thread safe, so without spawn there could be a data race.
|
||||
return [this, yield]() { boost::asio::spawn(yield, [this](auto&&) { onCoroutineCompleted(); }); };
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -73,10 +73,11 @@ public:
|
||||
* @note A foreign coroutine is still counted as a child one, i.e. calling this method increases the size of the
|
||||
* group.
|
||||
*
|
||||
* @param yield The yield context owning the coroutine group.
|
||||
* @return A callback to call on foreign coroutine completes or std::nullopt if the group is already full.
|
||||
*/
|
||||
std::optional<std::function<void()>>
|
||||
registerForeign();
|
||||
registerForeign(boost::asio::yield_context yield);
|
||||
|
||||
/**
|
||||
* @brief Wait for all the coroutines in the group to finish
|
||||
|
||||
@@ -118,7 +118,7 @@ public:
|
||||
{
|
||||
std::optional<Response> response;
|
||||
util::CoroutineGroup coroutineGroup{yield, 1};
|
||||
auto const onTaskComplete = coroutineGroup.registerForeign();
|
||||
auto const onTaskComplete = coroutineGroup.registerForeign(yield);
|
||||
ASSERT(onTaskComplete.has_value(), "Coroutine group can't be full");
|
||||
|
||||
bool const postSuccessful = rpcEngine_->post(
|
||||
@@ -127,7 +127,7 @@ public:
|
||||
&response,
|
||||
&onTaskComplete = onTaskComplete.value(),
|
||||
&connectionMetadata,
|
||||
subscriptionContext = std::move(subscriptionContext)](boost::asio::yield_context yield) mutable {
|
||||
subscriptionContext = std::move(subscriptionContext)](boost::asio::yield_context innerYield) mutable {
|
||||
try {
|
||||
auto parsedRequest = boost::json::parse(request.message()).as_object();
|
||||
LOG(perfLog_.debug()) << connectionMetadata.tag() << "Adding to work queue";
|
||||
@@ -136,7 +136,11 @@ public:
|
||||
parsedRequest[JS(params)] = boost::json::array({boost::json::object{}});
|
||||
|
||||
response = handleRequest(
|
||||
yield, request, std::move(parsedRequest), connectionMetadata, std::move(subscriptionContext)
|
||||
innerYield,
|
||||
request,
|
||||
std::move(parsedRequest),
|
||||
connectionMetadata,
|
||||
std::move(subscriptionContext)
|
||||
);
|
||||
} catch (boost::system::system_error const& ex) {
|
||||
// system_error thrown when json parsing failed
|
||||
|
||||
@@ -294,14 +294,13 @@ ConnectionHandler::sequentRequestResponseLoop(
|
||||
|
||||
LOG(log_.trace()) << connection.tag() << "Processing sequentially";
|
||||
while (true) {
|
||||
auto expectedRequest = connection.receive(yield);
|
||||
auto const expectedRequest = connection.receive(yield);
|
||||
if (not expectedRequest)
|
||||
return handleError(expectedRequest.error(), connection);
|
||||
|
||||
LOG(log_.info()) << connection.tag() << "Received request from ip = " << connection.ip();
|
||||
|
||||
auto maybeReturnValue =
|
||||
processRequest(connection, subscriptionContext, std::move(expectedRequest).value(), yield);
|
||||
auto maybeReturnValue = processRequest(connection, subscriptionContext, expectedRequest.value(), yield);
|
||||
if (maybeReturnValue.has_value())
|
||||
return maybeReturnValue.value();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user