如何解决io_context.run返回为时过早
我正在基于boost-beast创建一个api_client库。我正在使用composed operations将http :: async_write()和http :: async_read()合并为api_client :: async_get()。
我面临的问题是io_context.run()函数现在确实等待http :: async_read()完成并返回,然后调用async_get()回调。
我在做什么错? 还有其他建议来改进代码吗?
我在Mac上使用boost 1.74
// api_client.h
#ifndef API_CLIENT_H
#define API_CLIENT_H
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/use_future.hpp>
#include <boost/asio/write.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
struct async_resolve_initiation
{
template <typename CompletionHandler>
void operator()(CompletionHandler &&completion_handler,tcp::resolver &resolver,const std::string &host,const std::string &port) const
{
auto executor =
boost::asio::get_associated_executor(completion_handler,resolver.get_executor());
resolver.async_resolve(
host,port,boost::asio::bind_executor(executor,std::forward<CompletionHandler>(completion_handler)));
}
};
template <typename CompletionToken>
auto async_resolve_host(tcp::resolver &resolver,const std::string &port,CompletionToken &&token) ->
typename boost::asio::async_result<typename std::decay<CompletionToken>::type,void(const boost::system::error_code &,const tcp::resolver::results_type &)>::return_type
{
return boost::asio::async_initiate<CompletionToken,const tcp::resolver::results_type &)>(
async_resolve_initiation(),token,std::ref(resolver),host,port);
}
struct async_connect_initiation
{
template <typename CompletionHandler>
void operator()(CompletionHandler &&completion_handler,beast::tcp_stream &stream,const tcp::resolver::results_type &results) const
{
stream.expires_after(std::chrono::seconds(10));
auto executor = boost::asio::get_associated_executor(completion_handler,stream.get_executor());
stream.async_connect(results,std::forward<CompletionHandler>(completion_handler));
}
};
template <typename CompletionToken>
auto async_connect_host(beast::tcp_stream &stream,const tcp::resolver::results_type &results,CompletionToken &&token) ->
typename boost::asio::async_result<
typename std::decay<CompletionToken>::type,const tcp::resolver::results_type::endpoint_type &)>::return_type
{
return boost::asio::async_initiate<CompletionToken,const tcp::resolver::results_type::endpoint_type &)>(
async_connect_initiation(),std::ref(stream),results);
}
struct async_request
{
beast::tcp_stream & stream_;
std::unique_ptr<http::request<http::empty_body>> req_;
enum
{
request_in_progress,waiting_for_response,} state_;
beast::flat_buffer buffer_;
std::shared_ptr<http::response<http::string_body>> res_;
async_request(beast::tcp_stream &stream,std::unique_ptr<http::request<http::empty_body>> req)
: stream_(stream),req_(std::move(req)),state_(request_in_progress)
{
res_ = std::make_shared<http::response<http::string_body>>();
}
template <typename Self>
void operator()(Self &self)
{
stream_.expires_after(std::chrono::seconds(30));
http::async_write(stream_,*req_,std::move(self));
}
template <typename Self>
void operator()(Self &self,const boost::system::error_code &error,const std::size_t bytes_transferred)
{
if (!error)
{
switch (state_)
{
case request_in_progress:
http::async_read(stream_,buffer_,*res_,std::move(self));
state_ = waiting_for_response;
break;
case waiting_for_response:
self.complete(error);
break;
}
}
else
{
self.complete(error);
}
}
};
struct api_client
{
net::io_context & io_;
beast::tcp_stream stream_;
tcp::resolver resolver_;
const std::string base_url_;
const std::string port_;
api_client(net::io_context &io,const std::string &base_url,const std::string &port = "80")
: io_(io),stream_(net::make_strand(io)),resolver_(net::make_strand(io)),base_url_(base_url),port_(port)
{}
template <typename CompletionToken>
auto async_get(const std::string &path,CompletionToken &&token) ->
typename boost::asio::async_result<typename std::decay<CompletionToken>::type,void(const boost::system::error_code &)>::return_type
{
std::unique_ptr<http::request<http::empty_body>> req =
std::make_unique<http::request<http::empty_body>>();
req->version(11);
req->method(http::verb::get);
req->target(path);
req->set(http::field::host,base_url_);
return boost::asio::async_compose<CompletionToken,void(const boost::system::error_code &)>(
async_request(std::ref(stream_),std::move(req)),token);
}
template <typename CompletionToken>
auto async_resolve_host(CompletionToken &&token) ->
typename boost::asio::async_result<typename std::decay<CompletionToken>::type,const tcp::resolver::results_type &)>::return_type
{
return boost::asio::async_initiate<CompletionToken,const tcp::resolver::results_type &)>(
async_resolve_initiation(),std::ref(resolver_),base_url_,port_);
}
template <typename CompletionToken>
auto async_connect_host(const tcp::resolver::results_type &results,CompletionToken &&token) ->
typename boost::asio::async_result<
typename std::decay<CompletionToken>::type,const tcp::resolver::results_type::endpoint_type &)>::return_type
{
return boost::asio::async_initiate<CompletionToken,const tcp::resolver::results_type::endpoint_type &)>(
async_connect_initiation(),std::ref(stream_),results);
}
};
#endif
/// main.cpp
#include "api_client.h"
#include <iostream>
int main(void)
{
net::io_context io_context;
api_client client(io_context,"www.google.com");
client.async_resolve_host(
[&client](const boost::system::error_code &ec,const tcp::resolver::results_type &results) {
if (!ec)
{
client.async_connect_host(
results,[&client](const boost::system::error_code & ec,const tcp::resolver::results_type::endpoint_type &endpoint) {
if (!ec)
{
std::cout << "connected at " << endpoint << std::endl;
client.async_get("/",[](const boost::system::error_code &ec) {
std::cout << "async_get: " << ec.message() << "\n";
});
}
});
}
});
////////////////////////////////////////////////////////
io_context.run(); // PROBLEM: this function returns before callback of async_get() is called
////////////////////////////////////////////////////////
return EXIT_SUCCESS;
}
# CMakeLists.txt
project(main)
add_executable(${PROJECT_NAME} main.cpp)
find_package(Boost required COMPONENTS system)
target_link_libraries(${PROJECT_NAME} Boost::system)
解决方法
在我看来,它确实可以运行,但是崩溃了。具体来说,它会在我的系统上以调试版本进行打印:
connected at 216.58.214.4:80
async_get: buffer overflow
Segmentation fault (core dumped)
UB的来源
问题似乎是您的async_request
操作是一种值类型并且已被移动(很多次)。
通过手动“仪器”进行观察:
~async_request() { std::clog << __PRETTY_FUNCTION__ << std::endl; } async_request(async_request&&) = default;
大多数成员都启用了移动功能,但是flat_buffer
根本不在原来的地址上。在启用ASAN的情况下运行时也可以确认这一点:
connected at 216.58.208.100:80
=================================================================
==14000==ERROR: AddressSanitizer: heap-use-after-free on address 0x615000000300 at pc 0x7fdc5aff11f9 bp 0x7fff92b42110 sp 0x7fff92b418b8
WRITE of size 512 at 0x615000000300 thread T0
#0 0x7fdc5aff11f8 (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x3b1f8)
#1 0x7fdc5b02a5f4 (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x745f4)
#2 0x7fdc5b02acf3 in __interceptor_recvmsg (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x74cf3)
#3 0x55d8dc9fcba4 in boost::asio::detail::socket_ops::recv(int,iovec*,unsigned long,int,boost::system::error_code&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/socket_ops.ipp:773
#4 0x55d8dc9fcf73 in boost::asio::detail::socket_ops::non_blocking_recv(int,bool,boost::system::error_code&,unsigned long&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/socket_ops.ipp:940
#5 0x55d8dcbd2137 in boost::asio::detail::reactive_socket_recv_op_base<boost::beast::buffers_prefix_view<boost::asio::mutable_buffer> >::do_perform(boost::asio::detail::reactor_op*) /home/sehe/custom/boost_1_74_0/boost/asio/detail/reactive_socket_recv_op.hpp:72
#6 0x55d8dc9dd461 in boost::asio::detail::reactor_op::perform() /home/sehe/custom/boost_1_74_0/boost/asio/detail/reactor_op.hpp:44
#7 0x55d8dc9ee468 in boost::asio::detail::epoll_reactor::descriptor_state::perform_io(unsigned int) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/epoll_reactor.ipp:743
#8 0x55d8dc9ee829 in boost::asio::detail::epoll_reactor::descriptor_state::do_complete(void*,boost::asio::detail::scheduler_operation*,boost::system::error_code const&,unsigned long) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/epoll_reactor.ipp:774
#9 0x55d8dc9d711e in boost::asio::detail::scheduler_operation::complete(void*,unsigned long) /home/sehe/custom/boost_1_74_0/boost/asio/detail/scheduler_operation.hpp:40
#10 0x55d8dc9f748d in boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_mutex::scoped_lock&,boost::asio::detail::scheduler_thread_info&,boost::system::error_code const&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/scheduler.ipp:481
#11 0x55d8dc9f46a4 in boost::asio::detail::scheduler::run(boost::system::error_code&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/scheduler.ipp:204
#12 0x55d8dc9fa2e1 in boost::asio::io_context::run() /home/sehe/custom/boost_1_74_0/boost/asio/impl/io_context.ipp:63
#13 0x55d8dc9024ff in main /home/sehe/Projects/stackoverflow/test.cpp:232
#14 0x7fdc59871b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
#15 0x55d8dc901929 in _start (/home/sehe/Projects/stackoverflow/sotest+0x49e929)
0x615000000300 is located 0 bytes inside of 512-byte region [0x615000000300,0x615000000500)
freed by thread T0 here:
==14000==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cpp:177 "((res.trace)) != (0)" (0x0,0x0)
#0 0x7fdc5b06dba4 (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb7ba4)
#1 0x7fdc5b08d1da (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xd71da)
#2 0x7fdc5afe2adc (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x2cadc)
#3 0x7fdc5afe483a (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x2e83a)
#4 0x7fdc5afe7edb (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x31edb)
#5 0x7fdc5b06d99b (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb799b)
#6 0x7fdc5b06d255 (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb7255)
#7 0x7fdc5aff1221 (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x3b221)
#8 0x7fdc5b02a5f4 (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x745f4)
#9 0x7fdc5b02acf3 in __interceptor_recvmsg (/usr/lib/x86_64-linux-gnu/libasan.so.6+0x74cf3)
#10 0x55d8dc9fcba4 in boost::asio::detail::socket_ops::recv(int,boost::system::error_code&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/socket_ops.ipp:773
#11 0x55d8dc9fcf73 in boost::asio::detail::socket_ops::non_blocking_recv(int,unsigned long&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/socket_ops.ipp:940
#12 0x55d8dcbd2137 in boost::asio::detail::reactive_socket_recv_op_base<boost::beast::buffers_prefix_view<boost::asio::mutable_buffer> >::do_perform(boost::asio::detail::reactor_op*) /home/sehe/custom/boost_1_74_0/boost/asio/detail/reactive_socket_recv_op.hpp:72
#13 0x55d8dc9dd461 in boost::asio::detail::reactor_op::perform() /home/sehe/custom/boost_1_74_0/boost/asio/detail/reactor_op.hpp:44
#14 0x55d8dc9ee468 in boost::asio::detail::epoll_reactor::descriptor_state::perform_io(unsigned int) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/epoll_reactor.ipp:743
#15 0x55d8dc9ee829 in boost::asio::detail::epoll_reactor::descriptor_state::do_complete(void*,unsigned long) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/epoll_reactor.ipp:774
#16 0x55d8dc9d711e in boost::asio::detail::scheduler_operation::complete(void*,unsigned long) /home/sehe/custom/boost_1_74_0/boost/asio/detail/scheduler_operation.hpp:40
#17 0x55d8dc9f748d in boost::asio::detail::scheduler::do_run_one(boost::asio::detail::conditionally_enabled_mutex::scoped_lock&,boost::system::error_code const&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/scheduler.ipp:481
#18 0x55d8dc9f46a4 in boost::asio::detail::scheduler::run(boost::system::error_code&) /home/sehe/custom/boost_1_74_0/boost/asio/detail/impl/scheduler.ipp:204
#19 0x55d8dc9fa2e1 in boost::asio::io_context::run() /home/sehe/custom/boost_1_74_0/boost/asio/impl/io_context.ipp:63
#20 0x55d8dc9024ff in main /home/sehe/Projects/stackoverflow/test.cpp:232
#21 0x7fdc59871b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
#22 0x55d8dc901929 in _start (/home/sehe/Projects/stackoverflow/sotest+0x49e929)
解决该问题的“便宜”方法是添加另一个智能指针。但是:
- 这会添加更多的动态分配/实现碎片
-
enable_shared_from_this
似乎变得更加愚蠢 - IIRC Beast为此提供了一个抽象(寻找
handler_ptr
还是类似的东西?)。我的转到资源是
另一个微妙的问题
解决以上问题后,还有另一个问题潜伏:
http::async_read(stream_,buffer_,*res_,std::move(self));
state_ = waiting_for_response;
您在移动后更新state_
。那就是移动后使用。这里不是UB,因为它是原始类型成员。但这也不是您想要的。重新排序:
state_ = waiting_for_response;
http::async_read(stream_,*buffer_,std::move(self));
最低固定版本:
在Coliru上直播
// api_client.h
#ifndef API_CLIENT_H
#define API_CLIENT_H
#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/http.hpp>
#include <functional>
#include <iostream>
#include <memory>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
struct async_resolve_initiation
{
template <typename CompletionHandler>
void operator()(CompletionHandler &&completion_handler,tcp::resolver &resolver,const std::string &host,const std::string &port) const
{
auto executor =
boost::asio::get_associated_executor(completion_handler,resolver.get_executor());
resolver.async_resolve(
host,port,boost::asio::bind_executor(executor,std::forward<CompletionHandler>(completion_handler)));
}
};
template <typename CompletionToken>
auto async_resolve_host(tcp::resolver &resolver,const std::string &port,CompletionToken &&token) ->
typename boost::asio::async_result<typename std::decay<CompletionToken>::type,void(const boost::system::error_code &,const tcp::resolver::results_type &)>::return_type
{
return boost::asio::async_initiate<CompletionToken,const tcp::resolver::results_type &)>(
async_resolve_initiation(),token,std::ref(resolver),host,port);
}
struct async_connect_initiation
{
template <typename CompletionHandler>
void operator()(CompletionHandler &&completion_handler,beast::tcp_stream &stream,const tcp::resolver::results_type &results) const
{
stream.expires_after(std::chrono::seconds(10));
auto executor = boost::asio::get_associated_executor(completion_handler,stream.get_executor());
stream.async_connect(results,std::forward<CompletionHandler>(completion_handler));
}
};
template <typename CompletionToken>
auto async_connect_host(beast::tcp_stream &stream,const tcp::resolver::results_type &results,CompletionToken &&token) ->
typename boost::asio::async_result<
typename std::decay<CompletionToken>::type,const tcp::resolver::results_type::endpoint_type &)>::return_type
{
return boost::asio::async_initiate<CompletionToken,const tcp::resolver::results_type::endpoint_type &)>(
async_connect_initiation(),std::ref(stream),results);
}
struct async_request
{
beast::tcp_stream & stream_;
std::unique_ptr<http::request<http::empty_body>> req_;
enum
{
request_in_progress,waiting_for_response,} state_;
std::unique_ptr<beast::flat_buffer> buffer_;
std::shared_ptr<http::response<http::string_body>> res_;
async_request(beast::tcp_stream &stream,std::unique_ptr<http::request<http::empty_body>> req)
: stream_(stream),req_(std::move(req)),state_(request_in_progress)
{
res_ = std::make_shared<http::response<http::string_body>>();
buffer_ = std::make_unique<boost::beast::flat_buffer>();
}
template <typename Self>
void operator()(Self &self)
{
stream_.expires_after(std::chrono::seconds(30));
http::async_write(stream_,*req_,std::move(self));
}
template <typename Self>
void operator()(Self &self,const boost::system::error_code &error,const std::size_t /*bytes_transferred*/)
{
if (!error)
{
switch (state_)
{
case request_in_progress:
state_ = waiting_for_response;
http::async_read(stream_,std::move(self));
break;
case waiting_for_response:
self.complete(error);
break;
}
}
else
{
self.complete(error);
}
}
};
struct api_client
{
net::io_context & io_;
beast::tcp_stream stream_;
tcp::resolver resolver_;
const std::string base_url_;
const std::string port_;
api_client(net::io_context &io,std::string base_url,std::string port = "80")
: io_(io),stream_(net::make_strand(io)),resolver_(net::make_strand(io)),base_url_(std::move(base_url)),port_(std::move(port))
{}
template <typename CompletionToken>
auto async_get(const std::string &path,CompletionToken &&token) ->
typename boost::asio::async_result<typename std::decay<CompletionToken>::type,void(const boost::system::error_code &)>::return_type
{
std::unique_ptr<http::request<http::empty_body>> req =
std::make_unique<http::request<http::empty_body>>();
req->version(11);
req->method(http::verb::get);
req->target(path);
req->set(http::field::host,base_url_);
return boost::asio::async_compose<CompletionToken,void(const boost::system::error_code &)>(
async_request(std::ref(stream_),std::move(req)),token);
}
template <typename CompletionToken>
auto async_resolve_host(CompletionToken &&token) ->
typename boost::asio::async_result<typename std::decay<CompletionToken>::type,const tcp::resolver::results_type &)>::return_type
{
return boost::asio::async_initiate<CompletionToken,const tcp::resolver::results_type &)>(
async_resolve_initiation(),std::ref(resolver_),base_url_,port_);
}
template <typename CompletionToken>
auto async_connect_host(const tcp::resolver::results_type &results,CompletionToken &&token) ->
typename boost::asio::async_result<
typename std::decay<CompletionToken>::type,const tcp::resolver::results_type::endpoint_type &)>::return_type
{
return boost::asio::async_initiate<CompletionToken,const tcp::resolver::results_type::endpoint_type &)>(
async_connect_initiation(),std::ref(stream_),results);
}
};
#endif
/// main.cpp
//#include "api_client.h"
#include <iostream>
int main()
{
net::io_context io_context;
api_client client(io_context,"www.google.com");
client.async_resolve_host(
[&client](const boost::system::error_code &ec,const tcp::resolver::results_type &results) {
//std::cout << "resolved " << ec.message() << std::endl;
if (!ec)
{
client.async_connect_host(
results,[&client](const boost::system::error_code & ec,const tcp::resolver::results_type::endpoint_type &endpoint) {
if (!ec)
{
std::cout << "connected at " << endpoint << std::endl;
client.async_get("/",[](const boost::system::error_code &ec) {
std::cout << "async_get: " << ec.message() << "\n";
});
}
});
}
});
io_context.run();
}
打印:
connected at 216.58.208.100:80
async_get: Success
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。