muduo network library net source code line by line analysis_ one

Posted by timmy0320 on Mon, 17 Jan 2022 14:57:42 +0100

1 muduo::net::EchoServer class reference

Collaboration diagram of EchoServer:

[legend]

Public member function
EchoServer (EventLoop *loop, const InetAddress &listenAddr)
voidstart ()
Private member function
voidonConnection (const TcpConnectionPtr &conn)
voidonMessage (const TcpConnectionPtr &conn, Buffer *buf, Timestamp time)
Private property
EventLoop *loop_
TcpServerserver_

  • When an event arrives, it will call channel::handleEvent() and Channel::handleEventWithGuard(Timestamp receiveTime) for processing. The channel class is responsible for event registration and response encapsulation
void Channel::handleEventWithGuard(Timestamp receiveTime) {
eventHandling_ = true; // Place status in processing event
    if ((revents_ & POLLHUP) && !(revents_ & POLLIN)) // If it is half closed and there is no data to read
    {
        if (logHup_)
        {
            LOG_WARN << "Channel::handle_event() POLLHUP";
        }
        if (closeCallback_)
            closeCallback_(); // Closed callback function
    }
    if (revents_ & (POLLIN | POLLPRI | POLLRDHUP)) // If it is a read event, you need to pass in an additional time stamp object
    {
        if (readCallback_)
            readCallback_(receiveTime);
    }
    if (revents_ & POLLOUT) // If it is a write event
    {
        if (writeCallback_) // And the event has been registered
            writeCallback_();
    }
    eventHandling_ = false;
}

  • Update and register channel void Channel::update() call void EventLoop::updateChannel(Channel *channel)
void EventLoop::updateChannel(Channel *channel){
	assert(channel->ownerLoop() == this); // Assert the loop object to which the channel belongs
    assertInLoopThread();	 			  // Is loop in the open event loop
    poller_->updateChannel(channel);	  // 
}

Calling void pollpolller:: updateChannel (channel * channel) is a pure virtual function that overrides the updateChannel of the parent class

void PollPoller::fillActiveChannels(int numEvents,ChannelList *activeChannels) const
{
	for (PollFdList::const_iterator pfd = pollfds_.begin();pfd != pollfds_.end() && numEvents > 0; ++pfd) 
	// Traverse vector container STD:: vector < struct pollfd >
	{
		if (pfd->revents > 0)
		{
			--numEvents;
			ChannelMap::const_iterator ch = channels_.find(pfd->fd); // Find channels through file descriptors
			assert(ch != channels_.end());	  	// Asserts whether the channel can be found
			Channel *channel = ch->second;	  	// Get event type
			assert(channel->fd() == pfd->fd); 	// Assign a value to the file descriptor of the channel
			channel->set_revents(pfd->revents); // Set event type

			activeChannels->push_back(channel);
		}
	}
}

Mechanism for registering EPollPoller events

class EPollPoller : public Poller
{
    public:
    void update(int operation, Channel *channel);
    private:
	typedef std::vector<struct epoll_event> EventList;
	typedef std::map<int, Channel *> ChannelMap; // Registration of file descriptors and pipeline events
   
    EventList events_;
	ChannelMap channels_;
};

The difference between the method I wrote and this method is that ChannelMap uses the object-based callback function registration method. I use the virtual function method. In terms of performance and efficiency, it is better to use the callback function method, but it is difficult to understand from the code reading.

  • EventLoop constructor
EventLoop::EventLoop()
    : looping_(false),
      quit_(false),
      eventHandling_(false),
      threadId_(CurrentThread::tid()),
      poller_(Poller::newDefaultPoller(this)), // poller objects will be built, and different io multiplexing objects will be generated according to environment variables
      currentActiveChannel_(NULL){}
Poller* Poller::newDefaultPoller(EventLoop* loop)
{
  if (::getenv("MUDUO_USE_POLL"))
  {
    return new PollPoller(loop); // Generate poll
  }
  else
  {
    return new EPollPoller(loop);// Generate epoll
  }
}

