How can I prevent a deadline timer from calling a function in a deleted class?
boost-asio, c++
Solution
The typical recipe to solve this problem is to use a `shared_ptr`
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;
struct TimeBomb : public boost::enable_shared_from_this<TimeBomb>
{
bool m_active;
boost::asio::deadline_timer m_runTimer;
TimeBomb(boost::asio::io_service& ioService)
: m_active(true)
, m_runTimer(ioService)
{
cout << "Bomb placed @"<< hex << this << endl;
m_runTimer.expires_from_now(boost::posix_time::millisec(1000));
}
void start()
{
m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, shared_from_this(), _1));
}
void stop()
{
m_runTimer.cancel();
}
~TimeBomb()
{
m_active = false;
m_runTimer.cancel();
cout << "Bomb defused @"<< hex << this << endl;
}
void executeStepFunction(const boost::system::error_code& error)
{
// Canceled timer
if (error == boost::asio::error::operation_aborted)
{
std::cout << "Timer aborted: " << error.message() << " @" << std::hex << this << std::endl;
return;
}
if (m_active)
{
// Schedule next step
cout << "tick .." <<endl;
m_runTimer.expires_from_now(
boost::posix_time::millisec(1000));
m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, shared_from_this(), _1));
}
}
};
struct BomberMan
{
boost::asio::deadline_timer m_selfDestructTimer;
boost::shared_ptr<TimeBomb> myBomb;
BomberMan(boost::asio::io_service& ioService)
: m_selfDestructTimer(ioService)
{
cout << "BomberMan ready " << endl;
myBomb.reset( new TimeBomb(ioService) );
myBomb->start();
m_selfDestructTimer.expires_from_now(boost::posix_time::millisec(10500));
m_selfDestructTimer.async_wait(boost::bind(&BomberMan::defuseBomb, this, _1));
}
void defuseBomb(const boost::system::error_code& error)
{
cout << "Defusing TimeBomb" << endl;
myBomb->stop();
}
};
int main()
{
boost::asio::io_service m_ioService;
BomberMan* b = new BomberMan(m_ioService);
m_ioService.run();
return 0;
}
Problem
I have problem in a piece of real-life code, where a function belonging to a deleted class is called by a `boost::asio::deadline_timer`, occasionally leading to a segmentation fault. The issue I'm having is that the deletion of the deadline_timer is run from another timer on the same io_service. The deletion of the first `deadline_timer` will trigger one final call to the function to be run, with a `boost::asio::error::operation_aborted` error. However this can only be scheduled on the (same) `io_service` after the delete has finished, but by then the object is already deleted and thus no longer valid. So my question is: how can I prevent this from happening? The following is a simplified example with the same fault: ``` //============================================================================ // Name : aTimeToKill.cpp // Author : Pelle // Description : Delete an object using a timer, from a timer //============================================================================ #include <iostream> #include <boost/function.hpp> #include <boost/bind.hpp> #include <boost/asio.hpp> #include <boost/thread.hpp> using namespace std; using namespace boost; struct TimeBomb { bool m_active; asio::deadline_timer m_runTimer; TimeBomb(boost::asio::io_service& ioService) : m_active(true) , m_runTimer(ioService) { cout << "Bomb placed @"<< hex << (int)this << endl; m_runTimer.expires_from_now(boost::posix_time::millisec(1000)); m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, this, _1)); } ~TimeBomb() { m_active = false; m_runTimer.cancel(); cout << "Bomb defused @"<< hex << (int)this << endl; } void executeStepFunction(const boost::system::error_code& error) { // Canceled timer if (error == boost::asio::error::operation_aborted) { std::cout << "Timer aborted: " << error.message() << " @" << std::hex << (int)this << std::endl; return; } if (m_active) { // Schedule next step cout << "tick .." <<endl; m_runTimer.expires_from_now( boost::posix_time::millisec(1000)); m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, this, _1)); } } }; struct BomberMan { asio::deadline_timer m_selfDestructTimer; TimeBomb* myBomb; BomberMan(boost::asio::io_service& ioService) : m_selfDestructTimer(ioService) { cout << "BomberMan ready " << endl; myBomb = new TimeBomb(ioService); m_selfDestructTimer.expires_from_now(boost::posix_time::millisec(10500)); m_selfDestructTimer.async_wait(boost::bind(&BomberMan::defuseBomb, this, _1)); } void defuseBomb(const boost::system::error_code& error) { cout << "Defusing TimeBomb" << endl; delete myBomb; } }; int main() { boost::asio::io_service m_ioService; BomberMan* b = new BomberMan(m_ioService); m_ioService.run(); return 0; } ./aTimeToKill BomberMan ready Bomb placed @9c27198 tick .. tick .. tick .. tick .. tick .. tick .. tick .. tick .. tick .. tick .. Defusing TimeBomb Bomb defused @9c27198 Timer aborted: Operation canceled @9c27198 ``` The last line is printed after the delete, illustrating my problem.