Search code examples
c++boostintervals

How to call different methods with their own sample intervals inside a class using different a TimerInterval class working with io_context


I am trying to use the same idea suggested as answer of my question in this post. Thi goal is to implement a class that will call 3 different methods with their own intervals inside a start_trigger method.

The following sample code is compiled successfully, but it does not work logically since the life time of IntervalTimer objects are limited to the start_process() method rather than class lifetime.

So my solution to resolve this issue is to define 3 variables of IntervalTimer as a class member and then initialize them inside the start_processing method.

The start_process method is a public method and can be called through an object of MyTestClass.

Here is the code that will be compiled with no issue, but it does not repeatedly call three methods (method1, method2, and method3 with the provided intervals).

#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
#include <map>
#include <unordered_set>
#include <vector>

namespace asio = boost::asio;
using namespace std::chrono_literals;
using duration            = std::chrono::steady_clock::duration;
static constexpr auto now = std::chrono::steady_clock::now;
using boost::system::error_code;
using namespace std;




enum MethodTypes { FIRST = 0, SECOND = 1, THIRD = 2 };

using Callback = std::function<void()>;
//template<typename Callback>
struct IntervalTimer {
    IntervalTimer(asio::any_io_executor ex, duration interval, Callback callback)
        : interval_(interval)
        , cb_(std::move(callback))
        , tim_(ex, interval_) 
    {
        loop();
    }

  private:
    void loop() {
        while (tim_.expiry() <= now())
            tim_.expires_at(tim_.expiry() + interval_);

        tim_.async_wait([this](boost::system::error_code const& ec) {
            if (!ec) { // aborted or other failure
                cb_();
                loop();
            }
        });
    }

    duration           interval_;
    Callback           cb_;
    asio::steady_timer tim_;
};

class MyTestClass final {

public:
    MyTestClass(int transaction_id , boost::asio::io_context& io) :
        transaction_id_(transaction_id) ,io_context_(io) {
            //tt1(clone( IntervalTimer()));
        }

    void start_process (vector<int>ids, vector<bool> conditions, map <int, unordered_set<string> >& set_of_items);
    void stop_process();

private:
    void method1 (int id, const unordered_set<string>& set_of_buyers);
    void method2 (int id, const unordered_set<string>& set_of_sellers);
    void method3 (int id, const unordered_set<string>& set_of_units);
    void stats_cb(uint32_t tp_id, MethodTypes method_type, unordered_set<string>& set_of_values);
    int transaction_id_;
    boost::asio::io_context& io_context_;

    //IntervalTimer tt1;
    //IntervalTimer tt2;
    //IntervalTimer tt3;

};

void MyTestClass::start_process(vector<int>ids, vector<bool> conditions, map <int, unordered_set<string> >& set_of_items){
    
    if (conditions[0]) {
        auto     sample_interval = 10;
         //IntervalTimer tt1_tmp (io_context_.get_executor(), std::chrono::milliseconds(sample_interval), [&] {
         // stats_cb(ids[0], MethodTypes::FIRST, set_of_items[0]);
         //});
         //tt1 =std::move(tt1_tmp);
        IntervalTimer t1(io_context_.get_executor(), std::chrono::milliseconds(sample_interval), [&] {
        stats_cb(ids[0], MethodTypes::FIRST, set_of_items[0]);
        });
    }
        if (conditions[1]) {
        auto sample_interval = 20;
        IntervalTimer t2(io_context_.get_executor(), std::chrono::milliseconds(sample_interval), [&] {
            stats_cb(ids[1], MethodTypes::SECOND, set_of_items[1]);
        });
    } 
    if (conditions[1]) {
        auto  sample_interval = 30;
        IntervalTimer t3(io_context_.get_executor(), std::chrono::milliseconds(sample_interval), [&] {
             stats_cb(ids[2], MethodTypes::THIRD, set_of_items[3]);
        });
    } 
    io_context_.run();
}


void MyTestClass::stats_cb(uint32_t id, MethodTypes method_type, unordered_set<string>& set_of_values){

    switch (method_type) {
    case MethodTypes::FIRST:
         method1(id, set_of_values);
         break;
    case MethodTypes::SECOND:
        method2(id, set_of_values);
         break;
    case MethodTypes::THIRD:
        method3(id, set_of_values);
        break;
    default:
         cout<<"the provided  type is not supported"<< endl;
         return;
    }
}


void MyTestClass::method1 (int v, const unordered_set<string>& set_of_values){
     cout<<"method-1 is called and v ="<<v<<endl;
     for (auto e: set_of_values)
        cout<<e<< endl;
     cout<<"method-1 is done"<<endl;
}

