Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 43 additions & 11 deletions src/glib/glib_timer.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015-2024 The NATS Authors
// Copyright 2015-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand All @@ -25,9 +25,32 @@ void nats_freeTimers(natsLib *lib)
static void
_insertTimer(natsTimer *t)
{
natsTimer *cur = nats_lib()->timers.timers;
natsTimer *prev = NULL;
natsLibTimers *timers = &(nats_lib()->timers);
natsTimer *tail = timers->tail;
natsTimer *cur = NULL;
natsTimer *prev = NULL;

// Can we insert as the last in the list?
if ((tail != NULL) && (tail->absoluteTime <= t->absoluteTime))
{
t->prev = tail;
t->next = NULL;
// If there was a previous tail, then link to `t`.
if (tail != NULL)
tail->next = t;
else
{
// There was no tail, so no head either.
timers->head = t;
}
// This is the new tail.
timers->tail = t;
// We are done
return;
}

// Find the spot. Start at the beginning.
cur = timers->head;
while ((cur != NULL) && (cur->absoluteTime <= t->absoluteTime))
{
prev = cur;
Expand All @@ -51,7 +74,9 @@ _insertTimer(natsTimer *t)
}

if (prev == NULL)
nats_lib()->timers.timers = t;
timers->head = t;
if (t->next == NULL)
timers->tail = t;
}

// Locks must be held before entering this function
Expand All @@ -69,9 +94,14 @@ _removeTimer(natsLib *lib, natsTimer *t)
t->prev->next = t->next;
if (t->next != NULL)
t->next->prev = t->prev;

if (t == lib->timers.timers)
lib->timers.timers = t->next;
else
{
// The timer had no "next", so it was the tail. Move back the tail.
lib->timers.tail = t->prev;
}
// If we removed the first, move the head.
if (t == lib->timers.head)
lib->timers.head = t->next;

t->prev = NULL;
t->next = NULL;
Expand Down Expand Up @@ -183,7 +213,7 @@ nats_getTimersCountInList(void)

natsMutex_Lock(lib->timers.lock);

t = lib->timers.timers;
t = lib->timers.head;
while (t != NULL)
{
count++;
Expand Down Expand Up @@ -211,7 +241,7 @@ void nats_timerThreadf(void *arg)
while (!(timers->shutdown))
{
// Take the first timer that needs to fire.
t = timers->timers;
t = timers->head;

if (t == NULL)
{
Expand Down Expand Up @@ -244,9 +274,11 @@ void nats_timerThreadf(void *arg)
natsMutex_Lock(t->mu);

// Remove timer from the list:
timers->timers = t->next;
timers->head = t->next;
if (t->next != NULL)
t->next->prev = NULL;
else
timers->tail = NULL;

t->prev = NULL;
t->next = NULL;
Expand Down Expand Up @@ -298,7 +330,7 @@ void nats_timerThreadf(void *arg)

// Process the timers that were left in the list (not stopped) when the
// library is shutdown.
while ((t = timers->timers) != NULL)
while ((t = timers->head) != NULL)
{
natsMutex_Lock(t->mu);

Expand Down
5 changes: 3 additions & 2 deletions src/glib/glibp.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2015-2024 The NATS Authors
// Copyright 2015-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
Expand Down Expand Up @@ -46,7 +46,8 @@ typedef struct __natsLibTimers
natsMutex *lock;
natsCondition *cond;
natsThread *thread;
natsTimer *timers;
natsTimer *head;
natsTimer *tail;
int count;
bool changed;
bool shutdown;
Expand Down
104 changes: 104 additions & 0 deletions test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -1237,9 +1237,20 @@ void test_natsTimer(void)
natsTimer *t = NULL;
struct threadArg tArg;
int refs;
int numTimers = 100000;
natsTimer **timers;
int i;
int64_t start = 0;
int64_t dur = 0;

test("Setup test: ");
s = _createDefaultThreadArgsForCbTests(&tArg);
if (s == NATS_OK)
{
timers = (natsTimer**) calloc(numTimers, sizeof(natsTimer*));
if (timers == NULL)
s = NATS_ERR;
}
testCond(s == NATS_OK);

tArg.control = 0;
Expand Down Expand Up @@ -1444,6 +1455,99 @@ void test_natsTimer(void)

_destroyDefaultThreadArgs(&tArg);

// Test insert code and make sure timers are added in the proper order.
test("Add as first: ");
s = natsTimer_Create(&(timers[0]), _dummyTimerCB, NULL, 10000, NULL);
testCond(s == NATS_OK);

test("Add to the end: ");
s = natsTimer_Create(&(timers[1]), _dummyTimerCB, NULL, 20000, NULL);
testCond(s == NATS_OK);

test("Add to the end again: ");
s = natsTimer_Create(&(timers[2]), _dummyTimerCB, NULL, 30000, NULL);
testCond(s == NATS_OK);

test("Add as first again: ");
s = natsTimer_Create(&(timers[3]), _dummyTimerCB, NULL, 1000, NULL);
testCond(s == NATS_OK);

test("Insert in between: ");
s = natsTimer_Create(&(timers[4]), _dummyTimerCB, NULL, 15000, NULL);
testCond(s == NATS_OK);

test("Verify count: ");
testCond(nats_getTimersCountInList() == 5);

test("Verify order: ");
{
natsLibTimers *timers = &(nats_lib()->timers);
natsTimer *cur = NULL;
int idx = 0;

s = NATS_OK;
natsMutex_Lock(timers->lock);
cur = timers->head;
while ((s == NATS_OK) && (cur != NULL))
{
switch (idx)
{
case 0:
s = (cur->interval == 1000 ? NATS_OK : NATS_ERR);
break;
case 1:
s = (cur->interval == 10000 ? NATS_OK : NATS_ERR);
break;
case 2:
s = (cur->interval == 15000 ? NATS_OK : NATS_ERR);
break;
case 3:
s = (cur->interval == 20000 ? NATS_OK : NATS_ERR);
break;
case 4:
s = (cur->interval == 30000 ? NATS_OK : NATS_ERR);
break;
default:
s = NATS_ERR;
}
cur = cur->next;
idx++;
}
natsMutex_Unlock(timers->lock);
}
testCond(s == NATS_OK);

for (i=0; i<5; i++)
{
natsTimer_Destroy(timers[i]);
timers[i] = NULL;
}

test("Create performance: ");
s = NATS_OK;
start = nats_NowInNanoSeconds();
for (i=0; (s == NATS_OK) && (i<numTimers); i++)
s = natsTimer_Create(&(timers[i]), _dummyTimerCB, NULL, 10000+i, NULL);
if (s == NATS_OK)
dur = nats_NowInNanoSeconds() - start;
testCond((s == NATS_OK) && (dur < NATS_SECONDS_TO_NANOS(1)));

test("Verify count: ");
testCond(nats_getTimersCountInList() == numTimers);

test("Destroy performance: ");
start = nats_NowInNanoSeconds();
for (i=0; i<numTimers; i++)
natsTimer_Destroy(timers[i]);
dur = nats_NowInNanoSeconds() - start;
testCond(dur < NATS_SECONDS_TO_NANOS(1));

test("Verify count: ");
testCond(nats_getTimersCountInList() == 0);

free(timers);
timers = NULL;

// Create a timer that will not be stopped here to exercise
// code that cleans up timers when library is unloaded.
test("Create timer: ");
Expand Down