I am trying to write a relatively simple class 'CallbackTimer' that takes an amount of time and a function, and after the amount of time elapses the function is called. This can be repeated a few times if necessary.
It all works as intended, however I am occasionally getting a crash when I call start a couple of times before the timer has finished, but I can't see where the problem is. I assume it's because I'm not mixing my threads and the io_service correctly, but I'm not sure. Any help or pointers would be appreciated.
The class is as follows
CallbackTimer::CallbackTimer(const std::function<void()>& callback) :
callback_(callback),
currentRepeats_(0),
//io_(boost::asio::io_service()),
timer_(new boost::asio::deadline_timer(io_)),
strand_(io_),
thread_(nullptr)
{
}
CallbackTimer::~CallbackTimer()
{
cancel();
}
void CallbackTimer::start(size_t intervalMillis, int repeats)
{
if (thread_)
{
cancel();
}
requestedRepeats_ = repeats;
intervalMillis_ = intervalMillis;
currentRepeats_ = 0;
runTimer();
thread_.reset(new std::thread([this]()
{
this->io_.run();
}));
thread_->detach();
}
void CallbackTimer::cancel()
{
timer_->cancel();
if (thread_ && thread_->joinable())
{
thread_->join();
}
io_.reset();
}
void CallbackTimer::runCallback(const boost::system::error_code& e)
{
if (e == boost::asio::error::operation_aborted)
{
io_.stop();
return;
}
currentRepeats_++;
callback_();
if (currentRepeats_ >= requestedRepeats_)
{
io_.stop();
return;
}
runTimer();
}
void CallbackTimer::runTimer()
{
timer_->expires_from_now(boost::posix_time::millisec(intervalMillis_));
timer_->async_wait(strand_.wrap(std::bind(&CallbackTimer::runCallback, this, std::placeholders::_1)));
}
At quick glance, there are two potential problems:
CallbackTimer
class fails to guarantee the pre-condition thatio_service::reset()
is not invoked when there are unfinished calls to to the run()
, run_one()
, poll()
or poll_one()
, resulting in unspecified behavior.CallbackTimer::runCallback()
may be invoked after the lifetime of the CallbackTimer
instance has ended, invoking undefined behavior.Both problems result from no synchronization with the thread running the io_service
. The thread is detached after its creation in CallbackTimer::start()
, and the the if-statement within CallbackTimer::cancel()
will always be false, causing no synchronization to occur. This allows for the following cases:
io_service::reset()
is invoked while thread_
is within io_service::run()
.CallbackTimer
that owns the io_service
is deallocated while thread_
is within io_service::run()
.To resolve this, consider no longer detaching from thread_
so that synchronization may occur.
For further debugging, a stack trace or an mcve could help in identifying the exact source of the crash.