Boost ASIO timers — errors are never enough

boost_logoThe Boost ASIO Library is a wonderful piece of software. I’ve built high-performance event-driven IO C++ programs that just scream — it works very well. However, there is one subtlety when it comes to timers — specifically when it comes to cancelling expired timers.


Buried in the documentation is the following statement about setting the expiration time of expired deadline timers: These handlers can no longer be cancelled, and therefore are passed an error code that indicates the successful completion of the wait operation. I didn’t appreciate this at the time until I hit a bug in my program. Originally the code looked something like this:

void timer_handler(const boost::system::error_code& e) {
if (e) {
std::cout << "Timer cancelled!" << std::endl
// Don't do any work.
} else {
std::cout << "Timer expired!" << std::endl;
// Do the work here.
}

But every so often the program would raise an assertion because timer_handler() would be called, work would be attempted involving pointers, but no work was available. Then I understood what the documentation was saying — another part of the code was calling expires_from_now() on the timer to reset it and start new work, and I knew this would cancel the timer. But, and this is the point, if the timer has already been put on the event queue for serving (by the ASIO framework) the error code will not be set when timer_handler() is called!
My code now looks like this:

void timer_handler(const boost::system::error_code&amp; e) {
if (e) {
std::cout << "Timer cancelled!" << std::endl
// Don't do any work.
} else {
std::cout << "Timer expired!" << std::endl;
if (work_to_be_done()) {
// Do the work here.
}
}

So be careful when cancelling a timer, or resetting its expiration. It’s not enough to just check the error code.