It mainly depends on the encapsulation of epoll

If there is a return ready event fd, press the return collection and the pipeline responsible for event registration into the function fillactivchannels

Not clear enough. Last pseudo code

	int epfd = epoll_create(1);
    
    epollAddFd(sfd, epfd);
    epollAddFd(exitPipe[0],epfd); // Put the pipeline reader into the listening queue
    struct epoll_event evs[2]; // Collection of events
	int newFd = 0;
    int sums = 0;
    while(1){ // Turn on uninterrupted waiting polling - 1, that is, do not set timeout
        sums = epoll_wait(epfd, evs, 2, -1);
        for(int i = 0; i < sums; ++i)
        {
            if(evs[i].data.fd == sfd) // evs[i].data.fd is to monitor the returned ready file descriptor and compare it with what needs attention
            {
                newFd = accept(sfd, NULL, NULL);
                printf("The connection was established successfully\n");
            }
        }
    }   

struct epoll_event evs[2] is equivalent to & (* (events_. Begin()))

sfd is equivalent to ChannelList *activeChannels

Timestamp EPollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
	int numEvents = ::epoll_wait(epollfd_, & (*(events_.begin() ) ),static_cast<int>(events_.size()),timeoutMs);
	Timestamp now(Timestamp::now());
	if (numEvents > 0)
	{
		
		fillActiveChannels(numEvents, activeChannels); // In this function
		if (implicit_cast<size_t>(numEvents) == events_.size()) // If the vector has reached its capacity limit, double its capacity
		{
			events_.resize(events_.size() * 2);
		}
	}
	else if (numEvents == 0)
	{
		LOG_TRACE << " nothing happended";
	}
	else
	{
		LOG_SYSERR << "EPollPoller::poll()";
	}
	return now;
}

Fillactivchannels function

void EPollPoller::fillActiveChannels(int numEvents,ChannelList *activeChannels) const
{
	assert(implicit_cast<size_t>(numEvents) <= events_.size()); // The ready event fd returned by the assertion cannot be out of order
	for (unsigned long i = 0; i < static_cast<unsigned>(numEvents); ++i) // Circular scan ready file descriptor
	{
		Channel *channel = static_cast<Channel *>(events_[i].data.ptr);  // Return the registered event list. ptr is some key information that can be attached to the epoll red black tree. You also need to determine which events are returned
#ifndef NDEBUG 
		int fd = channel->fd();
		ChannelMap::const_iterator it = channels_.find(fd);
		assert(it != channels_.end());
		assert(it->second == channel);
#endif
		channel->set_revents(static_cast<int>(events_[i].events)); 	// Set the events registered for each pipeline
		activeChannels->push_back(channel); 						// Add pipeline events to the pipeline collection
	}
}

Then it is used in this way in the call, and the encapsulation is very complex

while (!quit_)
    {
        activeChannels_.clear();
        pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_); // The events returned by epoll should be saved in this list
        if (Logger::logLevel() <= Logger::TRACE)
        {
            printActiveChannels();
        }
        eventHandling_ = true;
        for (ChannelList::iterator it = activeChannels_.begin();
             it != activeChannels_.end(); ++it)
        {
            currentActiveChannel_ = *it;
            currentActiveChannel_->handleEvent(pollReturnTime_);
        }
        currentActiveChannel_ = NULL;
        eventHandling_ = false; // Termination event handling ID

    }
  • updateChannel function, used to update and add events
