C++ beast:多个异步写入调用触发断言错误
我正在为我的全双工服务器编写测试,当我执行多个(顺序)的C++ beast:多个异步写入调用触发断言错误,c++,boost,websocket,boost-asio,boost-beast,C++,Boost,Websocket,Boost Asio,Boost Beast,我正在为我的全双工服务器编写测试,当我执行多个(顺序)的async_write调用时(尽管包含一个串),我从文件boost/beast/websocket/detail/stream_base.hpp中的boost::beast中得到以下断言错误: // If this assert goes off it means you are attempting to // simultaneously initiate more than one of same asynchronous // op
async_write
调用时(尽管包含一个串),我从文件boost/beast/websocket/detail/stream_base.hpp
中的boost::beast
中得到以下断言错误:
// If this assert goes off it means you are attempting to
// simultaneously initiate more than one of same asynchronous
// operation, which is not allowed. For example, you must wait
// for an async_read to complete before performing another
// async_read.
//
BOOST_ASSERT(id_ != T::id);
要在您的计算机上重现问题:可以找到重现此问题(MCVE)的完整客户端代码。它在链接中不起作用,因为您需要一个服务器(在您自己的机器上,很抱歉,因为无法方便地在线执行此操作,客观地说,最好表明问题出在客户端,而不是服务器(如果我将其包括在此处)。我曾经使用命令/websocketd--ssl--sslkey/path/to/server.key--sslcert/path/to/server.crt--port=8085./prog.py
创建一个服务器,其中/prog.py
是一个简单的python程序,可以打印和刷新(我从中获得)
在客户端执行写入操作的调用如下所示:
std::vector<std::vector<std::future<void>>> clients_write_futures(
clients_count);
for (int i = 0; i < clients_count; i++) {
clients_write_futures[i] = std::vector<std::future<void>>(num_of_messages);
for (int j = 0; j < num_of_messages; j++) {
clients_write_futures[i][j] =
clients[i]->write_data_async_future("Hello"); // writing here
}
}
test.cpp | 683 +++++++++++++++++++++++----------------------------------------
1 file changed, 249 insertions(+), 434 deletions(-)
std::向量客户端\u写入\u未来(
客户数量);
对于(int i=0;i写入数据异步未来(“Hello”);//在此处写入
}
}
注意,在这个示例中,我只使用了一个客户机。客户机阵列只是测试时对服务器施加更多压力的一种概括
我对这个问题的看法是:
编辑: Sehe,我想首先为代码混乱道歉(我没意识到这有那么糟糕),并感谢你在这方面的努力。我希望你能问我为什么它的结构是这样(可能)有组织的,同时也是混乱的,答案很简单:主要是一个gtest代码,看看我的通用、多功能websocket客户端是否工作,我正在使用它对我的服务器进行压力测试(它使用了大量的多线程IoService对象,我认为它是敏感的,需要广泛的测试)。我计划在实际生产测试期间同时与许多客户端一起轰炸我的服务器。我发布这个问题是因为客户端的行为我不理解。(人们总是这样要求)。我花了两个小时剥离代码来创建它,最终我复制了我的gtest测试夹具代码(这是服务器上的一个夹具),并将其粘贴到主服务器上,验证问题仍然存在于另一台服务器上,并进行了一些清理(这显然是不够的) 那么为什么我不捕获异常呢?因为gtest会捕获异常并认为测试失败。主要不是生产代码,而是客户机。我从您提到的内容中学到了很多,我不得不说抛出并捕获异常是愚蠢的,但我不知道std::make_exception_ptr(),所以我找到了(dumm)方法来实现相同的结果:-)。为什么会有太多无用的函数:在这个测试/示例中,它们是无用的,但通常我可以在以后的其他事情中使用它们,因为这个客户机不仅仅适用于这种情况
现在回到问题上来:我不明白的是,当它在主线程的循环中顺序使用时,为什么我们必须使用strand来覆盖
async\u write
。我理解为什么会覆盖处理程序,因为套接字不是线程安全的,而多线程的io\u服务将在那里创建竞争。我们还知道io\u服务::post
本身是线程安全的(这就是为什么我认为不必包装异步写入的原因)。请您解释一下,在执行此操作时,我们需要包装async_write本身,这不是线程安全的吗?我知道您已经知道这一点,但仍然会触发相同的断言。我们将处理程序和异步队列顺序化,客户端仍然不乐意进行多个写调用。还可能缺少什么
(顺便说一句,如果你写,然后得到未来,然后读,然后再写,它会起作用。这就是为什么我使用未来来准确定义测试用例和定义测试的时间顺序。我在这里有点偏执。)您说您用一个串覆盖了异步写入
。但是您没有这样做。您所做的只是在该串中包装完成处理程序。但是您直接发布了异步操作
更糟糕的是,您正在从主线程执行此操作,而与WSClient
实例关联的任何线程上都在进行异步操作,这意味着您正在并发访问非线程安全的对象实例
这是一场数据竞赛,所以你得到了
一个简单的解决方案可能是:
std::future<void> write_data_async_future(const std::string &data) {
// shared_ptr is used to ensure data's survival
std::shared_ptr<std::string> data_ptr = std::make_shared<std::string>(data);
std::shared_ptr<std::promise<void> > write_promise = std::make_shared<std::promise<void> >();
post(strand_, [=,self=shared_from_this()] {
websock.async_write(
boost::asio::buffer(*data_ptr),
boost::asio::bind_executor(strand_, std::bind(&WSClientSession::on_write_future, self,
std::placeholders::_1, std::placeholders::_2, data_ptr,
write_promise)));
});
return write_promise->get_future();
}
所有数据结构都已折叠到小助手actor
:
struct actor {
std::shared_ptr<WSClient> client;
std::future<void> session_start_future;
struct message {
std::string message = GenerateRandomString(20);
std::future<void> write_future;
std::future<std::string> read_future;
};
std::vector<message> messages;
};
看起来…很好。等等,在未来的书写中有?这可能意味着我们需要蒸发更多未使用的代码行。看起来…是的。噗,不见了
到目前为止,diffstat如下所示:
std::vector<std::vector<std::future<void>>> clients_write_futures(
clients_count);
for (int i = 0; i < clients_count; i++) {
clients_write_futures[i] = std::vector<std::future<void>>(num_of_messages);
for (int j = 0; j < num_of_messages; j++) {
clients_write_futures[i][j] =
clients[i]->write_data_async_future("Hello"); // writing here
}
}
test.cpp | 683 +++++++++++++++++++++++----------------------------------------
1 file changed, 249 insertions(+), 434 deletions(-)
回到那个函数,让我们看看关于未来的:
std::future<void> write_data_async_future(const std::string &data) {
// shared_ptr is used to ensure data's survival
std::shared_ptr<std::string> data_ptr = std::make_shared<std::string>(data);
std::shared_ptr<std::promise<void> > write_promise = std::make_shared<std::promise<void> >();
websock.async_write(
boost::asio::buffer(*data_ptr),
boost::asio::bind_executor(strand_, std::bind(&WSClientSession::on_write_future, shared_from_this(),
std::placeholders::_1, std::placeholders::_2, data_ptr,
write_promise)));
return write_promise->get_future();
}
void on_write_future(boost::system::error_code ec, std::size_t bytes_transferred,
std::shared_ptr<std::string> data_posted,
std::shared_ptr<std::promise<void> > write_promise) {
boost::ignore_unused(bytes_transferred);
boost::ignore_unused(data_posted);
if (ec) {
try {
throw std::runtime_error("Error thrown while performing async write: " + ec.message());
} catch (...) {
write_promise->set_exception(std::current_exception());
}
return;
}
write_promise->set_value();
}
即便如此,现在还是有一个概念上的问题。