TDengine Code Learning: Timer Implementation

Keywords: Linux

Timer Implementation

Code introduction

Learning TDengine ttimer.c Code in.
First, look at the data structures used.

#define MSECONDS_PER_TICK 5

typedef struct _tmr_ctrl_t {
  void *          signature;
  pthread_mutex_t mutex;            /* mutex to protect critical resource */
  int             resolution;       /* resolution in mseconds */
  int             numOfPeriods;     /* total number of periods */
  int64_t         periodsFromStart; /* count number of periods since start */
  pthread_t       thread;           /* timer thread ID */
  tmr_list_t *    tmrList;
  mpool_h         poolHandle;
  char            label[12];
  int             maxNumOfTmrs;
  int             numOfTmrs;
  int             ticks;
  int             maxTicks;
  int             tmrCtrlId;
} tmr_ctrl_t;

tmr_ctrl_t is a timer control class. Setting the common attributes of such timers is mainly scheduling granularity and numOfPeriods.

  • signature: signature is used to determine whether the timer control class is created.
  • resolution: The scheduling granularity of the timer, which indicates how many milliseconds will be queried once, in millisecond.
  • numOfPeriods: Number of time slices, with resolution for each time slice.
  • PeriododsFromStart: The current time slice, used to obtain the current expiration time slice index, in the creation of a new timer to calculate the timeout time.
  • tmrList: Create an array of type tmr_list_t and size numOfPeriods.
  • poolHandle: Memory pool, which is used to get a new timer class tmr_obj_t memory block.
  • Ticks: The number of ticks that have passed in the current time slice. A tick time is set to MSECONDS_PER_TICK 5 milliseconds by default.
  • maxTicks: The value resolution / MSECONDS_PER_TICK indicates that a time slice takes several tick times.
typedef struct {
  tmr_obj_t *head;
  int        count;
} tmr_list_t;

In tmr_list_t, head is a pointer to the list header, count stores the number of items in the list, and the item type in the list is tmr_obj_t.

typedef struct _tmr_obj {
  void *param1;
  void (*fp)(void *, void *);
  tmr_h               timerId;
  short               cycle;
  struct _tmr_obj *   prev;
  struct _tmr_obj *   next;
  int                 index;
  struct _tmr_ctrl_t *pCtrl;
} tmr_obj_t;

tmr_obj_t is the timer class. Each time a timer is created, the index in the tmrList array is calculated according to the set timeout time, and then added to the linked list corresponding to the tmrList[index].

  • fp: Function pointer invoked when the timer expires
  • param1: Parameters of the calling function when the timer expires
  • cycle: It means how many miles to go before it expires. This will be explained later.
  • prev, next: Implementing a pointer to a two-way linked list
  • index: index in the tmrList array

Timer scheduling

Every tick interval, the timer's scheduling thread calls the following taosTimerLoopFunc function, traverses all the timer control classes, determines whether the timer control class has been created according to the signature value, and when "pCtrl - > ticks >= pCtrl - > maxTicks" indicates that the current time slice has expired, it needs to call tao. The sTmrProcessList function handles timers on the timeslice list.

#define maxNumOfTmrCtrl 512
tmr_ctrl_t tmrCtrl[maxNumOfTmrCtrl];

void *taosTimerLoopFunc(int signo) {
  tmr_ctrl_t *pCtrl;
  int         count = 0;

  for (int i = 1; i < maxNumOfTmrCtrl; ++i) {
    pCtrl = tmrCtrl + i;
    if (pCtrl->signature) {
      count++;
      pCtrl->ticks++;
      if (pCtrl->ticks >= pCtrl->maxTicks) {
        taosTmrProcessList(pCtrl);
        pCtrl->ticks = 0;
      }
      if (count >= numOfTmrCtrl) break;
    }
  }

  return NULL;
}