void EPollPoller::updateChannel(Channel *channel)
{
	Poller::assertInLoopThread(); // Whether the assertion is in the io thread
	LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events(); // Output file descriptor and event type
	const int index = channel->index();
	if (index == kNew || index == kDeleted)
	{
		// a new one, add with EPOLL_CTL_ADD
		int fd = channel->fd();
		if (index == kNew)
		{
			assert(channels_.find(fd) == channels_.end()); // Assert that the file descriptor has not been registered in the pipeline event
			channels_[fd] = channel;        // Registering events is similar to the method I wrote myself ["cmd command"] = "task_data * parent class pointer"
		}
		else // index == kDeleted 
		{
			assert(channels_.find(fd) != channels_.end());
			assert(channels_[fd] == channel); // Assert again whether the registration has been successful
		}
		channel->set_index(kAdded);      // Set the current state of this channel to the added state
		update(EPOLL_CTL_ADD, channel);	 // epoll_ctr_add to the red black tree
	}
	else
	{
        // ...... EPOLL_CTL_MOD
    }
}
  • update adds the event to the red black tree
void EPollPoller::update(int operation, Channel *channel)
{
	struct epoll_event event;
	bzero(&event, sizeof event);
	event.events = static_cast<uint32_t>(channel->events()); // epoll focuses on reading and writing, timeout, and various events, signalfd and pipeline fd
	event.data.ptr = channel;   // Take the channel as the carried data and return the channel. Then you will know which channel generated the event
	int fd = channel->fd();     // File descriptor
    // Register on red black tree
	if (::epoll_ctl(epollfd_, operation, fd, &event) < 0)
	{
		if (operation == EPOLL_CTL_DEL)
		{
			LOG_SYSERR << "epoll_ctl op=" << operation << " fd=" << fd;
		}
		else
		{
			LOG_SYSFATAL << "epoll_ctl op=" << operation << " fd=" << fd;
		}
	}
}
  • Delete event attention on red black tree
void EventLoop::removeChannel(Channel *channel)
{
    assert(channel->ownerLoop() == this);
    assertInLoopThread();
    if (eventHandling_)
    {
        assert(currentActiveChannel_ == channel ||
               std::find(activeChannels_.begin(), activeChannels_.end(), channel) == activeChannels_.end());
    }
    poller_->removeChannel(channel);
}
// Called by removeChannel overridden by poller
void EPollPoller::removeChannel(Channel *channel)
{
	Poller::assertInLoopThread();
	int fd = channel->fd();
	LOG_TRACE << "fd = " << fd;
	assert(channels_.find(fd) != channels_.end());
	assert(channels_[fd] == channel);
	assert(channel->isNoneEvent());
	int index = channel->index();
	assert(index == kAdded || index == kDeleted); // Asserts whether to add or remove identities
	size_t n = channels_.erase(fd);
	(void)n;
	assert(n == 1);

	if (index == kAdded)
	{
		update(EPOLL_CTL_DEL, channel);
	}
	channel->set_index(kNew);
}

2 Realization of timer

Timerld,Timer,TimerQueue

The latter two classes are internal implementation details

muduo::net::Timer class reference or Muduo / base / timestamp CC operation

#include <Timer.h>
Public member function
Timer (const TimerCallback &cb, Timestamp when, double interval)
voidrun () const
Timestampexpiration () const
boolrepeat () const
int64_tsequence () const
voidrestart (Timestamp now)
Static Public member function
static int64_tnumCreated ()
Private property
const TimerCallbackcallback_ Timer activated callback function
Timestampexpiration_ Next time interval
const doubleinterval_ Timeout interval, if it is a one-time setting, 0
const boolrepeat_ Repeat
const int64_tsequence_ Timer serial number
Static Private property
static AtomicInt64s_numCreated_ Timer count
#ifdef __GXX_EXPERIMENTAL_CXX0X__
        Timer(TimerCallback&& cb, Timestamp when, double interval)
                : callback_(cb),
                  expiration_(when),
                  interval_(interval),
                  repeat_(interval > 0.0),
                  sequence_(s_numCreated_.incrementAndGet()) // Timer serial number = timer count + 1, counting starts from 0
                      // incrementAndGet is an atomic operation of one plus one
        { }
