[common] linux: replace create_timer with a single threaded timer

Now LG uses a 25Hz tick timer it is an issue that `create_timer` spawns
a new thread for every single timer event, so instead multiplex all the
timers into a single thread with a 1ms resolution.
This commit is contained in:
Geoffrey McRae 2022-01-12 13:00:12 +11:00
parent 6bba9bc25d
commit 344d2ec599

View File

@ -20,6 +20,8 @@
#include "common/time.h" #include "common/time.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/thread.h"
#include "common/ll.h"
#include <errno.h> #include <errno.h>
#include <signal.h> #include <signal.h>
@ -27,84 +29,124 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
struct LGTimer struct LGTimerState
{ {
LGTimerFn fn; bool running;
void * udata; struct LGThread * thread;
timer_t id; struct ll * timers;
bool running;
}; };
static void TimerProc(union sigval arg) struct LGTimer
{ {
LGTimer * timer = (LGTimer *)arg.sival_ptr; unsigned int interval;
if (!timer->fn(timer->udata)) unsigned int count;
LGTimerFn fn;
void * udata;
};
static struct LGTimerState l_ts = { 0 };
static int timerFn(void * fn)
{
struct LGTimer * timer;
struct timespec time;
clock_gettime(CLOCK_MONOTONIC, &time);
while(l_ts.running)
{ {
if (timer_delete(timer->id)) ll_lock(l_ts.timers);
DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno)); ll_forEachNL(l_ts.timers, item, timer)
timer->running = false; {
if (timer->count++ == timer->interval)
{
timer->count = 0;
if (!timer->fn(timer->udata))
ll_removeNL(l_ts.timers, item);
}
}
ll_unlock(l_ts.timers);
tsAdd(&time, 1000000);
while(clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &time, NULL) != 0) {}
} }
return 0;
}
static inline bool setupTimerThread(void)
{
if (l_ts.thread)
return true;
l_ts.timers = ll_new();
l_ts.running = true;
if (!l_ts.timers)
{
DEBUG_ERROR("failed to create linked list");
goto err;
}
if (!lgCreateThread("TimerThread", timerFn, NULL, &l_ts.thread))
{
DEBUG_ERROR("failed to create the timer thread");
goto err_thread;
}
return true;
err_thread:
ll_free(l_ts.timers);
err:
return false;
}
static void destroyTimerThread(void)
{
if (ll_count(l_ts.timers))
return;
l_ts.running = false;
lgJoinThread(l_ts.thread, NULL);
l_ts.thread = NULL;
} }
bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn, bool lgCreateTimer(const unsigned int intervalMS, LGTimerFn fn,
void * udata, LGTimer ** result) void * udata, LGTimer ** result)
{ {
LGTimer * ret = malloc(sizeof(*ret)); struct LGTimer * timer = malloc(sizeof(*timer));
if (!timer)
if (!ret)
{ {
DEBUG_ERROR("failed to malloc LGTimer struct"); DEBUG_ERROR("out of memory");
return false; return false;
} }
ret->fn = fn; timer->interval = intervalMS;
ret->udata = udata; timer->count = 0;
ret->running = true; timer->fn = fn;
timer->udata = udata;
struct sigevent sev = if (!setupTimerThread())
{ {
.sigev_notify = SIGEV_THREAD, DEBUG_ERROR("failed to setup the timer thread");
.sigev_notify_function = &TimerProc, goto err_thread;
.sigev_value.sival_ptr = ret,
};
if (timer_create(CLOCK_MONOTONIC, &sev, &ret->id))
{
DEBUG_ERROR("failed to create timer: %s", strerror(errno));
free(ret);
return false;
} }
struct timespec interval = ll_push(l_ts.timers, timer);
{ *result = timer;
.tv_sec = intervalMS / 1000,
.tv_nsec = (intervalMS % 1000) * 1000000,
};
struct itimerspec spec =
{
.it_interval = interval,
.it_value = interval,
};
if (timer_settime(ret->id, 0, &spec, NULL))
{
DEBUG_ERROR("failed to set timer: %s", strerror(errno));
timer_delete(ret->id);
free(ret);
return false;
}
*result = ret;
return true; return true;
err_thread:
free(timer);
return false;
} }
void lgTimerDestroy(LGTimer * timer) void lgTimerDestroy(LGTimer * timer)
{ {
if (timer->running) if (!l_ts.thread)
{ return;
if (timer_delete(timer->id))
DEBUG_ERROR("failed to destroy the timer: %s", strerror(errno));
}
free(timer); ll_removeData(l_ts.timers, timer);
destroyTimerThread();
} }