[source code reading] - Sylar server framework: timer module

Keywords: C++ Linux server

Timer module overview

   sylar implements the timer function based on epoll. Because the accuracy of epoll is milliseconds, the accuracy of its timer is also milliseconds. The timer has the design based on time wheel and time heap. In sylar, it adopts the design of minimum heap. For the specific explanation of timer, please refer to my previous reading notes:
[reading] Linux high performance server programming - Chapter 11.
  timers usually contain at least two members: timeout and task callback functions. If you use a linked list as a container to connect all timers in series, each timer also includes a pointer member pointing to the next timer (one-way linked list). The timer design based on time stack takes the timeout value of the timer with the smallest timeout time among all timers as the heart beat interval. In this way, once the heart beat function tick is called, the timer with the minimum timeout time must expire, so as to process the timer. Repeat in turn to achieve accurate timing.
   in sylar, all timers are sorted according to the absolute time point of timeout (system time + timeout time). Each time, take out the time point closest to the current time, calculate the waiting time for timeout, and then wait. When the timeout time expires, click to obtain the current absolute value, and all timers in the minimum heap of the timer that are less than this time point will be called back.

Design of sylar timer

   although the timer is also an important part of the whole system, such as IO scheduling, Hook, judging timeout, etc., the timer module is used, but the overall implementation of the timer by sylar is relatively simple.
   sylar defines two classes in the timer module: timer class and TimerManager timer management class. Timer timer class includes timer absolute timeout, callback function, corresponding management class pointer and some reset, refresh and cancel methods. The TimerManager timer management class manages timers and implements the minimum heap structure through std::set (the elements in the set are sorted).

Timer class

// timer
class Timer : public std::enable_shared_from_this<Timer> {
friend class TimerManager;

public:
    typedef std::shared_ptr<Timer> ptr;

    // Cancel timer
    bool cancel();
    // Refresh timer execution time
    bool refesh();
    // Reset timer time
    bool reset(uint64_t ms, bool from_now);

private:
    // Constructor
    Timer(uint64_t ms, std::function<void()> cb, bool recurring, TimerManager* manager);
    // next: timestamp of execution
    Timer(uint64_t next);

private:
    // Cycle timer
    bool m_recurring = false;
    // Execution cycle
    uint64_t m_ms = 0;
    // Precise execution time
    uint64_t m_next = 0;
    // Callback function
    std::function<void()> m_cb;
    // Timer manager
    TimerManager* m_manager = nullptr;

private:
    // Timer comparison function
    struct Comparator {
        // Compare the size of the smart pointer of the timer (sorted by execution time)
        bool operator()(const Timer::ptr& lhs, const Timer::ptr& rhs) const;
    };
};

   the detailed time of the whole Timer class is relatively simple, mainly in the aspect of imitation functions. Because you are still a little unfamiliar with C + +, you need to learn it separately here. Its function is to compare two Timer class objects according to the absolute timeout.
   Functor, also known as Function Object, is a class that can exercise function functions. The syntax of the Functor is almost the same as that of our ordinary function calls, but as the class of the Functor, the operator() operator must be overloaded. Because calling an imitation function is actually calling the overloaded operator() operator through a class object.
   next, let's take a look at the specific implementation of the imitation function:

bool Timer::Comparator::operator()(const Timer::ptr& lhs, const Timer::ptr& rhs)const{
    if(!lhs && !rhs){
        return false;
    }
    if(!lhs){
        return true;
    }
    if(!rhs){
        return false;
    }
    if(lhs->m_next < rhs->m_next){
        return true;
    }
    if(rhs->m_next < lhs->m_next){
        return false;
    }
    return lhs.get() < rhs.get();
}

Timer management class

   the timer management class mainly maintains a timer set STD:: set < timer:: PTR, timer:: comparator > M_ timers; Of course, some related methods are also provided, such as notifying IOManager to update epoll_wait timeout, detect whether a teacher has occurred, create a timer, etc.

// Timer manager
class TimerManager{
friend class Timer;

public:
    typedef RWMutex RWMutexType;

    // Constructor
    TimerManager();
    // Destructor
    virtual ~TimerManager();

    // Add timer
    Timer::ptr addTimer(uint64_t ms, std::function<void()> cb, bool recurring = false);

    // Add condition timer
    Timer::ptr addConditionTimer(uint64_t ms, std::function<void()> cb,
                                std::weak_ptr<void> weak_cond, bool recurring = false);
    
    // Time interval to the last timer execution
    uint64_t getNextTimer();

    // Get the callback function list of the timer to be executed
    void listExpiredCb(std::vector<std::function<void()> >& cbs);

    // Is there a timer
    bool hasTimer();

protected:
    // This function is executed when a timer is inserted into the timer header
    virtual void onTimerInsertedAtFront() = 0;

    // Add timer to manager
    void addTimer(Timer::ptr val, RWMutexType::WriteLock& lock);

private:
    // Check whether the server time has been adjusted
    bool detectClockRollover(uint64_t now_ms);

private:
    // mutex
    RWMutexType m_mutex;
    // Timer set
    std::set<Timer::ptr, Timer::Comparator> m_timers;
    // Trigger onITimerInsertedAtFront
    bool m_tickled = false;
    // Last execution time
    uint64_t m_previouseTime = 0;
};

   the implementation of the Timer management class is also easy to read, but it is recommended to read it again in combination with the previous idle, because the initial IOManager does not combine the Timer module, but the subsequent sylar integrates the Timer module and IOManager through epoll_ On the one hand, wait detects whether an event is triggered, and on the other hand, it determines whether it is timeout. At the same time, sylar supports conditional timers and binds variables when creating timers. When the Timer is triggered, judge whether the variable is valid. If it is invalid, click the Timer to cancel the trigger.

summary

   I think the Timer module is easier to understand than the previous collaborative scheduling, mainly because I know the design of its Timer, and I will try to change it to the form of time wheel later.

Posted by rrijnders on Mon, 06 Dec 2021 22:32:47 -0800