void run() const
{
    callback_(); // Execute callback function
}
// Reset Timer 
void Timer::restart(Timestamp now)
{
  if (repeat_) // Recalculate the next timeout
  {
    expiration_ = addTime(now, interval_); // Add the current time + time interval to get the next time
    // This object has only one 64 bit integer, so it's too lazy to pass by reference. Directly put the value into an 8-byte register
  }
  else
  {
    expiration_ = Timestamp::invalid(); // If it is not a repeated timer, it means illegal time
  }
}

In terms of user operation, the timer is not called directly, but in EventLoop

  1. runAt runs the timer at a certain time
  2. runAfter runs the timer after a period of time
  3. runEvery runs the timer at regular intervals
  4. Cancel cancel timer

TimerQueue internally maintains a timer list. Timer only operates on timing logic and does not directly call timer related functions. It is a high-level Abstract encapsulation

muduo::net::TimerQueue class reference

#include <TimerQueue.h>

Inherited from noncopyable

Collaboration diagram of muduo::net::TimerQueue:

[legend]

Public member function

Public member function
TimerQueue (EventLoop *loop
~TimerQueue ()
TimerIdaddTimer (const TimerCallback &cb, Timestamp when, double interval)
voidcancel (TimerId timerId)
Private type
typedef std::pair< Timestamp, Timer * >Entry
typedef std::set< Entry >TimerList
typedef std::pair< Timer *, int64_t >ActiveTimer
typedef std::set< ActiveTimer >ActiveTimerSet
Private member function
voidaddTimerInLoop (Timer *timer)
voidcancelInLoop (TimerId timerId)
voidhandleRead ()
std::vector< Entry >getExpired (Timestamp now) returns the list of timers that have timed out
voidreset (const std::vector< Entry > &expired, Timestamp now)
boolinsert (Timer *timer)
Private property
EventLoop *loop_ eventloop to which it belongs
const inttimerfd_ The file descriptor of the timer is saved
ChanneltimerfdChannel_ The channel of the timer. When the timer expires, a readable event is generated, and the callback function handleRead()
TimerListtimers_ List of timers sorted by expiration time
ActiveTimerSetactiveTimers List of timers sorted by object address
boolcallingExpiredTimers_ Whether to process the call processing timeout timer
ActiveTimerSetcancelingTimers_ List of cancelled timers

The TimerQueue value provides addTimer and cancel to add and cancel timers

typedef std::pair< Timestamp, Timer *> store timer object and address of timer

typedef std::set< Entry >Stored is a unique key value pair

typedef std::pair<Timestamp, Timer*> Entry; 
  typedef std::set<Entry> TimerList; // List of stored timers
  typedef std::pair<Timer*, int64_t> ActiveTimer; // Timer address and serial number
  typedef std::set<ActiveTimer> ActiveTimerSet;
// The storage is the same. Both sets are timer lists, but one is sorted by time and the other by address

It can be called by other threads, which means it can belong to different eventloop calls

You need to be able to quickly find expired timers according to the current time, and you also need to efficiently add and delete timers, so use binary tree

Add a timer to the two exposed interfaces addTimer

///Constructor
TimerQueue::TimerQueue(EventLoop *loop)
	: loop_(loop),
	  timerfd_(createTimerfd()), // Create a timer
	  timerfdChannel_(loop, timerfd_),
	  timers_(),
	  callingExpiredTimers_(false)
{
	timerfdChannel_.setReadCallback(boost::bind(&TimerQueue::handleRead, this));
	timerfdChannel_.enableReading();
}

​ timerfdChannel_.enableReading();

Add the file descriptor of the timer to the epoller class

->update->UpdateChannel->updateChannel

/**
 * @brief Add a timer
 * 
 * @param cb  Callback function of timer
 * @param when Timeout event
 * @param interval Interval time
 * @return TimerId 
 */
TimerId TimerQueue::addTimer(const TimerCallback &cb, 
							 Timestamp when,
							 double interval)
{ 
	Timer *timer = new Timer(cb, when, interval); // It is thread safe to construct timer objects and register callback functions
	/* Original writing, thread safety
	loop_->runInLoop(
		boost::bind(&TimerQueue::addTimerInLoop, this, timer));
	return TimerId(timer, timer->sequence());
	*/
	addTimerInLoop(timer);
	return TimerId(timer,timer->sequence());
}

Timerfd in constructor_ (createtimerfd()) create a timer file descriptor for listening

1. Introduction to timerfd

timerfd yes Linux A timer interface provided for user programs.
This interface is based on the file descriptor, and the timeout notification is performed through the readable events of the file descriptor, so it can be used in the application scenario of select/poll/epoll.

man 2 timerfd_create

These system calls create and operate on a timer that delivers timer expiration notifications via a file descriptor. They provide an alternative to the use of setitimer(2) or timer_create(2), with the advantage
that the file descriptor may be monitored by select(2), poll(2), and epoll(7).

// Create timer file descriptor
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <atomic>

#define handle_error(msg)   \
    do                      \
    {                       \
        perror(msg);        \
        exit(EXIT_FAILURE); \
    } while (0)
using namespace std;
atomic<int> fd_;
static void print_elapsed_time()
{
    static struct timespec start;
    struct timespec curr;
    static int first_call = 1;
    int secs, nsecs;

    if (first_call)
    {
        first_call = 0;
        if (clock_gettime(CLOCK_MONOTONIC, &start) == -1)
            handle_error("clock_gettime");
    }

    if (clock_gettime(CLOCK_MONOTONIC, &curr) == -1)
        handle_error("clock_gettime");

    secs = curr.tv_sec - start.tv_sec;
    nsecs = curr.tv_nsec - start.tv_nsec;
    if (nsecs < 0)
    {
        secs--;
        nsecs += 1000000000;
    }
    printf("%d.%03d: ", secs, (nsecs + 500000) / 1000000);
}
void printids(const char *s)
{
    uint64_t exp;
    pid_t pid;
    pthread_t tid;
    pid = getpid();
    tid = pthread_self();
    int fd = fd_.load();
    read(fd_.load(),&exp,0);
    cout<<"exp = "<<(unsigned long long)exp<<endl;
    printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,
           (unsigned int)tid, (unsigned int)tid);
}
void *thr_fn(void *arg)
{
    
    printids("new thread: ");
    return NULL;
}