void MyTestClass::method2 (int v, const unordered_set<string>& set_of_values){
    cout<<"method-2 is called and v ="<<v<<endl;
    for (auto e: set_of_values)
       cout<<e<< endl;
    cout<<"method-2 is done"<<endl;
}

void MyTestClass::method3 (int v, const unordered_set<string>& set_of_values){
    cout<<"method-3 is called and v ="<<v<<endl;
    for (auto e: set_of_values)
         cout<<e<< endl;
     cout<<"method-3 is done"<<endl;
}



int main() {
    boost::asio::io_context iocx;
    MyTestClass obj(100, iocx);
    vector<int> ids {100,200,300};
    vector<bool> conds {false,true,true};
    map <int, unordered_set<string> > mp;
    mp[0].insert("test1_0");
    mp[0].insert("test1_1");
    mp[0].insert("test1_2");       mp[1].insert("test2_0");
    mp[1].insert("test2_1");
    mp[1].insert("test2_2");       mp[2].insert("test3_0");
    mp[2].insert("test3_1");
    
    obj.start_process(ids, conds, mp);
    return 0;
}

However, when i uncomment the commented parts like the following definition inside the MyTestClass

//IntervalTimer tt1;

and the following one inside the start_process()

 //IntervalTimer tt1_tmp (io_context_.get_executor(), std::chrono::milliseconds(sample_interval), [&] {
 // stats_cb(ids[0], MethodTypes::FIRST, set_of_items[0]);
 //});
 //tt1 =std::move(tt1_tmp);

I get the following compilation errors:

