【问题标题】:asio high_resolution_timer segmentation fault in async_waitasync_wait 中的 asio high_resolution_timer 分段错误
【发布时间】:2016-03-16 14:19:20
【问题描述】:

我已经实现了一个优先级来自asio examples 的任务队列和一个使用这个队列的计时器类。代码如下:

priority_task_queue.h

class handler_priority_queue
{
private:
    class queued_handler{
    private:
        size_t _priority;
        std::function<void()> _function;
    public:
        queued_handler(size_t p, std::function<void()> f): _priority(p), _function(f){}
        friend bool operator<(const queued_handler& a, const queued_handler& b){
            return a._priority < b._priority;
        }
        void operator()() {
            _function();
        }
    };
    std::priority_queue<queued_handler> _handlers;

public:
    // A generic wrapper class for handlers to allow the invocation to be hooked.
    template <typename Handler> class wrapped_handler
    {
        private:
            handler_priority_queue& _queue;
            size_t _priority;
            Handler _handler;

        public:
            handler_priority_queue& queue() {return _queue;}
            size_t priority() {return _priority;}

            wrapped_handler(handler_priority_queue& q, size_t p, Handler h)
                : _queue(q), _priority(p), _handler(h){}


            template <typename ...Args>
            void operator()(Args&&... args){
                _handler(std::forward<Args>(args)...);
            }
    };
    template <typename Handler> wrapped_handler<Handler> wrap(size_t priority, Handler handler){
        return wrapped_handler<Handler>(*this, priority, handler);
    }
    void add(size_t priority, std::function<void()> function);
    void execute_all();
    void execute_one();
    bool empty();
};

// Custom invocation hook for wrapped handlers.
template <typename Function, typename Handler>
void asio_handler_invoke(Function f, handler_priority_queue::wrapped_handler<Handler>* h){
    h->queue().add(h->priority(), f);
    std::cout<<"LLAMANDO AL INVOKE"<<std::endl; //BORRAR!!
}

class C_priority_task_queue{

    private:
        asio::io_service& _io;
        handler_priority_queue _pri_queue;

    public:
        template <typename Handler> handler_priority_queue::wrapped_handler<Handler> wrap(int priority, Handler handler){
            return _pri_queue.wrap(priority, handler);
        }

        explicit C_priority_task_queue(asio::io_service& io): _io(io){}
        C_priority_task_queue(C_priority_task_queue const&) = delete;
        C_priority_task_queue& operator =(C_priority_task_queue const&) = delete;

        asio::io_service& io() {return _io;}
        void run();
};

priority_task_queue.cpp

void handler_priority_queue::add(size_t priority, std::function<void()> function){
    _handlers.push(queued_handler(priority, function));
}

void handler_priority_queue::execute_one(){
    if(!_handlers.empty()){
        queued_handler handler = _handlers.top();
        handler();
        _handlers.pop();
    }
}

bool handler_priority_queue::empty(){
    return _handlers.empty();
}

void C_priority_task_queue::run(){
    while (_io.run_one())
    {
        _io.poll();
        while(!_pri_queue.empty())
        {
            _io.poll();
            _pri_queue.execute_one();
        }
    }
}

base_timer.h

class C_timer {
    private:
        asio::high_resolution_timer _timer;
        uint8_t _timer_id; 
        C_priority_task_queue& _prio_queue;


    void timer_handler_internal(const asio::error_code& e, uint8_t timer_id, const uint64_t sched_time);
    virtual void timer_handler(const uint64_t sched_time)=0;

    public:
        size_t _priority;
        explicit C_timer(C_priority_task_queue& prio_queue, size_t priority);
        virtual ~C_timer();

        void set_timer(uint64_t sched_time);
        int cancel();
};

base_timer.cpp

C_timer::C_timer(C_priority_task_queue& prio_queue, size_t priority):
        _timer(prio_queue.io()), _timer_id(0), _prio_queue(prio_queue), _priority(priority){}

C_timer::~C_timer(){}

void C_timer::set_timer(uint64_t sched_time){
    ++_timer_id;

    _timer.expires_at(std::chrono::time_point<std::chrono::high_resolution_clock>(std::chrono::milliseconds(sched_time)));
    _timer.async_wait(_prio_queue.wrap(_priority, std::bind(&C_timer::timer_handler_internal, this,
                      std::placeholders::_1/*error*/, _timer_id, sched_time)));
}

int C_timer::cancel(){
    ++_timer_id;
    return _timer.cancel();
}

void C_timer::timer_handler_internal(const asio::error_code& e, uint8_t timer_id,
                                               const uint64_t sched_time){
    if(e==asio::error::operation_aborted || timer_id != _timer_id){
        return;
    }
    timer_handler(sched_time);
}

测试类

class C_timer_test: public C_timer{
    private:
        int _period;

        virtual void timer_handler(const uint64_t sched_time) override{
            std::cout<<"timer fired"<<std::endl;

            uint64_t current_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
            set_timer(current_time + _period);
        }