int main(int argc, char *argv[])
{
    itimerspec new_value;
    int max_exp, fd;
    struct timespec now;
    uint64_t exp, tot_exp;
    ssize_t s;

    if ((argc != 2) && (argc != 4))
    {
        fprintf(stderr, "%s init-secs [interval-secs max-exp]\n",
                argv[0]);
        exit(EXIT_FAILURE);
    }

    if (clock_gettime(CLOCK_REALTIME, &now) == -1)
        handle_error("clock_gettime");

    new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]); // The initial expiration time of the timer is seconds
    new_value.it_value.tv_nsec = now.tv_nsec;           // Nanosecond unit
    if (argc == 2)
    {
        new_value.it_interval.tv_sec = 0; // Periodic interval
        max_exp = 1;
    }
    else
    {
        new_value.it_interval.tv_sec = atoi(argv[2]); // Periodic interval of timer
        max_exp = atoi(argv[3]);        
    }
    new_value.it_interval.tv_nsec = 0;
    int t = timerfd_create(CLOCK_REALTIME, 0);
    
    fd_.exchange(t);

    fd = timerfd_create(CLOCK_REALTIME, 0); // Create timer file descriptor
    if (fd_.load() == -1)
        handle_error("timerfd_create");

    if (timerfd_settime(fd_.load(), TFD_TIMER_ABSTIME, &new_value, NULL) == -1) // Start timer file descriptor
        handle_error("timerfd_settime");
    int err;
    pthread_t ntid;
   
    print_elapsed_time(); // Prints the value of the current clock
    printf("timer started\n");

    for (tot_exp = 0; tot_exp < max_exp;)
    {
        cout << "Waiting" << endl;
        err = pthread_create(&ntid, NULL, thr_fn, NULL);
        s = read(fd_.load(), &exp, sizeof(uint64_t)); // The blocking state is not released until the file descriptor is ready
        if (s != sizeof(uint64_t))
            handle_error("read");

        tot_exp += exp;
        print_elapsed_time();
        printf("read: %llu; total=%llu\n",
               (unsigned long long)exp,
               (unsigned long long)tot_exp);
    }

    exit(EXIT_SUCCESS);
}
$g++ test_timer.cc -lpthread -std=c++11
$./a.out 2 5 2 
# Timer test case
2 create timer and set non blocking mode
int createTimerfd()
			{
				int timerfd = ::timerfd_create(CLOCK_MONOTONIC,
											   TFD_NONBLOCK | TFD_CLOEXEC); // Create a timer, non blocking mode
				if (timerfd < 0)
				{
					LOG_SYSFATAL << "Failed in timerfd_create";
				}
				return timerfd;
			}
