销毁含windows::stream_handle与命名管道的io_context时Boost.Asio触发访问违规
在Windows环境下运行一个命名管道服务器服务,使用Boost.Asio在单线程通过C++20协程处理多客户端重叠IO,异步监听新客户端连接。服务关闭时通过设置Windows事件对象,由协程异步等待该事件,触发后调用io_context.stop()实现线程优雅退出。目前客户端连接、通信、断开逻辑均正常,但关机时会触发访问违规——即使无客户端连接,问题已定位到等待连接的协程。stop信号接收后,io_context.stop()调用成功,run()返回且线程正常退出并join,但销毁io_context时清理服务对象出错(调用无效函数指针func_),怀疑存在生命周期问题,但无法明确具体原因。
#include <iostream> #include <string> #include <conio.h> #include <boost/asio.hpp> #include <boost/asio/windows/object_handle.hpp> namespace as = boost::asio; const char* pipeName = R"(//./pipe/asio-pipe)"; class DuplexPipe { public: DuplexPipe(as::io_context& ioctx, std::string pipeName) : stream_{ ioctx } { const auto pipeHandle = CreateNamedPipeA( pipeName.c_str(), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, // open mode PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, // pipe mode 3, // max instances 4096, // out buffer 4096, // in buffer 0, // timeout nullptr); // security stream_.assign(pipeHandle); } as::awaitable<void> Accept() { as::windows::object_handle connEvt{ co_await as::this_coro::executor, CreateEventA(nullptr, TRUE, FALSE, nullptr) }; OVERLAPPED over{ .hEvent = connEvt.native_handle() }; ConnectNamedPipe(stream_.native_handle(), &over); if (const auto error = GetLastError(); error == ERROR_IO_PENDING) { // not yet connected, await on connection event co_await connEvt.async_wait(as::use_awaitable); } else if (error == ERROR_PIPE_CONNECTED) { // connected even before we could await event } else { // some error has occurred during connection std::cout << "Failed to connect" << std::endl; std::terminate(); } } private: as::windows::stream_handle stream_; }; class PipeServer { public: PipeServer() : stopEvent_{ ioctx_, CreateEventA(nullptr, TRUE, FALSE, nullptr) } { thread_ = std::jthread{ [this] { as::co_spawn(ioctx_, [this]() -> as::awaitable<void> { co_await stopEvent_.async_wait(as::use_awaitable); ioctx_.stop(); }, as::detached); as::co_spawn(ioctx_, Accept_(), as::detached); ioctx_.run(); } }; } void SignalStop() { SetEvent(stopEvent_.native_handle()); } private: as::awaitable<void> Accept_() { DuplexPipe pipe{ ioctx_, pipeName }; co_await pipe.Accept(); // at this point in the actual app we would spawn another Accept_ coro and then // proceed to communicate with the connected client } as::io_context ioctx_; as::windows::object_handle stopEvent_; std::jthread thread_; }; int main(int argc, const char** argv) { PipeServer srv; while (!_kbhit()); srv.SignalStop(); return 0; }
核心问题
io_context.stop()仅会停止调度新的异步任务,但不会主动取消已提交的挂起异步操作。当服务停止时,Accept_协程处于挂起状态(等待客户端连接),其关联的DuplexPipe对象、connEvt事件对象及ConnectNamedPipe发起的重叠IO操作仍持有io_context的内部状态引用。当io_context被销毁时,这些挂起操作的回调尝试访问已释放的内存,最终触发访问违规。
修复步骤
添加资源清理逻辑
给DuplexPipe添加析构函数,主动取消挂起的IO操作并关闭管道句柄:~DuplexPipe() { if (stream_.is_open()) { // 取消管道上挂起的所有IO操作 CancelIoEx(stream_.native_handle(), nullptr); stream_.close(); } }优化停止流程
修改停止事件的协程逻辑,确保在调用io_context.stop()前,清理所有挂起的工作项:as::co_spawn(ioctx_, [this]() -> as::awaitable<void> { co_await stopEvent_.async_wait(as::use_awaitable); // 通知执行器所有工作已完成,避免残留异步任务 while (ioctx_.run_one()); ioctx_.stop(); }, as::detached);改用Asio原生异步操作(可选)
替换手动调用Windows API的逻辑,使用Boost.Asio封装的异步操作,让Asio自动管理重叠IO的生命周期,减少手动资源管理的风险。
内容的提问来源于stack exchange,提问作者chili

