You need to enable JavaScript to run this app. 如何解决boost::asio::system_timer的wait()被信号中断抛异常的问题
优惠活动
大模型
产品
解决方案
定价
更多

如何解决boost::asio::system_timer的wait()被信号中断抛异常的问题

问题本质

interrupted system call是POSIX信号机制的标准行为:所有处于阻塞状态的系统调用(asio定时器底层依赖的等待/事件轮询接口)在收到未被进程阻塞的信号时,会被强制中断并返回EINTR错误码。boost.asio默认会将这个错误码转化为boost::system::system_error异常抛出,属于正常的错误透传,不是代码逻辑缺陷。
结合你当前对齐30秒整点的定时逻辑,哪怕wait()被信号打断,重新进入循环重算过期时间的操作本身是幂等的,完全不会破坏长期定时精度。

可选解决方案

1. 最小改动:同步捕获中断异常,适配现有循环结构

不需要调整原有的循环框架,仅需给同步wait()加上异常捕获逻辑:

while(!exit_program) {
    auto expiration_time =
        std::chrono::duration_cast< std::chrono::seconds >(
            std::chrono::system_clock::now().time_since_epoch());
    expiration_time =
        expiration_time - (expiration_time % std::chrono::seconds(30));
    expiration_time += std::chrono::seconds(30);
    auto tp = std::chrono::time_point< std::chrono::system_clock >(
        std::chrono::duration(expiration_time));
    timer.expires_at(tp);

    try {
        timer.wait();
    } catch (const boost::system::system_error& e) {
        // 仅对系统调用被信号中断的错误做静默跳过,其他异常正常抛出
        if (e.code() != boost::asio::error::interrupted) {
            throw;
        }
        // 被信号打断后直接回到循环开头,重算下一个对齐时间点
        continue;
    }

    <do some stuff>
}

注意:捕获异常时必须校验错误码,不能无差别吞掉所有异常,避免掩盖真正的代码错误

2. 结构优化:切换为异步定时器模式,由io_context统一调度

你现有代码存在结构割裂:既定义了io_context和异步信号集,又在外部通过同步wait()实现阻塞,没有让io_context接管所有异步事件。将定时器改为异步等待模式后,io_context会在底层自动处理EINTR中断,不需要手动捕获异常:

std::atomic<bool> running{true};
boost::asio::io_context timer_context;
boost::asio::system_timer* timer_ptr; // 持有定时器对象指针供回调访问

void on_timer_expire(const boost::system::error_code& ec) {
    if (ec) {
        return;
    }
    <do some stuff>
    if (!running) {
        return;
    }
    // 重算下一个30秒对齐时间点,重新发起异步等待
    auto expiration_time =
        std::chrono::duration_cast< std::chrono::seconds >(
            std::chrono::system_clock::now().time_since_epoch());
    expiration_time =
        expiration_time - (expiration_time % std::chrono::seconds(30));
    expiration_time += std::chrono::seconds(30);
    auto tp = std::chrono::time_point< std::chrono::system_clock >(
        std::chrono::duration(expiration_time));
    timer_ptr->expires_at(tp);
    timer_ptr->async_wait(on_timer_expire);
}

void exit_signal_handler(const boost::system::error_code& ec, int signo) {
    if (ec) {
        return;
    }
    running = false;
    timer_context.stop();
}

int main() {
    boost::asio::signal_set term_signals(timer_context, SIGINT, SIGTERM);
    term_signals.async_wait(exit_signal_handler);
    boost::asio::system_timer timer(timer_context);
    timer_ptr = &timer;

    // 发起第一次异步等待
    auto expiration_time =
        std::chrono::duration_cast< std::chrono::seconds >(
            std::chrono::system_clock::now().time_since_epoch());
    expiration_time =
        expiration_time - (expiration_time % std::chrono::seconds(30));
    expiration_time += std::chrono::seconds(30);
    auto tp = std::chrono::time_point< std::chrono::system_clock >(
        std::chrono::duration(expiration_time));
    timer.expires_at(tp);
    timer.async_wait(on_timer_expire);

    // 启动io_context事件循环,内部会自动对EINTR中断做重试
    timer_context.run();
    return 0;
}

注意:这种模式下所有事件由同一个io_context统一调度,和你原本设计的异步信号处理逻辑完全适配,不存在线程竞争问题

3. 系统级调整:给信号处理加上SA_RESTART标志

如果不想修改代码业务逻辑,可以在程序启动时为SIGINT、SIGTERM的信号处理配置SA_RESTART标志,让被信号打断的系统调用自动重启,不会返回EINTR错误。
该方式属于进程级全局修改,会影响所有阻塞系统调用的行为,仅作为备选方案,不推荐在业务代码中优先使用


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.02 14:06:06