3 insert timer

The interface exposes addTimer to the outside, and the real work is the insert function. The specific implementation is not exposed to the user

void TimerQueue::addTimerInLoop(Timer *timer)
{
	loop_->assertInLoopThread();
	bool earliestChanged = insert(timer); // When inserting a timer, its expiration time may affect the first expired timer and change the sequence
	if (earliestChanged) // Is it necessary to change the sequence and reset the timeout of the timer
	{
		resetTimerfd(timerfd_, timer->expiration());
	}
}
1. Insertion implementation of internal package
bool TimerQueue::insert(Timer *timer)
{
	loop_->assertInLoopThread();
	assert(timers_.size() == activeTimers_.size()); // Assert whether the two lists are the same size
	bool earliestChanged = false;					// Does the expiration time need to change the flag bit
	Timestamp when = timer->expiration();			// Take out the expiration time of the timer to be added
	TimerList::iterator it = timers_.begin();		// For the earliest timer, set is arranged from small to large by default
	if (it == timers_.end() || when < it->first)	// If the expiration time of the newly inserted timer is less than the minimum time of the timer in the container or the container is empty
	{
		earliestChanged = true;						// Set flag to 1
	}
	{
		std::pair<TimerList::iterator, bool> result = timers_.insert(Entry(when, timer)); // Insert the new timer into two containers respectively
		assert(result.second);
		(void)result; // Is it written to cancel wall
	}
	{
		std::pair<ActiveTimerSet::iterator, bool> result = activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
		assert(result.second);
		(void)result;
	}
	assert(timers_.size() == activeTimers_.size());
	return earliestChanged;
}
2 the timeout needs to be reset during insertion
void resetTimerfd(int timerfd, Timestamp expiration) // Take out the timeout of the current new insertion
			{
				struct itimerspec newValue{};
				struct itimerspec oldValue{}; // It is slightly changed here. If the list is initialized to null, there is no need to call memset or bzero
				newValue.it_value = howMuchTimeFromNow(expiration); // Is to convert the class of muduo/base into a type that can be used for time
				int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue); // Set the new timeout
				if (ret){ LOG_SYSERR << "timerfd_settime()";}
			}
3. The time class and timestamp need to be converted during reset
struct timespec howMuchTimeFromNow(Timestamp when)
			{
				int64_t microseconds = when.microSecondsSinceEpoch() - Timestamp::now().microSecondsSinceEpoch(); // Get incoming timestamp - current timestamp
				if (microseconds < 100)
				{
					microseconds = 100; // Accurate to 100
				}
				struct timespec ts;
				ts.tv_sec = static_cast<time_t>(
					microseconds / Timestamp::kMicroSecondsPerSecond); // Divide by 100W and convert to seconds
				ts.tv_nsec = static_cast<long>(
					(microseconds % Timestamp::kMicroSecondsPerSecond) * 1000); // Convert to nanosecond units
				return ts;
			}