Firstly, the index of the current time slice is calculated as "pCtrl - > periods FromStart% > pCtrl - > numOfPeriods", and the linked list pCtrl - > tmrList [index] is obtained, then the linked list is traversed:

  • If the cycle value of the current node is > 0, it means that the timer will not expire until the next round, which will only reduce the cycle value by one. Because when the timer inserts the linked list, it connects from small to Dalian according to the cycle value, so the node cycle value behind the node must be greater than 0, and the cycle value will also be reduced by one.
  • If the current node's cycle value is 0, which means that the timer is expired, the timer node is deleted from the list and memory is released.

Finally, add the periodsFromStart value to the next time slice.
Here's a little question: Why not add the value of periodsFromStart to the remainder of numOfPeriods directly, and define periodsFromStart as int64?

void taosTmrProcessList(tmr_ctrl_t *pCtrl) {
  unsigned int index;
  tmr_list_t * pList;
  tmr_obj_t *  pObj, *header;

  pthread_mutex_lock(&pCtrl->mutex);
  index = pCtrl->periodsFromStart % pCtrl->numOfPeriods;
  pList = &pCtrl->tmrList[index];

  while (1) {
    header = pList->head;
    if (header == NULL) break;

    if (header->cycle > 0) {
      pObj = header;
      while (pObj) {
        pObj->cycle--;
        pObj = pObj->next;
      }
      break;
    }

    pCtrl->numOfTmrs--;
    tmrTrace("%s %p, timer expired, fp:%p, tmr_h:%p, index:%d, total:%d", pCtrl->label, header->param1, header->fp,
             header, index, pCtrl->numOfTmrs);

    pList->head = header->next;
    if (header->next) header->next->prev = NULL;
    pList->count--;
    header->timerId = NULL;
    //ignore timer function processing code
    tmrMemPoolFree(pCtrl->poolHandle, (char *)header);
  }
  pCtrl->periodsFromStart++;
  pthread_mutex_unlock(&pCtrl->mutex);
}

Examples of timer structure

If the timer's timeout time is mseconds, the corresponding cycle and index formulas are as follows:

period = mseconds / resolution
cycle = period / numOfPeriods
index = (period + periodsFromStart) % numOfPeriods

Assuming that the resolution of the timer control class is set to 100 milliseconds and the number of time slices numOfPeriods is set to 5, the time to traverse a round of time slices is 500 milliseconds. At initialization, the value of periods FromStart is 0 and the value of ticks is 0.
At this point, we create three timers

  • When the timeout time of timer 1 is 100 milliseconds, the period value is 1, the cycle value is 0, and the index value is 1.
  • Timer 2 timeout time mseconds is 600 milliseconds, then period value is 6, cycle value is 1, index value is 1
  • When the timeout time of timer 3 is 800 milliseconds, the period value is 8, the cycle value is 1, and the index value is 3.

The timer status at this time is shown in Figure 1.
The periodsFromStart value is 0
You can see that both the timer 1 and 2 are in the linked list pointed to by the tmrList[1], and are from small to large according to the cycle value.
Timer 3 is in the linked list pointed to by tmrList[3]
Figure 1
Time passes 200 milliseconds. At this time, the timer control class just passes two time slices. The timer status is shown in Figure 2.
The periodsFromStart value is 2
The list with time slice index 1 has been processed. Timer 1 has timed out and deleted from the list. The cycle value of timer 2 is reduced by one.
Figure 2

Timer Accuracy Error

When creating a timer, the calculation formula only considers the current time slice index periods FromStart, but does not consider the ticks value that the current time slice has passed, nor does it take this into account in the scheduling thread, so the actual timeout time of the timer will have an error of (0, resolution) range.
For example, if the time control class resolution is 100 milliseconds, the current periods FromStart value is 0, and the timeout time of a new timer mseconds is 100 milliseconds, then the time slice is in place.
index is 1.

  • If the ticks value at this time is 0, the timer will be timed out after 200 milliseconds, with resolution (100 milliseconds) error.
  • If the ticks value at this time is 10 (that is, the time slice has passed 50 milliseconds), the timer will be timed out after 150 milliseconds, with an error of 50 milliseconds.

