gpt4 book ai didi

c++ - 我可以 co_await 一个 io_context 在协程中由另一个在 Asio 中执行的操作吗?

转载 作者:行者123 更新时间:2023-12-05 03:19:11 24 4
gpt4 key购买 nike

我有一个由 Asio 用 one io_context per thread 编写的 HTTP 服务器库模型。它有一个 io_context_pool检索一个 io_context有顺序地。所以当服务器启动时,io_context #1将用于执行接受器和io_context #2将用于第一次连接和io_context #3将与第二个等等。

它还等待信号调用 io_context.stop()有序地停止服务器,它运行良好。

我想用 C++20 协程重构它。我快完成了,但我发现如果收到信号可以调用 io_context.stop() , heap used after free将由 sanitizer 报告。我不知道如何解决这个问题。

这是一个简化的例子:

constexpr int wait_second = 2;
constexpr int run_second = 3;

asio::awaitable<void> co_main(asio::io_context& other_ctx){
asio::steady_timer timer(other_ctx);
timer.expires_after(std::chrono::seconds(wait_second));
co_await timer.async_wait(asio::use_awaitable);
std::cout<<"timer"<<std::endl;
}

int main() {
asio::io_context ctx;
asio::io_context other_ctx;
asio::executor_work_guard<asio::io_context::executor_type> g1(ctx.get_executor());
asio::executor_work_guard<asio::io_context::executor_type> g2(other_ctx.get_executor());

asio::co_spawn(ctx, co_main(other_ctx), asio::detached);

std::thread([&]{
std::this_thread::sleep_for(std::chrono::seconds(run_second));
ctx.stop();
other_ctx.stop();
}).detach();
std::thread t1([&]{ctx.run();});
std::thread t2([&]{other_ctx.run();});
t1.join();
t2.join();

return 0;
}

协程 co_mainctx 执行但是其中的定时器是由 other_ctx 执行的. wait_second run_second 表示定时器的等待时间ctx 代表多少秒后和 io_context将被停止。

如果wait_second小于 run_second , 一切正常,但如果 wait_second更长,heap used after free将由 sanitizer 报告。那么这是使用 asio::awaitable 的正确方法吗? ?我可以混合使用 io_context 吗?与协程?

最佳答案

是的,这种用法很好。不是最优的,但很好。

将 IO 对象绑定(bind)到执行程序需要注意的是,它主要有两个目的:

  1. 首先,它指示哪个执行上下文保存 IO 对象实现的服务实例
  2. 其次,它作为完成处理程序的默认(!) 执行器

请注意,1. 意味着它可能比共享上下文产生更多的开销,而 2. 意味着在这种情况下几乎没有行为差异。它只是默认值,因此可等待对象的 coro 执行程序会在此处覆盖它。这可能与您预期的不同。参见例如这个类似的问题Boost asio steady_timer work on different strand than intended

什么是 sanitizer 问题

这是一辈子的事。让我们进行一些测试并使用 BOOST_ASIO_ENABLE_HANDLER_TRACKING:

#include <boost/asio.hpp>
#include <iostream>
using namespace std::chrono_literals;
namespace asio = boost::asio;

constexpr auto wait_for = 200ms, run_for = 300ms;

asio::awaitable<void> co_main(asio::io_context& other_ctx){
asio::steady_timer timer{other_ctx.get_executor(), wait_for};
co_await timer.async_wait(asio::use_awaitable);
std::cout << "timer" << std::endl;
}

int main() {
{
asio::io_context other_ctx;
{
asio::io_context ctx;
//auto g1 = ctx.get_executor();
//auto g2 = other_ctx.get_executor();

asio::co_spawn(ctx, co_main(other_ctx), asio::detached);

std::thread stopper([&] {
std::this_thread::sleep_for(run_for);
ctx.stop();
other_ctx.stop();
});

std::thread t1([&] { ctx.run(); });
std::thread t2([&] { other_ctx.run(); });
stopper.join();
t1.join();
t2.join();
std::cout << "All joined" << std::endl;
std::cout << "ctx: " << &ctx << std::endl;
std::cout << "other_ctx: " << &other_ctx << std::endl;
}
std::cout << "ctx destructed" << std::endl;
}
std::cout << "other_ctx destructed" << std::endl;
}

