You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

std::shared_ptr存在引用时意外析构——boost::asio协程场景排查

问题排查:Boost.Asio+C++20协程下Socket智能指针引用异常

背景

自研Socket网络库基于Boost.Asio与C++20协程实现:Server类在接受连接时创建Socket类的std::shared_ptr,并将其存入集合管理。Socket通过异步读写持续处理数据,核心代码如下:

Socket异步读写实现

void Socket::read(const String &delim) {
    boost::asio::async_read_until(_sock, boost::asio::dynamic_buffer(_recBuf), delim,
    boost::asio::bind_executor(_readStrand,
    [this, delim = delim, self = shared_from_this()] (const boost::system::error_code& ec, const std::size_t bytes) {
        // 读处理逻辑...
        Socket::handleMsg(<the msg>);
        Socket::read(delim);
    }));
}

void Socket::doWrite(std::shared_ptr<String> str) {
    asio::async_write(_sock, asio::buffer(*str),
    asio::bind_executor(_writeStrand,
    [this, str, self = shared_from_this()] (const boost::system::error_code& ec, const std::size_t bytes) {
        // 写处理逻辑
        // 要么从队列取出下一条消息,要么退出
    }));
}

消息处理逻辑

bool Socket::handleMsg(const object::String &msg) {
    try {
        auto self = shared_from_this();
        co_spawn(_asyncHandleMsgStrand, [this, self, m] () -> boost::asio::awaitable<void> {
            try {
                co_await handleMsgInt(m);
            }
            catch (const std::exception &e) {
                lh.e(fmt::format("{:s}", e.what()));
                stop_int();
            }
        },
        [this, self] (std::exception_ptr e) {
            if (e) {
                _ioc.post([e, self] {
                    std::rethrow_exception(e);
                });
            }
        });
    }
    catch (const std::exception &e) {
        lh.e(fmt::format("{:s}", e.what()));
        stop_int();
        return false;
    }
 
    return true;
}

问题现象

  1. 当handleMsgInt()中co_await挂起后,handleMsg()内的协程lambda析构时,捕获的self(std::shared_ptr<Socket>)被析构。此时在Socket析构函数中发现Server仍持有该Socket的引用,但use_count()返回0。
  2. Socket类存在虚拟菱形继承,且间接继承自std::enable_shared_from_this。创建Socket实例后,构造函数内的this地址、shared_ptr.get()地址、shared_from_this().get()地址三者不一致,日志如下:
(2024/05/31 18:19:02.775665476) <aaaf> (trace)   Socket: this: 0x7fffe8001000
(2024/05/31 18:19:02.775706976) <aaaf> (trace)   Server: shared_ptr before adding to sessions set: 0x7fffe8001150, ref: 1
(2024/05/31 18:19:02.775727176) <aaaf> (trace)   Server: shared_ptr after adding to sessions: 0x7fffe8001150, ref: 2
(2024/05/31 18:19:02.775739376) <aaaf> (trace)   Server: sessions.begin()->get(): 0x7fffe8001150, ref: 2
(2024/05/31 18:19:02.775747376) <aaaf> (trace)   Server: shared from this: 0x7fffe8001218, ref: 3

临时解决方法

移除handleMsg()中对shared_from_this()的捕获后,问题消失,但不清楚具体原因。

可能的关联问题与排查方向

  • 虚拟菱形继承导致enable_shared_from_this失效:std::enable_shared_from_this依赖内部弱引用控制块,菱形继承下若多次间接继承该类,会导致控制块重复初始化,或shared_from_this()返回指针指向子对象偏移地址,引发引用计数混乱。需确保继承链中只有最顶层基类直接继承std::enable_shared_from_this,且用虚拟继承避免重复实例化控制块。
  • shared_from_this()捕获与生命周期不匹配:handleMsg()中self被同时捕获到协程lambda和co_spawn完成处理函数,菱形继承导致this与self.get()地址不一致,可能使智能指针管理的对象和实际操作对象并非同一逻辑实例,引发引用计数提前释放。
  • 协程挂起后的引用计数混乱:C++20协程挂起时,捕获的变量会转移到协程帧,若shared_ptr指向地址与this存在偏移,会导致协程帧中的self与Server集合中的shared_ptr指向不同控制块,出现引用计数不统一的情况。
  • 析构时use_count()检查时机错误:Socket进入析构流程后,部分智能指针已销毁,此时use_count()返回值不准确,需在析构前(如stop_int()中)检查引用计数。

内容的提问来源于stack exchange,提问作者bustus_primus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 03:44:54