Test results

Write a test for yourself as usual. There are limits in the actual code, with a minimum number of time slices numOfPeriods of 10.

http timer ctrl is initialized, tmrCtrlId:0
malloc pObj:0x78c180
http 0x7fff072545f4, timer is reset, fp:0x401a4b, tmr_h:0x78c180, cycle:0, index:1, total:1 numOfFree:14
malloc pObj:0x78c1c0
http 0x7fff072545f8, timer is reset, fp:0x401a7f, tmr_h:0x78c1c0, cycle:1, index:1, total:2 numOfFree:13
malloc pObj:0x78c200
http 0x7fff072545fc, timer is reset, fp:0x401a7f, tmr_h:0x78c200, cycle:1, index:8, total:3 numOfFree:12
http 0x7fff072545f4, timer expired, fp:0x401a4b, tmr_h:0x78c180, index:1, total:2
timerFunc1 id[1]
http 0x7fff072545f8, timer expired, fp:0x401a7f, tmr_h:0x78c1c0, index:1, total:1
timerFunc2 id[2]
http 0x7fff072545fc, timer expired, fp:0x401a7f, tmr_h:0x78c200, index:8, total:0
timerFunc2 id[3]

Complete test code

Run the test under linux.
Compilation requires the - lpthread s option. Examples are as follows:
gcc -o timer timer.c -lpthread

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>


#define mpool_h void *
typedef void *tmr_h;

typedef struct {
	int				numOfFree;
	int 			first;
	int 			numOfBlock;
	int 			blockSize;
	int *			freeList;
	char * 			pool;
	pthread_mutex_t	mutex;
} pool_t;

#define maxNumOfTmrCtrl  	16
#define MSECONDS_PER_TICK 	5

typedef struct _tmr_obj
{
	void *param1;
	void (*fp)(void *, void *);
	tmr_h				timerId;
	short				cycle;
	struct _tmr_obj * 	prev;
	struct _tmr_obj * 	next;
	int                 index;
	struct _tmr_ctrl_t *pCtrl;
} tmr_obj_t;

typedef struct
{
	tmr_obj_t * head;
	int			count;
} tmr_list_t;

typedef struct _tmr_ctrl_t
{
	void * 			signature;
	pthread_mutex_t mutex;
	int				resolution;
	int 			numOfPeriods;
	unsigned int	periodsFromStart;
	tmr_list_t *	tmrList;
	mpool_h			poolHandle;
	char            label[12];
	int 			maxNumOfTmrs;
	int 			numOfTmrs;
	int 			ticks;
	int				maxTicks;
} tmr_ctrl_t;


tmr_ctrl_t tmrCtrl[maxNumOfTmrCtrl];


mpool_h memPoolInit(int maxNum, int blockSize);
char * memPoolMalloc(mpool_h handle);
void memPoolFree(mpool_h handle, char *p);
void memPoolCleanup(mpool_h handle);


void tmrProcessList(tmr_ctrl_t *pCtrl)
{
	int index;
	tmr_list_t * pList;
	tmr_obj_t * pObj, *header;

	pthread_mutex_lock(&pCtrl->mutex);
	index = pCtrl->periodsFromStart % pCtrl->numOfPeriods;
	pList = &pCtrl->tmrList[index];
	while(1)
	{
		header = pList->head;
		if(header == NULL) break;

		if(header->cycle > 0)
		{
			pObj = header;
			while(pObj)
			{
				pObj->cycle--;
				pObj = pObj->next;
			}
			break;
		}

		pCtrl->numOfTmrs--;
		printf("%s %p, timer expired, fp:%p, tmr_h:%p, index:%d, total:%d\n", pCtrl->label, header->param1, header->fp,
					 header, index, pCtrl->numOfTmrs);

		pList->head = header->next;
		if(header->next) header->next->prev = NULL;
		pList->count--;
		header->timerId = NULL;

		if (header->fp)
      		(*(header->fp))(header->param1, header);
		
		memPoolFree(pCtrl->poolHandle, (char *)header);
	}
	
	pCtrl->periodsFromStart++;
	pthread_mutex_unlock(&pCtrl->mutex);
	//printf("%s tmrProcessList index[%d]\n", pCtrl->label, index);
}