    public:
        C_timer_test(C_priority_task_queue& prio_queue, int priority, int period):C_timer(prio_queue, priority), _periodo(period){}
        virtual ~C_timer_test(){}
        void run(uint64_t delay=0){
            uint64_t time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
            set_timer(time + delay);
        }
};

问题是如果我执行这个:

int main()
{
    asio::io_service io;
    C_priority_task_queue prio_queue(io);
    asio::io_service::work w(io);

  C_timer_test ti1(prio_queue, 0, 2000);
  ti1.run();
  prio_queue.run();

  return 0;
}

我遇到了分段错误。

但是,如果我执行以下代码,它可以正常工作:

int main()
{
    asio::io_service io;
    C_priority_task_queue prio_queue(io);
    asio::high_resolution_timer _timer1(io);
    asio::io_service::work w(io);

  C_timer_test ti1(prio_queue, 0, 2000);
  ti1.run();
  prio_queue.run();

  return 0;
}

这两段代码之间的唯一区别在于第二个主要部分,我添加了以下行asio::high_resolution_timer _timer1(io);,我在任何地方都没有使用过。

调试程序我发现信号在这一行上升: func_(&amp;owner, this, ec, bytes_transferred); 在文件中task_io_service_operation.hpp

我使用的是 asio 版本 1.10.6。

对可能发生的事情有什么建议吗?

来自 gdb 的回溯:

gdb ./main 
GNU gdb (Ubuntu 7.7.1-0ubuntu5~14.04.2) 7.7.1
This GDB was configured as "i686-linux-gnu".
(gdb) r
[libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x0805f0d4 in ?? ()
(gdb) backtrace 
#0  0x0805f0d4 in ?? ()
#1  0x080529fb in asio::detail::task_io_service::do_run_one (this=0x805f030, lock=..., this_thread=..., ec=...) at /src/cpp/external_lib/asio/include/asio/detail/impl/task_io_service.ipp:371
#2  0x080526ce in asio::detail::task_io_service::run_one (this=0x805f030, ec=...) at /src/cpp/external_lib/asio/include/asio/detail/impl/task_io_service.ipp:169
#3  0x08052c68 in asio::io_service::run_one (this=0xbffff08c) at /src/cpp/external_lib/asio/include/asio/impl/io_service.ipp:71
#4  0x08051f32 in C_priority_task_queue::run (this=0xbffff094) at priority_task_queue.cpp:19
#5  0x08049ac3 in main () at main.cpp:46

这里有 MakeFile:

TARGET=main
SOURCES=  main.cpp base_timer.cpp  priority_task_queue.cpp
SOURCE_DIR=.
INCLUDE_LIB= -L/src/cpp/libcore
INCLUDE_DIR=-I/src/cpp/external_lib/asio/include \
             -I/src/cpp/libcore/include
INSTALL_DIR=.
LIB=-pthread
CXX=g++
CFLAGS=-Wall -fexceptions -fpermissive -std=c++11 -DASIO_STANDALONE
CFLAGS_DEBUG = -g3 -DDEBUG
OBJDIR_DEBUG=obj
BINDIR_DEBUG=.
OBJECTS_DEBUG:= $(addprefix $(OBJDIR_DEBUG)/,$(SOURCES:.cpp=.o))

all: debug

$(OBJDIR_DEBUG)/%.o: $(SOURCE_DIR)/%.cpp
    @test -d $(OBJDIR_DEBUG) || mkdir -p $(OBJDIR_DEBUG)
    $(CXX) $(CFLAGS) $(CFLAGS_DEBUG) $(INCLUDE_DIR) -c $< -o $@

debug: $(OBJECTS_DEBUG)
    @test -d $(BINDIR_DEBUG) || mkdir -p $(BINDIR_DEBUG)
    $(CXX) -o $(BINDIR_DEBUG)/$(TARGET) $^ $(INCLUDE_LIB) $(LIB)

更新

我的调查,我发现如果我在 .h 中定义 base_timer 成员(基本上是 asio::high_resolution_timer)初始化,代码运行正常,但如果我在 .cpp 中这样做,代码会崩溃。

我的意思是,

explicit C_timer(C_priority_task_queue& prio_queue, size_t priority):
        _timer(prio_queue.io()), _timer_id(0), _prio_queue(prio_queue), _priority(priority){}

在 .h 中有效,但是

C_timer::C_timer(C_priority_task_queue& prio_queue, size_t priority): 
   _timer(prio_queue.io()), _timer_id(0), _prio_queue(prio_queue), _priority(priority){}

在 .cpp 中失败

【问题讨论】:

  • 我认为您错过了对 io.run() 的呼叫。 io_service 类需要在其上运行的线程。编辑:run_one() 调用可能不够,如果您打算使用计时器,您可能应该有一个线程供io_service 运行。
  • @nimble_ninja 这不是问题。只是这部分取自 asio 示例(执行 while(io.run_one()) 与调用 io.run() 相同)。无论如何,正如我在问题中提到的,如果我添加了这一行,asio::high_resolution_timer _timer1(io),与其余代码无关,程序运行完美。如果我省略它,它会引发分段错误。
  • 在调试器中运行它会发生什么?崩溃的回溯是什么?
  • 除了“不自然”与 chrono 杂耍(时间点或持续时间,选择一个!)代码看起来没问题。我无法重现任何故障(GCC、linux):coliru.stacked-crooked.com/a/33f400874358b133
  • 如果添加随机变量会使问题出现/消失,你应该想到Undefined Behaviour,使用静态分析、valgrind/purify/...和代码审查来找到你的罪魁祸首。 Valgrind、ASAN 和 UBSAN 在我的 PC 上运行干净

标签: c++ c++11 segmentation-fault boost-asio


【解决方案1】:

除了“不自然”的 chrono 杂耍(时间点或持续时间,选择一个!)代码看起来没问题。我无法重现任何失败(GCC、linux):

Live On Coliru

如果添加随机变量会使问题出现/消失,您应该考虑未定义的行为,使用静态分析、valgrind/purify/... 和代码审查来找到您的罪魁祸首。 Valgrind、ASAN 和 UBSAN 在我的 PC 上运行干净

@sehe 感谢您的努力。不同之处在于,如果我将每个代码都放在一个文件 main.cpp 中,它会运行,但如果我分成几个文件,问题仍然存在。另一方面,如果我在 main.cpp 中实例化一个 high_resolution_timer 对象,无论在哪里(在 main() 中,在从不被调用的单独函数中,......)它都会运行,但没有它,会引发分段错误.

太好了:您找到了 UB 的一个潜在来源:看看静态变量或非 ODR 安全的内联函数的使用。 (仔细检查所有翻译单元是否使用相同的编译器标志)。

另外,请记住 UBUNDEFINED,所以就像添加一个不相关的 _timer1 会改变明显的行为(而不改变 来源 UB 的 em>)同样的事情可以使它看起来工作。

它在我的机器上运行干净的事实告诉你,这必须是一个特定于平台的 UB 源

【讨论】:

  • 我将生成文件添加到帖子中。我虽然它与全局/静态变量有关。我认为 asio 是一个成熟的库,并且对这种行为是免费的。我在官方文档中查找,但在 asio 源中找不到与全局或静态变量建议或问题相关的任何内容。
  • 没有人说原因在于库代码。事实上that's highly unlikely。我指的是你的代码。我没看过您能与 Boost Asio 进行比较(假设 Asio 1.10.6 表示独立版本?)。也许是时候在那里获得支持票了
  • -DASIO_STANDALONE 可能确实涉及,如果支持,请尝试使用链接库。如果这很重要,那么 90% 是 ODR 问题(检查翻译单元之间的不同编译标志/编译器版本;例如,进行完全重建、绕过 ccache 等)。在剩下的 10% 的可能性中,Asio 标头中可能存在问题(可能是文件静态冲突)。我只是猜测。希望对你有帮助
  • 我发布的代码都是程序。认为原因在图书馆是我的最后一张卡,因为我已经隔离和修改了整个代码,我找不到任何问题。但是,我对C++模板编程的知识非常有限,所以我不知道我是否做错了什么,考虑到asio是肠子编程。
  • 使用-DASIO_STANDALONE 标志是程序的要求,因为我不想与其余的boost库有依赖关系,设置这个标志是我可以使用C++11的唯一方法类。
【解决方案2】:

问题是我一直在使用 1.10.2 版本的库,似乎有一个错误。

我已经更新到最新版本 1.10.6,现在可以正常运行了。

【讨论】:

  • 它“似乎”有一个错误?如果我不确定是什么错误以及那个它已修复,我将无法入睡。
  • 发布历史中与您的症状相关的似乎合理怀疑的最早更改是"Fixed delegation of continuation hook for handlers produced by io_service::wrap() and strand::wrap().",但这是在 1.10.2 中修复的,而不是之后。唔。没时间追查了:(
  • @sehe 我已经从 here 下载了 asio 存储库,并查看了从 1.10.2 版到 1.10.6 版的提交,但由于我不是元编程专家,所以我是不太确定错误在哪里。也许是提交 df2e96d0 "Fix unsigned int overflow by clang's integer sanitizer."??
  • @Aaronux 单步调试调试器可能会提供有关崩溃的详细信息。在回溯中,frame 1 正在调用 calls a static function via a pointer 的函数。由于第 0 帧的符号名称未知,我会怀疑翻译单元编译器标志,尤其是 priority_task_queue.o
  • @TannerSansbury priority_task_queue 不是问题所在。在解决 asio 版本的问题之前,我尝试隔离错误,在不使用优先级队列的情况下重新编码程序,删除两个类文件。然后我将 base_timer 类 async_wait 调用更改为这个新的 _timer.async_wait( std::bind(&amp;C_timer::timer_handler_internal, this, std::placeholders::_1/*error*/, _timer_id, sched_time)); 并且问题继续存在。
猜你喜欢
  • 2013-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多