zoukankan      html  css  js  c++  java
  • Timeout watchdog using a standby thread

    http://codereview.stackexchange.com/questions/84697/timeout-watchdog-using-a-standby-thread

    he simple but generic timeout class to be used watching for network connections, user input, filesystem events, and is intended to have a very simple interface specific to only our use cases (i.e. no satisfy-all attitude).

    Intended steps to use:

    1. Construct
    2. Activate
    3. Potentially react to a timeout, deactivates itself
    4. Re-activate
    5. Destruct cleanly

    After triggering the alarm the guard is expected to be inactive until explicitly activated. The code expected to be reasonably tested (that is one of the issues).

    This posted code as a testable Visual Studio 2013 project lives on the GitHub.

    Besides a general feedback on the code quality - or if, please, please, please, you see a bug, I would love to hear about these areas:

    1. Destruction. Although I did my best to tell the guard thread to end, I am still concerned about having to join() in the destructor. Generally I love my destructors short and sweet for emergency landings - is it possible here? Is there an STL way to brutally kill that thread?

    2. Tests. Existing ones test for intended simple scenarios. I am not sure this is enough to claim that the code works as intended. Is it? I did not find a better way to test timing edge cases. Also, as tests are time-dependant, they occasionally spuriously fail when run on slow VMs. Or are they? Is it sufficient for code like this to know that if tests run somewhere consistently? If I increase timeouts the spurious fails go away, but that lengthens the overall project test run.

    Below are the current header, implementation, and tests files to save the GitHub trip.

    Header:

    #pragma once
    
    namespace utility
    {
        /**
            The `clock` alias is for easy switching to `steady_clock` once Microsoft fixes it
        */
        typedef std::chrono::system_clock clock;
    
        /**
            The `TimeoutGuard` class triggers the `alarm` callback from the `guard_thread`
            if `touch` was not called for at least the `timeout` duration.
    
            Because of the way the `guard_thread` sleeps, the actual detection may happen
            as late as after `timeout` + `naptime` duration. Hence it is possible that the alarm
            will not be called if the `TimeoutGuard` instance is touched within the
            'timeout` and `timeout` + `naptime` timeframe.
    
            If not provided, by default the `naptime` is same as `timeout`.
    
            The `TimeoutGuard` is not active after construction, whicn means, that the
            `guard_thread` will block until it is activated by calling the `watch` method.
    
            The `TimeoutGuard` class is not copyable and not moveable.
        */
        class TimeoutGuard
        {
        public:
            TimeoutGuard(
                clock::duration timeout,
                std::function<void( void )> alarm,
                clock::duration naptime
            );
    
            TimeoutGuard(
                clock::duration timeout,
                std::function<void( void )> alarm
            );
    
            ~TimeoutGuard();
    
            TimeoutGuard( const TimeoutGuard & ) = delete;
            TimeoutGuard & operator=(const TimeoutGuard & ) = delete;
    
            TimeoutGuard( TimeoutGuard && ) = delete;
            TimeoutGuard & operator=( TimeoutGuard && ) = delete;
    
            void watch();
            void touch();
    
        private:
    
            void guard();
    
            clock::duration timeout;
            clock::duration naptime;
            std::function<void( void )> alarm;
    
            std::atomic_bool idle;
            std::atomic_bool live;
    
            std::atomic<clock::time_point> touched;
    
            std::thread guard_thread;
            std::mutex guard_mutex;
            std::condition_variable wakeup;
        };
    }

    Here is the implementation:

    #include "stdafx.h"
    #include "TimeoutGuard.h"
    
    namespace utility
    {
        TimeoutGuard::TimeoutGuard(
            clock::duration timeout,
            std::function<void( void )> alarm,
            clock::duration naptime
        )
            : timeout( timeout )
            , alarm( alarm )
            , naptime( naptime )
        {
            idle.store( true );
            live.store( true );
    
            guard_thread = std::thread( std::bind( &TimeoutGuard::guard, this ) );
        }
    
        TimeoutGuard::TimeoutGuard(
            clock::duration timeout,
            std::function<void( void )> alarm
        )
        : TimeoutGuard( timeout, alarm, timeout )
        {};
    
        TimeoutGuard::~TimeoutGuard()
        {
            live.store( false );
            wakeup.notify_all();
            guard_thread.join();
        }
    
        void TimeoutGuard::guard()
        {
            while ( live.load() )
            {
                if ( idle.load() )
                {
                    // Sleep indefinitely until either told to become active or destruct
                    std::unique_lock<std::mutex> live_lock( guard_mutex );
                    wakeup.wait( live_lock, [this]() { return ! this->idle.load() || ! this->live.load(); } );
                };
    
                // quit the loop if destructing
                if ( ! live.load() ) break;
    
                // the actual timeout checking
                auto now = clock::now();
    
                if ( ( now - touched.load() ) > timeout )
                {
                    idle.store( true );
                    alarm();
                    continue; // skip waiting for next timeout
                }
    
                {
                    // sleep until next timeout check or destruction
                    std::unique_lock<std::mutex> live_lock( guard_mutex );
                    wakeup.wait_for( live_lock, naptime, [this](){ return ! this->live.load(); } );
                }
            };
        }
    
        void TimeoutGuard::watch()
        {
            touch();
            idle.store( false );
            wakeup.notify_all();
        }
    
        void TimeoutGuard::touch()
        {
            touched.store( clock::now() );
        }
    }

    And, finally, existing tests:

    #include "stdafx.h"
    #include "CppUnitTest.h"
    
    #include "TimeoutGuard.h"
    
    using namespace Microsoft::VisualStudio::CppUnitTestFramework;
    
    namespace utility
    {       
        TEST_CLASS( TimeoutGuardTest )
        {
        public:
    
            bool triggered = false;
    
            void shoud_trigger()
            {
                triggered = true;
            }
    
            TEST_METHOD( TimeoutGuardExpiration )
            {
                TimeoutGuard tg{
                    std::chrono::milliseconds{ 5 },
                    std::bind( &TimeoutGuardTest::shoud_trigger, this )
                };
    
                triggered = false;
                tg.watch();
                std::this_thread::sleep_for( std::chrono::milliseconds{ 10 } );
                Assert::IsTrue( triggered, L"Failed to call the timeout alarm on the first run", LINE_INFO() );
    
                triggered = false;
                tg.watch();
                std::this_thread::sleep_for( std::chrono::milliseconds{ 10 } );
                Assert::IsTrue( triggered, L"Failed to call the timeout alarm on the second run", LINE_INFO() );
            }
    
    
            TEST_METHOD( TimeoutGuardNoAlarm )
            {
                TimeoutGuard tg{
                    std::chrono::milliseconds{ 5 },
                    std::bind( &TimeoutGuardTest::shoud_trigger, this )
                };
    
                triggered = false;
                tg.watch();
                std::this_thread::sleep_for( std::chrono::milliseconds{ 1 } );
                Assert::IsFalse( triggered, L"Wrongly called the timeout alarm on the first run", LINE_INFO() );
    
                triggered = false;
                tg.watch();
                for (auto i = 0; i < 10; ++i)
                {
                    std::this_thread::sleep_for( std::chrono::milliseconds{ 1 } );
                    tg.touch();
                }
                Assert::IsFalse( triggered, L"Wrongly called the timeout alarm on the second run", LINE_INFO() );
            }
        };
    }
  • 相关阅读:
    android--从手动存取->View Model->Live Data->Data Binding
    android--------解决Entities and POJOs must have a usable public constructor
    开课第一周周总结
    Pandas Series: sum()方法
    .Net Core/Framework之Nginx反向代理后获取客户端IP等数据探索
    readonly与disabled的区别
    html使用frame框架导航跳转至指定的节的用法
    HTML+Css让网页自动适应电脑手机屏幕
    仿Quora的免费问答网站程序
    WebGL 纹理颜色原理
  • 原文地址:https://www.cnblogs.com/kex1n/p/5151042.html
Copyright © 2011-2022 走看看