Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does boost asio function expires_from_now() cancel a deadline_timer?

When I try to get (not set!) the current expiry time using boost expires_from_now() is seems to actually cancel the timer, yet it actually runs as expected, but does finally not call the handler.

Or put in other words, when accessing a deadline_timer with expires_from_now() it will call the handler immediately and not call the handler when it expires.

Please consider the following code and corresponding output:

#include <boost/asio.hpp> 
#include <boost/thread.hpp> 
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>
#include <iostream> 

using namespace boost::posix_time;
using namespace std;

void handler1(const boost::system::error_code &ec) 
{ 
    if (ec == boost::asio::error::operation_aborted)
    {
        std::cout << microsec_clock::local_time() << " Handler1: Timer 1 was cancelled or retriggered." << std::endl; 
    }
    else
    {
        std::cout << microsec_clock::local_time() << " Handler1: expired." << std::endl; 
    }
} 

boost::asio::io_service io_service1; 

class Mytimer {
public:
    Mytimer();
    void startTimer();
    void runTimerThread();
    bool isRunning();
private:
    bool m_isRunning;
    boost::asio::deadline_timer* m_pTimer;
    boost::thread* m_pThread;
};

Mytimer::Mytimer()
  : m_pTimer(NULL),
    m_pThread(NULL)
{
    m_pTimer = new boost::asio::deadline_timer(io_service1); 
    m_pTimer->async_wait(handler1); 
}

void Mytimer::runTimerThread()
{
    io_service1.run();
}

void Mytimer::startTimer()
{
    m_pThread = new boost::thread(&Mytimer::runTimerThread, this);
    m_pTimer->expires_from_now(boost::posix_time::seconds(10));
}

bool Mytimer::isRunning()
{

    time_duration td = m_pTimer->expires_from_now();
    if (td.total_seconds() > 0)
    {
        return true;
    }
    return false;
}

int main() 
{ 
    time_facet *facet = new time_facet("%Y%m%d %H:%M:%S%f");
    std::cout.imbue(std::locale(std::cout.getloc(), facet));

    cout << microsec_clock::local_time() << " before timer construction" << endl;
    Mytimer timer;
    cout << microsec_clock::local_time() << " before startTimer()" << endl;
    timer.startTimer();
    cout << microsec_clock::local_time() << " IsRunning: " << timer.isRunning() << endl;
    for (int i = 0; i <= 20; i++)
    {
        sleep(1);
        cout << microsec_clock::local_time() << " IsRunning: " << timer.isRunning() << endl;
    }
} 

20120412 22:41:45689235 before timer construction
20120412 22:41:45689514 before startTimer()
20120412 22:41:45689619 IsRunning: 1
20120412 22:41:45689693 Handler1: Timer 1 was cancelled or retriggered.
20120412 22:41:46689792 IsRunning: 1
20120412 22:41:47689994 IsRunning: 1
20120412 22:41:48690179 IsRunning: 1
20120412 22:41:49690375 IsRunning: 1
20120412 22:41:50690530 IsRunning: 1
20120412 22:41:51690712 IsRunning: 1
20120412 22:41:52690884 IsRunning: 1
20120412 22:41:53691069 IsRunning: 1
20120412 22:41:54691236 IsRunning: 0
20120412 22:41:55691428 IsRunning: 0
20120412 22:41:56691614 IsRunning: 0
20120412 22:41:57691810 IsRunning: 0
20120412 22:41:58692001 IsRunning: 0
20120412 22:41:59692193 IsRunning: 0
20120412 22:42:00692364 IsRunning: 0
20120412 22:42:01692542 IsRunning: 0
20120412 22:42:02692706 IsRunning: 0
20120412 22:42:03692886 IsRunning: 0
20120412 22:42:04693071 IsRunning: 0
20120412 22:42:05693267 IsRunning: 0
20120412 22:42:06693465 IsRunning: 0

like image 558
Michael Hilbert Avatar asked Feb 02 '23 06:02

Michael Hilbert


2 Answers

When I try to get (not set!) the current expiry time using boost expires_from_now() is seems to actually cancel the timer, yet it actually runs as expected, but does finally not call the handler.

this assumption is incorrect. When you create your deadline_timer

m_pTimer = new boost::asio::deadline_timer(io_service1);
m_pTimer->async_wait(handler1);

you've told it to expire immediately. Then, when you start your io_service

m_pThread = new boost::thread(&Mytimer::runTimerThread, this);

you subsequently invoke

m_pTimer->expires_from_now(boost::posix_time::seconds(10));