void * timerLoopFunc(void)
{
	tmr_ctrl_t *pCtrl;
	int i = 0;

	for(i = 0; i < maxNumOfTmrCtrl; i++)
	{
		pCtrl = tmrCtrl + i;
		if(pCtrl->signature)
		{
			pCtrl->ticks++;
			if(pCtrl->ticks >= pCtrl->maxTicks)
			{
				tmrProcessList(pCtrl);
				pCtrl->ticks = 0;
			}
		}
	}
}

void * processAlarmSignal(void *tharg)
{
	sigset_t 		sigset;
	timer_t	 		timerId;
	int signo;

	sigemptyset(&sigset);
	sigaddset(&sigset, SIGALRM);
	sigprocmask(SIG_BLOCK, &sigset, NULL);

	struct itimerval new_value, old_value;
	new_value.it_value.tv_sec = 0;
	new_value.it_value.tv_usec = 1000 * MSECONDS_PER_TICK;
	new_value.it_interval.tv_sec = 0;
	new_value.it_interval.tv_usec = 1000 * MSECONDS_PER_TICK;
	setitimer(ITIMER_REAL, &new_value, &old_value);

	while(1)
	{
		if(sigwait(&sigset, &signo))
		{
	      	printf("Failed to wait signal: number %d", signo);
	      	continue;
	    }
		timerLoopFunc();
	}
	return NULL;
}

void tmrModuleInit(void)
{
	pthread_t		thread;
	pthread_attr_t	tattr;
	
	memset(tmrCtrl, 0, sizeof(tmrCtrl));

	pthread_attr_init(&tattr);
	pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
	if(pthread_create(&thread, &tattr, processAlarmSignal, NULL) != 0)
	{
		printf("failed to create timer thread");
    	return;
  	}

  	pthread_attr_destroy(&tattr);
}

void * tmrInit(int maxNumOfTmrs, int resolution, int longest, char * label)
{
	tmr_ctrl_t * pCtrl;
	int tmrCtrlId = 0;
	int i = 0;

	//tmrCtrlId = taosAllocateId(tmrIdPool);
	pCtrl = tmrCtrl + tmrCtrlId;

	memset(pCtrl, 0, sizeof(tmr_ctrl_t));
	strncpy(pCtrl->label, label, sizeof(pCtrl->label));
	pCtrl->maxNumOfTmrs = maxNumOfTmrs;

	if((pCtrl->poolHandle = memPoolInit(maxNumOfTmrs + 10,sizeof(tmr_obj_t))) == NULL)
	{
		printf("%s failed to allocate mem pool", label);
		memPoolCleanup(pCtrl->poolHandle);
		return NULL;
	}
	
	if(resolution < MSECONDS_PER_TICK) resolution = MSECONDS_PER_TICK;
	pCtrl->resolution = resolution;
	pCtrl->maxTicks = resolution / MSECONDS_PER_TICK;
	pCtrl->ticks = rand() / pCtrl->maxTicks;
	pCtrl->numOfPeriods = longest / resolution;
	if(pCtrl->numOfPeriods < 10) pCtrl->numOfPeriods = 10;

	pCtrl->tmrList = (tmr_list_t *)malloc(sizeof(tmr_list_t) * pCtrl->numOfPeriods);
	for(i = 0; i < pCtrl->numOfPeriods; i++)
	{
		pCtrl->tmrList[i].head = NULL;
		pCtrl->tmrList[i].count = 0;
	}

	pCtrl->signature = pCtrl;
	printf("%s timer ctrl is initialized, tmrCtrlId:%d\n", label, tmrCtrlId);
	return pCtrl;
}

