/* SPDX-License-Identifier: GPL-2.0 */ #include #include #include #include #include #include "minecproxy.h" #include "uring.h" #include "ptimer.h" struct ptimer { uint64_t value; time_t previous_time; struct uring_task task; unsigned task_count; struct list_head ptasks; }; static void ptimer_set(unsigned value, unsigned interval) { struct itimerspec tspec = { .it_interval = { .tv_sec = interval, .tv_nsec = 0 }, .it_value = { .tv_sec = value, .tv_nsec = 0 } }; assert_return(cfg->ptimer && cfg->ptimer->task.fd >= 0); if (timerfd_settime(cfg->ptimer->task.fd, 0, &tspec, NULL) != 0) error("timerfd_settime: %m"); } static unsigned gcd(unsigned a, unsigned b) { if (a == 0) return b; return gcd(b % a, a); } static unsigned array_gcd(unsigned arr[], unsigned n) { unsigned result = arr[0]; for (unsigned i = 1; i < n; i++) result = gcd(arr[i], result); return result; } static void ptimer_tick(struct ptimer *ptimer) { time_t now = time(NULL); unsigned diff = (unsigned)(now - ptimer->previous_time); struct ptimer_task *ptask, *ptmp; debug(DBG_TIMER, "got a tick of %u secs", diff); list_for_each_entry_safe(ptask, ptmp, &ptimer->ptasks, list) { if (ptask->remain > diff) { ptask->remain -= diff; continue; } debug(DBG_TIMER, "triggering ptask %p (times %u)", ptask, ptask->times); ptask->cb(ptask); ptask->remain = ptask->interval; if (ptask->times == 0) continue; ptask->times--; if (ptask->times == 0) { ptask->active = false; list_del(&ptask->list); } } ptimer->previous_time = now; } static void ptimer_reconfig(struct ptimer *ptimer) { struct ptimer_task *ptask; unsigned i = 0; unsigned lowest = ~0; unsigned interval; if (list_empty(&ptimer->ptasks)) { debug(DBG_TIMER, "no tasks"); ptimer_set(0, 0); return; } unsigned intervals[ptimer->task_count]; list_for_each_entry(ptask, &ptimer->ptasks, list) { if (ptask->remain < lowest) lowest = ptask->remain; intervals[i++] = ptask->interval; } interval = array_gcd(intervals, i); debug(DBG_TIMER, "lowest: %u, gcd: %u\n", lowest, interval); ptimer_set(lowest, interval); } void ptimer_del_task(struct ptimer_task *ptask) { struct ptimer *ptimer = cfg->ptimer; assert_return(ptask && ptimer); assert_return_silent(ptask->active); assert_return(ptimer->task_count > 0); list_del(&ptask->list); ptask->active = false; ptimer->task_count--; ptimer_tick(ptimer); ptimer_reconfig(ptimer); uring_task_put(&ptimer->task); } void ptimer_add_task(struct ptimer_task *ptask) { struct ptimer *ptimer = cfg->ptimer; assert_return(ptask && ptask->interval > 0 && ptask->cb && ptimer); assert_return_silent(!ptask->active); uring_task_get(&ptimer->task); ptask->active = true; ptask->remain = ptask->interval; ptimer_tick(ptimer); list_add(&ptask->list, &ptimer->ptasks); ptimer->task_count++; ptimer_reconfig(ptimer); } void ptimer_refdump() { assert_return(cfg->ptimer); uring_task_refdump(&cfg->ptimer->task); } static void ptimer_free(struct uring_task *task) { struct ptimer *ptimer = container_of(task, struct ptimer, task); assert_return(task); debug(DBG_TIMER, "task %p, ptimer %p", task, ptimer); xfree(ptimer); cfg->ptimer = NULL; } void ptimer_delete() { assert_return(cfg->ptimer); debug(DBG_TIMER, "closing fd %i", cfg->ptimer->task.fd); uring_task_destroy(&cfg->ptimer->task); } static void ptimer_cb(struct uring_task *task, int res) { struct ptimer *ptimer = container_of(task, struct ptimer, task); assert_return(task); assert_task_alive(DBG_IGMP, task); if (res != sizeof(ptimer->value)) { error("timerfd_read: res: %i, %m", res); return; } ptimer_tick(ptimer); uring_read(&ptimer->task, &ptimer->value, sizeof(ptimer->value), ptimer_cb); } void ptimer_init() { struct ptimer *ptimer; int tfd; assert_return(!cfg->ptimer); ptimer = zmalloc(sizeof(*ptimer)); if (!ptimer) die("malloc: %m"); tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC); if (tfd < 0) die("timerfd_create: %m"); ptimer->task_count = 0; ptimer->previous_time = time(NULL); INIT_LIST_HEAD(&ptimer->ptasks); uring_task_init(&ptimer->task, "ptimer", uring_parent(), ptimer_free); uring_task_set_fd(&ptimer->task, tfd); cfg->ptimer = ptimer; uring_read(&ptimer->task, &ptimer->value, sizeof(ptimer->value), ptimer_cb); }