3 timer callback event

The event callback function has been set in TimerQueue::TimerQueue(EventLoop *loop) through setReadCallback: TimerQueue::handleRead passes in the passed in eventloop and timer file descriptor

void TimerQueue::handleRead()
{
	loop_->assertInLoopThread();
	Timestamp now(Timestamp::now()); // Get current time
	readTimerfd(timerfd_, now);		 // Process ready file descriptor

	std::vector<Entry> expired = getExpired(now); // Get the list of all timers at this time, that is, the timer whose timeout is ready

	callingExpiredTimers_ = true; // Processing timer expired
	cancelingTimers_.clear();	  // Clear the timer that has been cancelled

	for (std::vector<Entry>::iterator it = expired.begin();it != expired.end(); ++it)
	{
		it->second->run(); // Loop calls the callback function processed by the timer
	}
	callingExpiredTimers_ = false; // Processing completed

	reset(expired, now); // It is not a one-time timer and needs to be restarted
}
1 processing ready timer file descriptor
void readTimerfd(int timerfd, Timestamp now)
			{
				uint64_t howmany;
				ssize_t n = ::read(timerfd, &howmany, sizeof howmany);
				LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString();
				if (n != sizeof howmany)
				{
					LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8";
				}
			}
2 get all timers at this time, that is, the timers that have timed out

getExpired (Timestamp now) returns the list of timers that have timed out

std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{
	assert(timers_.size() == activeTimers_.size());
	std::vector<Entry> expired;
	Entry sentry(now, reinterpret_cast<Timer *>(UINTPTR_MAX));// UINTPTR_MAX set maximum address
	// It is possible to prevent. now is as big as Timer

	TimerList::iterator end = timers_.lower_bound(sentry);
	// lower_bound binary search returns the iterator with the first value > = sentry, and returns the first iterator position greater than the current time slice

	assert(end == timers_.end() || now < end->first); // Assert that the timer of the current time slice is the largest and runs behind the whole iterator
													  // Or the current time is less than the first returned position greater than the current time
													  // If now == *end(), uintptr is fired_ Max role, sentry value
	std::copy(timers_.begin(), end, back_inserter(expired));
	// Push the interval of the first unexpired timer from begin() - >_ Copy the method of back () to the expired list
	timers_.erase(timers_.begin(), end); // Remove the expired timer

	for (std::vector<Entry>::iterator it = expired.begin();it != expired.end(); ++it)
	{
		ActiveTimer timer(it->second, it->second->sequence());
		size_t n = activeTimers_.erase(timer); // Remove expiration timer
		assert(n == 1);
		(void)n; 
	}

	assert(timers_.size() == activeTimers_.size()); // Asserts whether the two containers are consistent
	return expired;
}

queue only focuses on the earliest timer time slice

3 application examples
///Explore whether the timer reset condition is the same as the source code analysis
void prints(const char *msg)
{
	printf("==========================\n,%s\n",msg);
}
int main()
{
	printTid();
	sleep(1);
	{
		EventLoop loop;
		g_loop = &loop;

		print("main");
		loop.runAfter(2,boost::bind(prints,"Hit me"));// The registered callback function and timer are 2 seconds and 1 second respectively
		loop.runAfter(1,boost::bind(prints,"Ha ha ha"));

		loop.loop(); // Turn on event polling
		print("main loop exits");
	}
}

Output results

Because the timer of 1 second will cause the timer of the previous 2 seconds to no longer be the minimum expiration time

