1

My simplified question
I read this thread and I am trying to delete the io_service object. I do this

m_IO.stop();
m_IO.~io_service();

m_IO is an object of boost::asio::io_service. I found that my thread was blocked by m_IO.~io_service(); How can I delete io_service?

My Complete question
I am making a daily timer by using boost io_service and deadline timer. The problem is when I want to delete my daily timer, my thread will disappear when it try to delete boost io_service.

main.cpp

int main()
{
    myDailyTimer* pTimer = new myDailyTimer;
    // do something
    delete pTimer;
    return 0;
}

I set break points in myDailyTimer.cpp::int i = 0; and myDailyTimer.cpp::int j = 0; and main::return 0; My main thread can reach int i = 0;, My timer thread cannot reach int j = 0;, My main thread cannot reach return 0;.

I found the my main thread will disappear when it try to delete boost::asio::io_service object. How to solve this problem? Am I using boost::asio::io_service in a wrong way?

myDailyTimer.h

class myDailyTimerInterface
{
public:
    myDailyTimerInterface(){}
    ~myDailyTimerInterface(){}

    virtual void TimerCallback(int nTimerID) = 0;
};
class myDailyTimer :
    public myThread
{
public:
    boost::asio::io_service m_IO;
    boost::asio::deadline_timer * m_pTimer;
    tm m_tmSpecificTime;
    std::string m_strSpecificTime;
    int m_nTimerID;
    myDailyTimerInterface* m_pParent;
public:
    myDailyTimer();
    ~myDailyTimer();

    void SetTime(tm strIN, int nID); // msec
    void TimerCallback();

    //Override
    void ThreadMain();
protected:
    std::string MakeStringSpecificTime();
    void AddOneDay();
};


myDailyTimer.cpp

myDailyTimer::myDailyTimer()
{
    m_pTimer = 0;
    m_strSpecificTime = "";
}

myDailyTimer::~myDailyTimer()
{
    EndThread();
    if (m_pTimer != 0)
    {
        m_pTimer->cancel();
        delete m_pTimer;
    }
    m_IO.stop();
    m_IO.~io_service();
    int i = 0;
    i++;
}

void myDailyTimer::SetTime(tm tmIN, int nID) // msec
{
    if (m_pTimer != 0)
    {
        m_pTimer->cancel();
        delete m_pTimer;
    }
    m_tmSpecificTime = tmIN;
    m_strSpecificTime = MakeStringSpecificTime();
    m_nTimerID = nID;
    m_pTimer = new boost::asio::deadline_timer(m_IO, boost::posix_time::time_from_string(m_strSpecificTime));
    m_pTimer->async_wait(boost::bind(&myDailyTimer::TimerCallback, this));

    myThread::Start();
}

std::string myDailyTimer::MakeStringSpecificTime()
{
    time_t localTime;
    localTime = mktime(&m_tmSpecificTime); // time is GMT local
    struct tm * ptm = gmtime(&localTime); // convert time to GMT +0
    char veccNextTime[64];
    memset(veccNextTime, 0, sizeof(veccNextTime));
    sprintf(veccNextTime, "%d-%02d-%02d %02d:%02d:%02d.000",
        ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday,
        ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    std::string strTemp(veccNextTime);
    return strTemp;
}

void myDailyTimer::AddOneDay()
{
    m_tmSpecificTime.tm_mday += 1;
    mktime(&m_tmSpecificTime); /* normalize result */
}

void myDailyTimer::TimerCallback()
{
    if (m_pParent != 0)
        m_pParent->TimerCallback(m_nTimerID);
    //m_timer->expires_from_now(boost::posix_time::milliseconds(m_nTimerDuration));
    AddOneDay();
    m_strSpecificTime = MakeStringSpecificTime();
    m_pTimer->expires_at(boost::posix_time::time_from_string(m_strSpecificTime));
    m_pTimer->async_wait(boost::bind(&myDailyTimer::TimerCallback, this));
}

//Override
void myDailyTimer::ThreadMain()
{
    while (!IsEndThread())
        m_IO.run();
    int j = 0;
    j++;
}


Community
  • 1
  • 1
sflee
  • 1,659
  • 5
  • 32
  • 63
  • 2
    `m_IO.~io_service();` -- That's a code smell if I've ever seen one, especially in this context. The question you refer to specifically mentions that the user has no control over the code in question, so they had to resort to such shenanigans (which you didn't implement correctly anyway, as you haven't recreated the object). The question also contains a comment with the sensible recommendation to allocate the `io_service` dynamically, if you need to have finer control over its lifetime. However this is not relevant here, since the problem is with the rest of the code, not deleting `io_service`. – Dan Mašek Oct 16 '16 at 17:04
  • Thx for your reply, if I want to implement a daily timer with the help of `deadline_timer`, what should I do? – sflee Oct 17 '16 at 00:52
  • 1
    I have an answer in the works, but give me some time to put it together. – Dan Mašek Oct 17 '16 at 00:56
  • Instead of using `asio::io_service` directly, use `boost::optional`, initialize it in the constructor and then instead of calling `m_IO.~io_service` call `m_IO.reset()`. I don't see you'r EndThread function anywhere, so I'm just going to note that you must make sure that the thread is `joined` and every asio IO object (such as the m_pTimer) is destroyed before you destroy the `io_service`. – Peter Jankuliak Oct 21 '16 at 09:04

1 Answers1

2

As Dan Mašek mentioned, explicitly calling the destructor isn't a good pattern here. The standard way to stop an io_service is to stop every "work" that is pending and then wait for io_service::run function to return. Also, to prevent the io_service::run function from returning prematurely, it is a good idea to create an instance of io_service::work object.

Hope you'll be able to modify this example to your use case:

namespace asio = boost::asio;

class MyTimer {
    using Clock = std::chrono::steady_clock;

  public:
    MyTimer(Clock::duration duration) 
      : _work(_ios)
      , _timer(_ios)
      , _thread([this] { _ios.run(); })
    {
      _ios.post([this, duration] { start(duration); });
    }

    ~MyTimer() {
      _ios.post([this] { stop(); });
      _thread.join();
    }

  private:
    void start(Clock::duration duration) {
      _timer.expires_from_now(duration);
      _timer.async_wait([this](boost::system::error_code) {
          // NOTE: Be careful here as this is run from inside
          //       the thread.
          if (!_work) {
            // Already stopped.
            std::cout << "Stopped" << std::endl;
            return;
          }
          std::cout << "Timer fired" << std::endl;
        });
    }

    void stop() {
      _work.reset();
      _timer.cancel();
    }

  private:
    asio::io_service _ios;
    boost::optional<asio::io_service::work> _work;
    asio::steady_timer _timer;
    std::thread _thread;
};

int main() {
  auto* my_timer = new MyTimer(std::chrono::seconds(1));
  delete my_timer;
  return 0;
}
Peter Jankuliak
  • 3,464
  • 1
  • 29
  • 40