ZachPerkitny
ZachPerkitny

Reputation: 143

How do I properly cancel a Boost deadline_timer from a destructor (in a multithreaded environment)?

I have a class called Timer that exposes two methods called start and stop.

void Timer::start() {
    _enabled.store(true, std::memory_order::memory_order_release);
    _timer.expires_from_now(_delay);
    _timer.async_wait(
        std::bind(
            &Timer::tick,
            this,
            std::placeholders::_1
        ));
}

void Timer::stop() {
    _enabled.store(false, std::memory_order::memory_order_release);
    _timer.cancel();
}

void Timer::tick(const boost::system::error_code& error) {
    if (error) return;
    if (!_enabled.load(std::memory_order::memory_order_acquire)) return;
    try {
        _task();
    } catch (...) {}
    if (_enabled.load(std::memory_order::memory_order_acquire)) {
        _timer.expires_from_now(_delay);
        _timer.async_wait(
            std::bind(
                &Timer::tick,
                this,
                std::placeholders::_1
            ));
    }
}

Another class that uses an instance of Timer (handler is executed on some other thread in a ThreadPool instance) calls stop in its destructor. From the Boost Documentation, it is now possible that the handler will be invoked and these two functions will be executed concurrently and the handler may try to access freed resources.

SomeOtherClass::~SomeOtherClass() {
    _timer.stop();
    // somehow wait for _timer handler to execute
    // delete[] some_thing;
    // other destructive things
}

Is there anyway to wait for the handler to finish execution? I've been scratching my head all day, I am quite new to Boost, so perhaps I made a design flaw. Any help would be greatly appreciated, thanks.

Upvotes: 1

Views: 731

Answers (1)

sehe
sehe

Reputation: 393487

A pattern is to have a shared_ptr to your Timer (by using std::enable_shared_from_this).

That way you can keep the timer alive as long as the handler hasn't been executed (by keeping a copy of the shared pointer bound to the handler).

Other solutions could be:

  • having externally allocated timers (e.g. in a container with reference stability, like a std::list) where you delete them manually when they're no longer needed
  • running a dedicated io_service on your own thread, so you can join the thread to await the work on the io_service.

Depending on your use cases/load patterns, one approach will be better than the others.

Samples:

I picked answers that have some contrasting approaches (some not using Boost Asio) so you can see the trade-offs and what changes between approaches.

Upvotes: 1

Related Questions