I'm working on a simple class which upon creation schedules a periodic timer for invoking one of its' methods. The method is virtual, so that derived classes can overload it with whatever periodic work they need.
In my test of this class, however, I randomly experience segmentation fault and can't figure out why. Here's the code and example of good and bad outputs:
#include <boost/thread/mutex.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/chrono.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/function.hpp>
#include <boost/atomic.hpp>
#include <boost/make_shared.hpp>
#include <boost/bind.hpp>
//******************************************************************************
class PeriodicImpl;
class Periodic {
public:
Periodic(boost::asio::io_service& io, unsigned int periodMs);
~Periodic();
virtual unsigned int periodicInvocation() = 0;
private:
boost::shared_ptr<PeriodicImpl> pimpl_;
};
//******************************************************************************
class PeriodicImpl : public boost::enable_shared_from_this<PeriodicImpl>
{
public:
PeriodicImpl(boost::asio::io_service& io, unsigned int periodMs,
boost::function<unsigned int(void)> workFunc);
~PeriodicImpl();
void setupTimer(unsigned int intervalMs);
boost::atomic<bool> isRunning_;
unsigned int periodMs_;
boost::asio::io_service& io_;
boost::function<unsigned int(void)> workFunc_;
boost::asio::steady_timer timer_;
};
//******************************************************************************
Periodic::Periodic(boost::asio::io_service& io, unsigned int periodMs):
pimpl_(boost::make_shared<PeriodicImpl>(io, periodMs, boost::bind(&Periodic::periodicInvocation, this)))
{
std::cout << "periodic ctor " << pimpl_.use_count() << std::endl;
pimpl_->setupTimer(periodMs);
}
Periodic::~Periodic()
{
std::cout << "periodic dtor " << pimpl_.use_count() << std::endl;
pimpl_->isRunning_ = false;
pimpl_->timer_.cancel();
std::cout << "periodic dtor end " << pimpl_.use_count() << std::endl;
}
//******************************************************************************
PeriodicImpl::PeriodicImpl(boost::asio::io_service& io, unsigned int periodMs,
boost::function<unsigned int(void)> workFunc):
isRunning_(true),
io_(io), periodMs_(periodMs), workFunc_(workFunc), timer_(io_)
{
}
PeriodicImpl::~PeriodicImpl()
{
std::cout << "periodic impl dtor" << std::endl;
}
void
PeriodicImpl::setupTimer(unsigned int intervalMs)
{
std::cout << "schedule new " << intervalMs << std::endl;
boost::shared_ptr<PeriodicImpl> self(shared_from_this());
timer_.expires_from_now(boost::chrono::milliseconds(intervalMs));
timer_.async_wait([self, this](const boost::system::error_code& e){
std::cout << "hello invoke" << std::endl;
if (!e)
{
if (isRunning_)
{
std::cout << "invoking" << std::endl;
unsigned int nextIntervalMs = workFunc_();
if (nextIntervalMs)
setupTimer(nextIntervalMs);
}
else
std::cout << "invoke not running" << std::endl;
}
else
std::cout << "invoke cancel" << std::endl;
});
std::cout << "scheduled " << self.use_count() << std::endl;
}
//******************************************************************************
class PeriodicTest : public Periodic
{
public:
PeriodicTest(boost::asio::io_service& io, unsigned int periodMs):
Periodic(io, periodMs), periodMs_(periodMs), workCounter_(0){}
~PeriodicTest(){
std::cout << "periodic test dtor" << std::endl;
}
unsigned int periodicInvocation() {
std::cout << "invocation " << workCounter_ << std::endl;
workCounter_++;
return periodMs_;
}
unsigned int periodMs_;
unsigned int workCounter_;
};
//******************************************************************************
void main()
{
boost::asio::io_service io;
boost::shared_ptr<boost::asio::io_service::work> work(new boost::asio::io_service::work(io));
boost::thread t([&io](){
io.run();
});
unsigned int workCounter = 0;
{
PeriodicTest p(io, 50);
boost::this_thread::sleep_for(boost::chrono::milliseconds(550));
workCounter = p.workCounter_;
}
work.reset();
//EXPECT_EQ(10, workCounter);
}
Good output:
hello invoke
invoking
invocation 9
schedule new 50
scheduled 5
periodic test dtor
periodic dtor 2
periodic dtor end 2
hello invoke
invoke cancel
periodic impl dtor
Bad output:
hello invoke
invoking
invocation 9
schedule new 50
scheduled 5
periodic test dtor
periodic dtor 2
periodic dtor end 2
periodic impl dtor
Segmentation fault: 11
Apparently, segmentation fault is happening because PeriodicImpl
is destructed so as its' timer timer_
. But timer is still scheduled - and this leads to SEGFAULT
. I can't understand why PeriodicImpl
destructor is called in this case, because a shared_ptr
to PeriodicImpl
was copied to lambda passed as the timer's handler function during setupTimer
call and this should've retained a copy of PeriodicImpl
and prevent destructor invocation.
Any ideas?
The problem turned out to be entirely not in the questioned code, but in the code that tested it.
I enabled saving core dump file by running
ulimit -c unlimited
and then usedlldb
to read it:Apparently, thread 2 causes crash as it tries to lock mutex which is already destructed. However, I'm not using any mutexes, so this must be something internal to
io_service
. This might happen ifio_service
is still being used after its' destruction. Looking closely at mymain()
function I noticed that the threadt
I created is left dangling, i.e. there is nojoin()
call on it. Consequently, this sometimes creates a situation whenio
object is already destructed (at the end ofmain
) but threadt
still tries to use it.Thus, the problem was fixed by adding
t.join()
call at the end ofmain()
function: