Search code examples
c++asio

Why can't I use two timers for one asio io_service?


I'm new to asio, and I'm trying to modify some examples for my purpose. One thought would be to run multiple timers and see if the behavior changes if I run io_service.run() on multiple threads. However, I'm not even getting that far as multiple timers cause a segmentation fault I don't understand.

My minimum working example is this:

#include <iostream>
#include <asio.hpp>

class printer {
public:
    asio::steady_timer timer;
    asio::io_service &io_service;
    int count;

    printer(asio::io_service &io_service) : io_service(io_service), timer(io_service, asio::chrono::milliseconds(10)) , count(0) {
        timer.async_wait(std::bind(&printer::timer_func, this));
    }

    void timer_func() {
        std::cout << "count " << ++count << ", on thread " << std::this_thread::get_id() << std::endl;
        if (count < 5)
        {
            timer.expires_at(timer.expiry() + asio::chrono::milliseconds(10));
            timer.async_wait(std::bind(&printer::timer_func, this));
        }
    }
};

int main()
{
    const int NUM_PRINTERS = 2;
    asio::io_service io;

    std::vector<printer> work_timers;
    for (int i = 0; i < NUM_PRINTERS; ++i) {
        work_timers.emplace_back(io);
    }

    io.run();

    std::cout << "broker completed.\n";

    return 0;
}

For NUM_PRINTERS=1, this succeeds nicely:

count 1, on thread 139779481040704
count 2, on thread 139779481040704
count 3, on thread 139779481040704
count 4, on thread 139779481040704
count 5, on thread 139779481040704
broker completed.

However, for NUM_PRINTERS > 1 I get a segmentation fault:

count 1, on thread 140493102753600

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

From my perspective, I have no idea how this can happen. Both printer objects are having their own variables, and I don't understand where this comes from. Why is there a segmentation fault?


Solution

  • In the constructor, you are binding a callable to this. The printer instances, however, are likely to be moved around while reallocating work_timers upon insertion, leaving registered callbacks to steady_timer invalid. A simple fix is to call work_timers.reserve(NUM_PRINTERS) beforehand. Then it works (fix the warnings).

    Apart from undefined behavior from pointer invalidation, steady_timers are also cancelled upon moving, so the handler was attempted to be called as a result of failure. You should really make sure the timers and their callbacks outlive the delay. If you want a more flexible printer class, allocate those internals dynamically.