which as the documentation describes clearly, this will cancel any outstanding handlers, emphasis added is mine

This function sets the expiry time. Any pending asynchronous wait operations will be cancelled. The handler for each cancelled operation will be invoked with the boost::asio::error::operation_aborted error code.

To resolve this, change your Mytimer::startTimer() method as follows

--- timer4.cc.orig  2012-04-13 11:18:47.000000000 -0500
+++ timer4.cc   2012-04-13 11:16:54.000000000 -0500
@@ -1,4 +1,3 @@
-
 #include <boost/asio.hpp> 
 #include <boost/thread.hpp> 
 #include <boost/date_time/posix_time/posix_time.hpp>
@@ -39,7 +38,6 @@
     m_pThread(NULL)
 {
     m_pTimer = new boost::asio::deadline_timer(io_service1); 
-    m_pTimer->async_wait(handler1); 
 }

 void Mytimer::runTimerThread()
@@ -49,8 +47,9 @@

 void Mytimer::startTimer()
 {
-    m_pThread = new boost::thread(&Mytimer::runTimerThread, this);
     m_pTimer->expires_from_now(boost::posix_time::seconds(10));
+    m_pTimer->async_wait(handler1); 
+    m_pThread = new boost::thread(&Mytimer::runTimerThread, this);
 }

 bool Mytimer::isRunning()

which results in the expected behavior

mbp:stackoverflow samm$ ./a.out
20120413 11:17:22922529 before timer construction
20120413 11:17:22923155 before startTimer()
20120413 11:17:22923530 IsRunning: 1
20120413 11:17:23924702 IsRunning: 1
20120413 11:17:24925971 IsRunning: 1
20120413 11:17:25927320 IsRunning: 1
20120413 11:17:26928715 IsRunning: 1
20120413 11:17:27929969 IsRunning: 1
20120413 11:17:28930601 IsRunning: 1
20120413 11:17:29931843 IsRunning: 1
20120413 11:17:30933098 IsRunning: 1
20120413 11:17:31934366 IsRunning: 0
20120413 11:17:32923594 Handler1: expired.
20120413 11:17:32934692 IsRunning: 0
20120413 11:17:33935922 IsRunning: 0
20120413 11:17:34936638 IsRunning: 0
20120413 11:17:35937905 IsRunning: 0
20120413 11:17:36939133 IsRunning: 0
20120413 11:17:37940407 IsRunning: 0
20120413 11:17:38941043 IsRunning: 0
20120413 11:17:39942319 IsRunning: 0
20120413 11:17:40942662 IsRunning: 0
20120413 11:17:41943989 IsRunning: 0
20120413 11:17:42945284 IsRunning: 0
20120413 11:17:43946555 IsRunning: 0
mbp:stackoverflow samm$ 
like image 106
Sam Miller Avatar answered Feb 05 '23 15:02

Sam Miller


The following code now has the correct deadline_timer initialization, and has a private bool flag, which allows to determine, if the timer is currently running. It also has a member local handler.

class Mytimer {
public:
    Mytimer();
    void startTimer(int timeDelaySecs);
    void cancelTimer();
    void runTimerThread();
    bool isRunning();
    void timerHandler(const boost::system::error_code& ec); 
private:
    bool m_isRunning;
    boost::asio::deadline_timer* m_pTimer;
    boost::thread* m_pThread;
};

Mytimer::Mytimer()
    : m_pTimer(NULL),
      m_pThread(NULL)
{
    m_pTimer = new boost::asio::deadline_timer(io_service1); 
}

void Mytimer::runTimerThread()
{
    io_service1.run();
}

void Mytimer::startTimer(int timeDelaySecs)
{
    m_pTimer->expires_from_now(boost::posix_time::seconds(timeDelaySecs));
    m_pTimer->async_wait(boost::bind(&Mytimer::timerHandler, this, _1)); 
    m_pThread = new boost::thread(&Mytimer::runTimerThread, this);
    m_isRunning = true;
}

bool Mytimer::isRunning()
{
    return m_isRunning;
}

void Mytimer::timerHandler(const boost::system::error_code& ec) 
{ 
    if (ec == boost::asio::error::operation_aborted)
    {
        std::cout << microsec_clock::local_time() << " Handler1: Timer 1 was cancelled or retriggered." << std::endl; 
    }
    else
    {
        std::cout << microsec_clock::local_time() << " Handler1: expired." << std::endl; 
    }
    m_isRunning = false;
} 
like image 31
Michael Hilbert Avatar answered Feb 05 '23 15:02

Michael Hilbert