在所有线程完成(并加入)后,other_ctx 的析构函数放弃操作队列中的所有操作。一些完成绑定(bind)到已经消失的 ctx 的执行器(因为 coro,见上文)。这导致使用后范围(截断 SO):

@asio|1661689229.839262|0^1|in 'co_spawn_entry_point' (/home/sehe/custom/superboost/boost/asio/impl/co_spawn.hpp:154)
@asio|1661689229.839262|0*1|io_context@0x7ffe27cf56a0.execute
@asio|1661689229.839715|>1|
@asio|1661689229.839961|1*2|deadline_timer@0x6110000000a0.async_wait
@asio|1661689229.839986|<1|
All joined
ctx: 0x7ffe27cf56a0
other_ctx: 0x7ffe27cf5680
ctx destructed
@asio|1661689230.139986|2*3|io_context@0x7ffe27cf56a0.execute
=================================================================
==16129==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffe27cf56a8 at pc 0x55bace9962f6 bp 0x7ffe27cf4270 sp 0x7ffe27cf4260
READ of size 8 at 0x7ffe27cf56a8 thread T0
#0 0x55bace9962f5 in void boost::asio::io_context::basic_executor_type<std::allocator<void>, 0ul>::....hpp:319
#1 0x55bace968067 in void boost::asio::execution::detail::any_executor_base::execute<boost::asio::d....hpp:611
#2 0x55bace968067 in std::enable_if<boost_asio_execution_execute_fn::call_traits<boost_asio_executi....hpp:208
#3 0x55bace968067 in void boost::asio::detail::initiate_post_with_executor<boost::asio::any_io_exec....hpp:122
#4 0x55bace968067 in void boost::asio::detail::completion_handler_async_result<boost::asio::detail:....hpp:482
#5 0x55bace968067 in boost::asio::constraint<boost::asio::detail::async_result_has_initiate_memfn<b....hpp:896
#6 0x55bace968067 in auto boost::asio::post<boost::asio::any_io_executor, boost::asio::detail::awai....hpp:242
#7 0x55bace969443 in boost::asio::detail::awaitable_thread<boost::asio::any_io_executor>::~awaitabl....hpp:673
#8 0x55bace99ecb0 in boost::asio::detail::awaitable_handler_base<boost::asio::any_io_executor, void....hpp:29
#9 0x55bace99ecb0 in boost::asio::detail::awaitable_handler<boost::asio::any_io_executor, boost::sy....hpp:78
#10 0x55bace99ecb0 in boost::asio::detail::binder1<boost::asio::detail::awaitable_handler<boost::as....hpp:139
#11 0x55bace99ecb0 in boost::asio::detail::wait_handler<boost::asio::detail::awaitable_handler<boos....hpp:67
#12 0x55bace91be12 in boost::asio::detail::scheduler_operation::destroy() /home/sehe/custom/superbo....hpp:45
#13 0x55bace91be12 in void boost::asio::detail::op_queue_access::destroy<boost::asio::detail::sched....hpp:47
#14 0x55bace91be12 in boost::asio::detail::op_queue<boost::asio::detail::scheduler_operation>::~op_....hpp:81
#15 0x55bace91be12 in boost::asio::detail::scheduler::abandon_operations(boost::asio::detail::op_qu....ipp:444
#16 0x55bace91be12 in boost::asio::detail::epoll_reactor::shutdown() /home/sehe/custom/superboost/b....ipp:92
#17 0x55bace8ebc8c in boost::asio::detail::service_registry::shutdown_services() /home/sehe/custom/....ipp:44
#18 0x55bace8ebc8c in boost::asio::execution_context::shutdown() /home/sehe/custom/superboost/boost....ipp:41
#19 0x55bace8ebc8c in boost::asio::execution_context::~execution_context() /home/sehe/custom/superb....ipp:34
#20 0x55bace88d684 in boost::asio::io_context::~io_context() /home/sehe/custom/superboost/boost/asi....ipp:56
#21 0x55bace88d684 in main /home/sehe/Projects/stackoverflow/test.cpp:16
#22 0x7efe676d0c86 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21c86)
#23 0x55bace88e859 in _start (/home/sehe/Projects/stackoverflow/build/sotest+0x1a5859)

Address 0x7ffe27cf56a8 is located in stack of thread T0 at offset 904 in frame
#0 0x55bace88bd2f in main /home/sehe/Projects/stackoverflow/test.cpp:14

