如何解决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
相关产品推荐
相关产品推荐