msg 1642152525.200913 main
Insert a timer
Timeout when the timer needs to be reset
Insert a timer
Timeout when the timer needs to be reset
20220114 09:28:45.200953Z 19142 TRACE loop EventLoop 0x7FFD2E1BA050 start looping - EventLoop.cc:70
20220114 09:28:46.200961Z 19142 TRACE poll 1 events happended - EPollPoller.cc:65
20220114 09:28:46.201379Z 19142 TRACE printActiveChannels {4: IN } - EventLoop.cc:163

4 restart the non disposable timer
void TimerQueue::reset(const std::vector<Entry> &expired, Timestamp now)
{
	Timestamp nextExpire;

	for (std::vector<Entry>::const_iterator it = expired.begin();
		 it != expired.end(); ++it)
	{
		ActiveTimer timer(it->second, it->second->sequence());
		// If the timer is repeated and not cancelled, restart the timer
		if (it->second->repeat() && cancelingTimers_.find(timer) == cancelingTimers_.end())
		{
			it->second->restart(now);
			insert(it->second);
		}
		else
		{
			// A one-time timer or a timer that has been cancelled cannot be reset, so delete the timer
			delete it->second; // FIXME: no delete please
		}
	}

	if (!timers_.empty())
	{
		// Gets the timer timeout for the earliest expiration
		nextExpire = timers_.begin()->second->expiration();
	}
	if (nextExpire.valid())
	{
		// Reset timer timeout (timerfd_settime)
		resetTimerfd(timerfd_, nextExpire);
	}
}
5 cancel timer

TimerId is a visible class used to cancel the timer

class TimerId : public muduo::copyable
{
 public:
  TimerId()
    : timer_(NULL),
      sequence_(0)
  {
  }

  TimerId(Timer* timer, int64_t seq)
    : timer_(timer),
      sequence_(seq)
  {
  }
  friend class TimerQueue;
 private:
  Timer* timer_; // Address of timer
  int64_t sequence_;// Serial number of timer
};
}
}
void TimerQueue::cancelInLoop(TimerId timerId)
{
	loop_->assertInLoopThread();
	assert(timers_.size() == activeTimers_.size());
	ActiveTimer timer(timerId.timer_, timerId.sequence_);

	ActiveTimerSet::iterator it = activeTimers_.find(timer); // Find out whether the timer to be cancelled is in the normal list
	if (it != activeTimers_.end()) // If you find it
	{
		size_t n = timers_.erase(Entry(it->first->expiration(), it->first));
		assert(n == 1);
		(void)n;
		delete it->first; // FIXME: no delete please
		activeTimers_.erase(it); // Both lists need to be removed
	}
	else if (callingExpiredTimers_) // If it is not in the list, it indicates that it has been removed
	{
		cancelingTimers_.insert(timer); // Insert the timer into the cancel list
	}
	assert(timers_.size() == activeTimers_.size());
}

pyable
{
public:
TimerId()
: timer_(NULL),
sequence_(0)
{
}

TimerId(Timer* timer, int64_t seq) timer_(timer),
sequence_(seq)
{
}
friend class TimerQueue;
private:
Timer* timer_; // Address of timer
int64_t sequence_;// Serial number of timer
};
}
}
```cpp
void TimerQueue::cancelInLoop(TimerId timerId)
{
	loop_->assertInLoopThread();
	assert(timers_.size() == activeTimers_.size());
	ActiveTimer timer(timerId.timer_, timerId.sequence_);

	ActiveTimerSet::iterator it = activeTimers_.find(timer); // Find out whether the timer to be cancelled is in the normal list
	if (it != activeTimers_.end()) // If you find it
	{
		size_t n = timers_.erase(Entry(it->first->expiration(), it->first));
		assert(n == 1);
		(void)n;
		delete it->first; // FIXME: no delete please
		activeTimers_.erase(it); // Both lists need to be removed
	}
	else if (callingExpiredTimers_) // If it is not in the list, it indicates that it has been removed
	{
		cancelingTimers_.insert(timer); // Insert the timer into the cancel list
	}
	assert(timers_.size() == activeTimers_.size());
}

Topics: C++ Linux network Multithreading server