prog.cc: In constructor 'MyTestClass::MyTestClass(int, boost::asio::io_context&)':
prog.cc:53:56: error: no matching function for call to 'IntervalTimer::IntervalTimer()'
   53 |         transaction_id_(transaction_id) ,io_context_(io) {
      |                                                        ^
prog.cc:23:5: note: candidate: 'IntervalTimer::IntervalTimer(boost::asio::any_io_executor, duration, Callback)'
   23 |     IntervalTimer(asio::any_io_executor ex, duration interval, Callback callback)
      |     ^~~~~~~~~~~~~
prog.cc:23:5: note:   candidate expects 3 arguments, 0 provided
prog.cc:22:8: note: candidate: 'IntervalTimer::IntervalTimer(IntervalTimer&&)'
   22 | struct IntervalTimer {
      |        ^~~~~~~~~~~~~
prog.cc:22:8: note:   candidate expects 1 argument, 0 provided

Is there any suggestion how to resolve this error?


Solution

  • You can do anything to dynamically construct the IntervalTimer objects. E.g. have a member like

    std::list<IntervalTimer> timers;
    

    Then, fixing some copy paste errors as well (conditions[1] is repeated and set_of_items[3] is always the empty set...):

    void MyTestClass::start_process(std::vector<int> ids, std::vector<bool> conditions,
                                    std::map<int, Set>& set_of_items) {
        auto ex = io_context_.get_executor();
        if (conditions[0])
            timers.emplace_back(ex, 10ms, [&] { stats_cb(ids[0], MethodTypes::FIRST, set_of_items[0]); });
        if (conditions[1])
            timers.emplace_back(ex, 20ms, [&] { stats_cb(ids[1], MethodTypes::SECOND, set_of_items[1]); });
        if (conditions[2])
            timers.emplace_back(ex, 30ms, [&] { stats_cb(ids[2], MethodTypes::THIRD, set_of_items[2]); });
    }
    

    There's a big problem that you capture the local ids by reference. This leads to Undefined Behavior. See below.

    In general, simplify your code by reducing duplication. There was a LOT of copy/paste mistakes. Also, never using namespace std.

    Always use good names for types as well as objects.

    When you keep having the same code switching on an index (0,1,2), consider grouping the behavior and data in a class. Don't add useless repetition/indirection like stats_cb, instead just write:

    void MyTestClass::start_process(std::vector<int> ids, std::vector<bool> conditions,
                                    std::map<int, Items>& set_of_items) {
        if (conditions[0])
            timers.emplace_back(ex_, 10ms, [this, id = ids[0], &items = set_of_items[0]] { method1(id, items); });
        if (conditions[1])
            timers.emplace_back(ex_, 20ms, [this, id = ids[1], &items = set_of_items[0]] { method2(id, items); });
        if (conditions[2])
            timers.emplace_back(ex_, 30ms, [this, id = ids[2], &items = set_of_items[0]] { method3(id, items); });
    }
    

    Do not call run inside start_process. MyTestClass doesn't own the io_context (and the name start_process would be wrong, because it also completes the process).

    Throwing in stop_process:

    void MyTestClass::stop_process() {
        for (auto& t : timers)
            t.cancel();
    }
    

    We get

    Live On Coliru

    #include <boost/asio.hpp>
    #include <iomanip>
    #include <iostream>
    #include <list>
    #include <map>
    #include <unordered_set>
    #include <vector>
    
    namespace asio = boost::asio;
    using namespace std::chrono_literals;
    using duration            = std::chrono::steady_clock::duration;
    static constexpr auto now = std::chrono::steady_clock::now;
    using boost::system::error_code;
    
    static auto const start = now();
    static inline void trace(auto const&... msg) {
        ((std::cout << (now() - start) / 1ms << "\tms\t") << ... << msg) << std::endl;
    }
    
    struct IntervalTimer {
        using Callback = std::function<void()>;
        IntervalTimer(asio::any_io_executor ex, duration interval, Callback callback)
            : interval_(interval)
            , cb_(std::move(callback))
            , tim_(ex, interval_) {
            loop();
        }
    
        void cancel() {
            post(tim_.get_executor(), [this] { tim_.cancel(); });
        }
    
      private:
        void loop() {
            while (tim_.expiry() <= now())
                tim_.expires_at(tim_.expiry() + interval_);
    
            tim_.async_wait([this](error_code const& ec) {
                if (!ec) { // aborted or other failure
                    cb_();
                    loop();
                }
            });
        }
    
        duration           interval_;
        Callback           cb_;
        asio::steady_timer tim_;
    };
    
    using Items = std::unordered_set<std::string>;
    class MyTestClass final {
      public:
        MyTestClass(int transaction_id, asio::any_io_executor ex) : transaction_id_(transaction_id), ex_(ex) {}
    
        void start_process(std::vector<int> ids, std::vector<bool> conditions,
                           std::map<int, std::unordered_set<std::string>>& set_of_items);
        void stop_process();
    
      private:
        void method1(int id, Items const& set_of_buyers);
        void method2(int id, Items const& set_of_sellers);
        void method3(int id, Items const& set_of_units);
        int  transaction_id_; // UNUSED
    
        asio::any_io_executor    ex_;
        std::list<IntervalTimer> timers;
    };
    
    void MyTestClass::start_process(std::vector<int> ids, std::vector<bool> conditions,
                                    std::map<int, Items>& set_of_items) {
        // for (int id = 0; id < 3; ++id)
        // timers.emplace_back(ex_, 10ms, [id, &items = set_of_items[id]] { method1(id, items); });
        if (conditions[0])
            timers.emplace_back(ex_, 1000ms, [this, id = ids[0], &items = set_of_items[0]] { method1(id, items); });
        if (conditions[1])
            timers.emplace_back(ex_, 2000ms, [this, id = ids[1], &items = set_of_items[0]] { method2(id, items); });
        if (conditions[2])
            timers.emplace_back(ex_, 3000ms, [this, id = ids[2], &items = set_of_items[0]] { method3(id, items); });
    }
    
    void MyTestClass::stop_process() {
        for (auto& t : timers)
            t.cancel();
    }
    
    void MyTestClass::method1(int v, Items const& set_of_values) {
        trace("method-1", " is called and v =" , v );
        std::cout << "\t\tmethod-1" << " iterating:";
        for (auto const& e : set_of_values)
            std::cout << " " << e;
        std::cout << std::endl;
        trace("method-1", " is done");
    }
    
    void MyTestClass::method2(int v, Items const& set_of_values) {
        trace("method-2", " is called and v =" , v );
        std::cout << "\t\tmethod-2" << " iterating:";
        for (auto const& e : set_of_values)
            std::cout << " " << e;
        std::cout << std::endl;
        trace("method-2", " is done");
    }
    
    void MyTestClass::method3(int v, std::unordered_set<std::string> const& set_of_values) {
        trace("method-3", " is called and v =" , v );
        std::cout << "\t\tmethod-3" << " iterating:";
        for (auto const& e : set_of_values)
            std::cout << " " << e;
        std::cout << std::endl;
        trace("method-3", " is done");
    }
    
    int main() {
        asio::io_context     ioc;
        MyTestClass          obj(100, ioc.get_executor());
        std::map<int, Items> mp = {{0, {"test1_0", "test1_1", "test1_2"}},
                                   {1, {"test2_0", "test2_1", "test2_2"}},
                                   {2, {"test3_0", "test3_1"}}};
    
        obj.start_process({100, 200, 300}, {false, true, true}, mp);
    
        ioc.run_for(8s);
        obj.stop_process();
        ioc.run(); // graceful shutdown
    }
    

    Printing

    2000    ms  method-2 is called and v =200
            method-2 iterating: test1_2 test1_1 test1_0
    2007    ms  method-2 is done
    3000    ms  method-3 is called and v =300
            method-3 iterating: test1_2 test1_1 test1_0
    3000    ms  method-3 is done
    4000    ms  method-2 is called and v =200
            method-2 iterating: test1_2 test1_1 test1_0
    4000    ms  method-2 is done
    6000    ms  method-2 is called and v =200
            method-2 iterating: test1_2 test1_1 test1_0
    6000    ms  method-2 is done
    6000    ms  method-3 is called and v =300
            method-3 iterating: test1_2 test1_1 test1_0
    6000    ms  method-3 is done
    8000    ms  method-2 is called and v =200
            method-2 iterating: test1_2 test1_1 test1_0
    8000    ms  method-2 is done
    

    Note that I made the delays much longer just so you can see the output. You can adjust them back to 10ms, 20ms, 30ms.

    enter image description here

    BONUS

    Always group objects and data. The repeated switching on indexes 0,1,2 indicates that something wants to be an entity:

    struct Task {
        using Func = std::function<void(int, Items const&)>;
    
        bool     condition = false;
        duration interval;
        Func     method;
    };
    

    If you squint a little you will see that MyTestClass really only adds complications at this point. It looks like a class that takes on too many responsibilities, ironically preventing the entities that need to exist from existing. How about this version:

    Live On Coliru

    #include <boost/asio.hpp>
    #include <iomanip>
    #include <iostream>
    #include <list>
    #include <map>
    #include <unordered_set>
    #include <vector>
    
    namespace asio = boost::asio;
    using namespace std::chrono_literals;
    using duration            = std::chrono::steady_clock::duration;
    static constexpr auto now = std::chrono::steady_clock::now;
    using boost::system::error_code;
    
    static auto const  start = now();
    static inline void trace(auto const&... msg) {
        ((std::cout << (now() - start) / 1ms << "\tms\t") << ... << msg) << std::endl;
    }
    
    struct RecurringTask {
        using Callback = std::function<void()>;
        RecurringTask(asio::any_io_executor ex, duration interval, Callback callback)
            : interval_(interval)
            , cb_(std::move(callback))
            , tim_(ex, interval_) {
            loop();
        }
    
        void cancel() {
            post(tim_.get_executor(), [this] { tim_.cancel(); });
        }
    
      private:
        void loop() {
            while (tim_.expiry() <= now())
                tim_.expires_at(tim_.expiry() + interval_);
    
            tim_.async_wait([this](error_code const& ec) {
                if (!ec) { // aborted or other failure
                    cb_();
                    loop();
                }
            });
        }
    
        duration           interval_;
        Callback           cb_;
        asio::steady_timer tim_;
    };
    
    using Items = std::unordered_set<std::string>;
    
    void method(std::string_view name, int v, Items const& set_of_values) {
        trace(name, " is called and v = ", v);
        std::cout << "\t\t" << name << " iterating:";
        for (auto const& e : set_of_values)
            std::cout << " " << e;
        std::cout << std::endl;
        trace(name, " is done");
    }
    
    int main() {
        asio::io_context ioc;
    
        int transaction_id_ = 100; // UNUSED
    
        Items buyers  = {"test1_0", "test1_1", "test1_2"};
        Items sellers = {"test2_0", "test2_1", "test2_2"};
        Items units   = {"test3_0", "test3_1"};
    
        std::list<RecurringTask> tasks;
     // tasks.emplace_back(ioc.get_executor(), 1000ms, bind(method, "buyers",  100, std::ref(buyers)));
        tasks.emplace_back(ioc.get_executor(), 2000ms, bind(method, "sellers", 200, std::ref(sellers)));
        tasks.emplace_back(ioc.get_executor(), 3000ms, bind(method, "units", 300, std::ref(units)));
    
        ioc.run_for(8s);
    }
    

    Printing

    2000    ms  sellers is called and v = 200
            sellers iterating: test2_2 test2_1 test2_0
    2016    ms  sellers is done
    3000    ms  units is called and v = 300
            units iterating: test3_1 test3_0
    3000    ms  units is done
    4000    ms  sellers is called and v = 200
            sellers iterating: test2_2 test2_1 test2_0
    4000    ms  sellers is done
    6000    ms  sellers is called and v = 200
            sellers iterating: test2_2 test2_1 test2_0
    6000    ms  sellers is done
    6000    ms  units is called and v = 300
            units iterating: test3_1 test3_0
    6000    ms  units is done
    8000    ms  sellers is called and v = 200
            sellers iterating: test2_2 test2_1 test2_0
    8006    ms  sellers is done