This frame has 34 object(s):
// ... skipped
[128, 136) '<unknown>'
[160, 168) 'stopper' (line 24)
[192, 200) 't1' (line 30)
[224, 232) '<unknown>'
[256, 264) 't2' (line 31)
[288, 296) '<unknown>'
// ... skipped
[832, 840) '<unknown>'
[864, 880) 'other_ctx' (line 16)
[896, 912) 'ctx' (line 18) <== Memory access at offset 904 is inside this variable
[928, 944) '<unknown>'
[960, 1016) '<unknown>'
[1056, 1112) '<unknown>'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope /home/sehe/custom/superboost/boost/asio/impl/io_context.hpp:319 in void boost::asio::io_context::basic_executor_type<std::allocator<void>, 0ul>::execute<boost::asio::detail::executor_function>(boost::asio::detail::executor_function&&) const
Shadow bytes around the buggy address:
0x100044f96a80: f8 f2 f2 f2 f8 f2 f2 f2 f8 f2 f2 f2 f8 f2 f2 f2
0x100044f96a90: f8 f2 f2 f2 f8 f2 f2 f2 00 f2 f2 f2 00 f2 f2 f2
0x100044f96aa0: 00 f2 f2 f2 f8 f2 f2 f2 f8 f2 f2 f2 f8 f2 f2 f2
0x100044f96ab0: f8 f2 f2 f2 f8 f2 f2 f2 f8 f2 f2 f2 f8 f2 f2 f2
0x100044f96ac0: f8 f2 f2 f2 f8 f2 f2 f2 f8 f2 f2 f2 f8 f2 f2 f2
=>0x100044f96ad0: 00 00 f2 f2 f8[f8]f2 f2 f8 f8 f2 f2 f8 f8 f8 f8
0x100044f96ae0: f8 f8 f8 f2 f2 f2 f2 f2 f8 f8 f8 f8 f8 f8 f8 f3
0x100044f96af0: f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
0x100044f96b00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100044f96b10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x100044f96b20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==16129==ABORTING

具有讽刺意味的是,翻转执行器销毁的顺序并没有帮助:

{
asio::io_context ctx;
{
asio::io_context other_ctx;
// ... skiped unchanged
}
std::cout << "other_ctx destructed" << std::endl;
}
std::cout << "ctx destructed" << std::endl;

现在我们得到了反向依赖:在所有线程都加入后,other_ctx 的析构函数正确地将 async_wait 的完成排队在 ctx 上。但是一旦 ctx 析构函数关闭其服务,它将破坏 coro 堆栈帧,其中包括计时器对象,该对象...具有对 other_ctx 析构函数的陈旧引用。