void tmrReset(void (*fp)(void *, void*), int mseconds, void * param1, void * handle, tmr_h *pTmrId)
{
	tmr_obj_t *pObj, *cNode, *pNode;
	tmr_list_t * pList = NULL;
	int index, period;
	tmr_ctrl_t *pCtrl = (tmr_ctrl_t *)handle;

	if(handle == NULL || pTmrId == NULL) return;

	period = mseconds / pCtrl->resolution;
	if(pthread_mutex_lock(&pCtrl->mutex) != 0)
		printf("%s mutex lock failed, reason:%s", pCtrl->label, strerror(errno));

	pObj = (tmr_obj_t *)(*pTmrId);

	if(pObj && pObj->timerId == *pTmrId)
	{
		pList = &(pCtrl->tmrList[pObj->index]);
		if(pObj->prev)
			pObj->prev->next = pObj->next;
		else
			pList->head = pObj->next;

		if(pObj->next)
			pObj->next->prev = pObj->prev;

		pList->count--;
		pObj->timerId = NULL;
		pCtrl->numOfTmrs--;
		printf("reset pObj:%p\n", pObj);
	}
	else
	{
		pObj = (tmr_obj_t *)memPoolMalloc(pCtrl->poolHandle);
		*pTmrId = pObj;
		if(pObj == NULL)
		{
			printf("%s failed to allocate timer, max:%d allocated:%d", pCtrl->label, pCtrl->maxNumOfTmrs, pCtrl->numOfTmrs);
      		pthread_mutex_unlock(&pCtrl->mutex);
      		return;
		}
		printf("malloc pObj:%p\n", pObj);
	}

	pObj->cycle = period / pCtrl->numOfPeriods;
	pObj->param1 = param1;
	pObj->fp = fp;
	pObj->timerId = pObj;
	pObj->pCtrl = pCtrl;

	index = (period + pCtrl->periodsFromStart) % pCtrl->numOfPeriods;
	pList = &(pCtrl->tmrList[index]);
	pObj->index = index;
	cNode = pList->head;
	pNode = NULL;

	while(cNode != NULL)
	{
		if(cNode->cycle < pObj->cycle)
		{
			pNode = cNode;
			cNode = cNode->next;
		}
		else
			break;
	}

	pObj->next = cNode;
	pObj->prev = pNode;

	if(cNode != NULL)
		cNode->prev = pObj;

	if(pNode != NULL)
		pNode->next = pObj;
	else
		pList->head = pObj;

	pList->count++;
	pCtrl->numOfTmrs++;

	if (pthread_mutex_unlock(&pCtrl->mutex) != 0)
    	printf("%s mutex unlock failed, reason:%s", pCtrl->label, strerror(errno));

	printf("%s %p, timer is reset, fp:%p, tmr_h:%p, cycle:%d, index:%d, total:%d numOfFree:%d\n", pCtrl->label, param1, fp, pObj,
           pObj->cycle, index, pCtrl->numOfTmrs, ((pool_t *)pCtrl->poolHandle)->numOfFree);
	return;
}



