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?