@asio|1661689960.225134|0^1|in 'co_spawn_entry_point' (/home/sehe/custom/superboost/boost/asio/impl/co_spawn.hpp:154)
@asio|1661689960.225134|0*1|io_context@0x7ffd9e839d80.execute
@asio|1661689960.225576|>1|
@asio|1661689960.225876|1*2|deadline_timer@0x6110000000a0.async_wait
@asio|1661689960.225899|<1|
All joined
ctx: 0x7ffd9e839d80
other_ctx: 0x7ffd9e839da0
@asio|1661689960.525759|2*3|io_context@0x7ffd9e839d80.execute
@asio|1661689960.525773|~2|
other_ctx destructed
/usr/include/c++/10/coroutine:128: runtime error: member call on null pointer of type 'struct steady_timer'
/home/sehe/custom/superboost/boost/asio/basic_waitable_timer.hpp:382:3: runtime error: member access within null pointer of type 'struct basic_waitable_timer'
/home/sehe/custom/superboost/boost/asio/basic_waitable_timer.hpp:382:3: runtime error: member call on null pointer of type 'struct io_object_impl'
/home/sehe/custom/superboost/boost/asio/detail/io_object_impl.hpp:97:5: runtime error: member access within null pointer of type 'struct io_object_impl'
AddressSanitizer:DEADLYSIGNAL
=================================================================
==22649==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x558135f6b18f bp 0x7ffd9e837d10 sp 0x7ffd9e837a00 T0)
==22649==The signal is caused by a READ memory access.
==22649==Hint: address points to the zero page.
#0 0x558135f6b18f in boost::asio::detail::io_object_impl<boost::asio::detail::deadline_timer_service<bo....hpp:97
#1 0x558135e6d07a in boost::asio::basic_waitable_timer<std::chrono::_V2::steady_clock, boost::asio::wai....hpp:382
#2 0x558135e6d07a in co_main(boost::asio::io_context&) [clone .actor] /home/sehe/custom/superboost/boos....hpp:380
#3 0x558135f0b5e9 in std::__n4861::coroutine_handle<void>::destroy() const /usr/include/c++/10/coroutine:128
#4 0x558135f0b5e9 in boost::asio::detail::awaitable_frame_base<boost::asio::any_io_executor>::destroy()....hpp:496
#5 0x558135f0b5e9 in boost::asio::awaitable<void, boost::asio::any_io_executor>::~awaitable() /home/seh....hpp:77
#6 0x558135f0b5e9 in boost::asio::awaitable<boost::asio::detail::awaitable_thread_entry_point, boost::a....actor] /home/sehe/custom/superboost/boost/asio/impl/co_spawn.hpp:183
#7 0x558135ea6d1d in std::__n4861::coroutine_handle<void>::destroy() const /usr/include/c++/10/coroutine:128
#8 0x558135ea6d1d in boost::asio::detail::awaitable_frame_base<boost::asio::any_io_executor>::destroy()....hpp:496
#9 0x558135ea6d1d in boost::asio::awaitable<boost::asio::detail::awaitable_thread_entry_point, boost::a....hpp:77
#10 0x558135ea6d1d in boost::asio::detail::awaitable_thread<boost::asio::any_io_executor>::~awaitable_t....hpp:674
#11 0x558135ea6d1d in boost::asio::detail::binder0<boost::asio::detail::awaitable_thread<boost::asio::a....hpp:32
#12 0x558135ea6d1d in void boost::asio::detail::executor_function::complete<boost::asio::detail::binder....hpp:110
#13 0x558135f6ecfb in boost::asio::detail::executor_function::~executor_function() /home/sehe/custom/su....hpp:55
#14 0x558135f6ecfb in boost::asio::detail::executor_op<boost::asio::detail::executor_function, std::all....hpp:62
#15 0x558135ec4893 in boost::asio::detail::scheduler_operation::destroy() /home/sehe/custom/superboost/....hpp:45
#16 0x558135ec4893 in boost::asio::detail::scheduler::shutdown() /home/sehe/custom/superboost/boost/asi....ipp:176
#17 0x558135ebfc8c in boost::asio::detail::service_registry::shutdown_services() /home/sehe/custom/supe....ipp:44
#18 0x558135ebfc8c in boost::asio::execution_context::shutdown() /home/sehe/custom/superboost/boost/asi....ipp:41
#19 0x558135ebfc8c in boost::asio::execution_context::~execution_context() /home/sehe/custom/superboost....ipp:34
#20 0x558135e61684 in boost::asio::io_context::~io_context() /home/sehe/custom/superboost/boost/asio/im....ipp:56
#21 0x558135e61684 in main /home/sehe/Projects/stackoverflow/test.cpp:16
#22 0x7fbd0bd07c86 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21c86)
#23 0x558135e62859 in _start (/home/sehe/Projects/stackoverflow/build/sotest+0x1a5859)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/sehe/custom/superboost/boost/asio/detail/io_object_impl.hpp:97 in boost::asio::detail::io_object_impl<boost::asio::detail::deadline_timer_service<boost::asio::detail::chrono_time_traits<std::chrono::_V2::steady_clock, boost::asio::wait_traits<std::chrono::_V2::steady_clock> > >, boost::asio::any_io_executor>::~io_object_impl()
==22649==ABORTING

问题似乎是 coro 框架的生命周期超过了此处 other_ctx 的生命周期。

结论

您必须避免因循环相互依赖而破坏两个上下文。这实际上意味着避免在他们的操作队列中进行未完成的操作。

值得注意的是,问题大多始于使用 io_context::stop() 停止上下文操作中。此外,目前似乎没有理由首先 run() other_ctx

考虑使用带有取消功能的异步完成/可等待运算符。

关于c++ - 我可以 co_await 一个 io_context 在协程中由另一个在 Asio 中执行的操作吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73517163/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com