mpool_h memPoolInit(int numOfBlock, int blockSize)
{
	int i = 0;
	pool_t * pool_p = NULL;

	if(numOfBlock <= 1 || blockSize <= 1)
	{
		printf("invalid parameter in memPoolInit\n");
		return NULL;
	}

	pool_p = (pool_t *)malloc(sizeof(pool_t));
	if(pool_p == NULL)
	{
		printf("mempool malloc failed\n");
		return NULL;
	}

	memset(pool_p, 0, sizeof(pool_t));

	pool_p->blockSize = blockSize;
	pool_p->numOfBlock = numOfBlock;
	pool_p->pool = (char *)malloc((size_t)(blockSize * numOfBlock));
	pool_p->freeList = (int *)malloc(sizeof(int) * (size_t)numOfBlock);

	if(pool_p->pool == NULL || pool_p->freeList == NULL)
	{
		printf("failed to allocate memory\n");
		free(pool_p->freeList);
		free(pool_p->pool);
		free(pool_p);
	}

	pthread_mutex_init(&(pool_p->mutex), NULL);

	for(i = 0; i < pool_p->numOfBlock; i++)
		pool_p->freeList[i] = i;

	pool_p->first = 0;
	pool_p->numOfFree= pool_p->numOfBlock;

	return (mpool_h)pool_p;
}

char * memPoolMalloc(mpool_h handle)
{
	char * pos = NULL;
	pool_t * pool_p = (pool_t *)handle;

	pthread_mutex_lock(&pool_p->mutex);

	if(pool_p->numOfFree <= 0)
	{
		printf("mempool: out of memory");
	}
	else
	{
		pos = pool_p->pool + pool_p->blockSize * (pool_p->freeList[pool_p->first]);
		pool_p->first = (pool_p->first + 1) % pool_p->numOfBlock;
		pool_p->numOfFree--;
	}

	pthread_mutex_unlock(&pool_p->mutex);
	if(pos != NULL) memset(pos, 0, (size_t)pool_p->blockSize);
	return pos;
}

void memPoolFree(mpool_h handle, char * pMem)
{
	int index = 0;
	pool_t * pool_p = (pool_t *)handle;

	if(pool_p == NULL || pMem == NULL) return;

	pthread_mutex_lock(&pool_p->mutex);

	index = (int)(pMem - pool_p->pool) % pool_p->blockSize;
	if(index != 0)
	{
		printf("invalid free address:%p\n", pMem);
	}
	else
	{
		index = (int)((pMem - pool_p->pool) / pool_p->blockSize);
		if(index < 0 || index >= pool_p->numOfBlock)
		{
			printf("mempool: error, invalid address:%p\n", pMem);
		}
		else
		{
			pool_p->freeList[(pool_p->first + pool_p->numOfFree) % pool_p->numOfBlock] = index;
			pool_p->numOfFree++;
			memset(pMem, 0, (size_t)pool_p->blockSize);
		}
	}
	
	pthread_mutex_unlock(&pool_p->mutex);
}

void memPoolCleanup(mpool_h handle)
{
	pool_t *pool_p = (pool_t *)handle;

	pthread_mutex_destroy(&pool_p->mutex);
	if(pool_p->pool) free(pool_p->pool);
	if(pool_p->freeList) free(pool_p->freeList);
}

void timerFunc1(void *param, void *tmrId) 
{
	int id = *(int *)param;
	printf("%s id[%d]\n", __func__, id);
}

void timerFunc2(void *param, void *tmrId) 
{
	int id = *(int *)param;
	printf("%s id[%d]\n", __func__, id);
}

void test()
{	
	void *timerHandle = tmrInit(5, 100, 1000, "http");
	void *timer1 = NULL, *timer2 = NULL, *timer3 = NULL;
	int id1 = 1, id2 = 2, id3 = 3;

	tmrReset(timerFunc1, 100, (void *)&id1, timerHandle, &timer1);

	tmrReset(timerFunc2, 1100, (void *)&id2, timerHandle, &timer2);

	tmrReset(timerFunc2, 1800, (void *)&id3, timerHandle, &timer3);
}

int main()
{
	sigset_t 		sigset;

	sigemptyset(&sigset);
	sigaddset(&sigset, SIGALRM);
	sigprocmask(SIG_BLOCK, &sigset, NULL);
	
	tmrModuleInit();
	test();
	while(1)
	{	
	}
}

Posted by ironman32 on Fri, 16 Aug 2019 05:56:55 -0700