Address #1551 - better timers handling

This commit is contained in:
Sergey Lyubka 2022-05-15 14:29:34 +01:00
parent 443d0f0c6c
commit cf7d1d8041
6 changed files with 48 additions and 38 deletions

View File

@ -1951,11 +1951,12 @@ struct mg_timer {
uint64_t period_ms; // Timer period in milliseconds
uint64_t expire; // Expiration timestamp in milliseconds
unsigned flags; // Possible flags values below
#define MG_TIMER_REPEAT 1 // Call function periodically, otherwise run once
#define MG_TIMER_ONCE 0 // Call function once
#define MG_TIMER_REPEAT 1 // Call function periodically
#define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set
void (*fn)(void *); // Function to call
void *arg; // Function argument
struct mg_timer *next; // Linkage in g_timers list
struct mg_timer *next; // Linkage
};
```

View File

@ -4158,12 +4158,13 @@ size_t mg_vsnprintf(char *buf, size_t len, const char *fmt, va_list ap) {
#define MG_TIMER_CALLED 4
void mg_timer_init(struct mg_timer **head, struct mg_timer *t, uint64_t ms,
unsigned flags, void (*fn)(void *), void *arg) {
struct mg_timer tmp = {ms, 0U, 0U, flags, fn, arg, *head};
*t = tmp;
*head = t;
if (flags & MG_TIMER_RUN_NOW) fn(arg);
}
void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
@ -4178,14 +4179,21 @@ void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
tmp = t->next;
if (t->prev_ms > now_ms) t->expire = 0; // Handle time wrap
t->prev_ms = now_ms;
if (t->expire == 0) t->expire = now_ms + t->period_ms;
if (t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
!(t->flags & MG_TIMER_CALLED)) {
// Handle MG_TIMER_NOW only once
} else if (t->expire == 0) {
t->expire = now_ms + t->period_ms;
}
if (t->expire > now_ms) continue;
t->fn(t->arg);
if ((t->flags & MG_TIMER_REPEAT) || !(t->flags & MG_TIMER_CALLED)) {
t->fn(t->arg);
}
t->flags |= MG_TIMER_CALLED;
// Try to tick timers with the given period as accurate as possible,
// even if this polling function is called with some random period.
t->expire = now_ms - t->expire > t->period_ms ? now_ms + t->period_ms
: t->expire + t->period_ms;
if (!(t->flags & MG_TIMER_REPEAT)) mg_timer_free(head, t);
}
}

View File

@ -719,15 +719,14 @@ struct mg_timer {
uint64_t prev_ms; // Timestamp of a previous poll
uint64_t expire; // Expiration timestamp in milliseconds
unsigned flags; // Possible flags values below
#define MG_TIMER_REPEAT 1 // Call function periodically, otherwise run once
#define MG_TIMER_ONCE 0 // Call function once
#define MG_TIMER_REPEAT 1 // Call function periodically
#define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set
void (*fn)(void *); // Function to call
void *arg; // Function argument
struct mg_timer *next; // Linkage in g_timers list
struct mg_timer *next; // Linkage
};
extern struct mg_timer *g_timers; // Global list of timers
void mg_timer_init(struct mg_timer **head, struct mg_timer *timer,
uint64_t milliseconds, unsigned flags, void (*fn)(void *),
void *arg);

View File

@ -4,12 +4,13 @@
#include "timer.h"
#include "arch.h"
#define MG_TIMER_CALLED 4
void mg_timer_init(struct mg_timer **head, struct mg_timer *t, uint64_t ms,
unsigned flags, void (*fn)(void *), void *arg) {
struct mg_timer tmp = {ms, 0U, 0U, flags, fn, arg, *head};
*t = tmp;
*head = t;
if (flags & MG_TIMER_RUN_NOW) fn(arg);
}
void mg_timer_free(struct mg_timer **head, struct mg_timer *t) {
@ -24,13 +25,20 @@ void mg_timer_poll(struct mg_timer **head, uint64_t now_ms) {
tmp = t->next;
if (t->prev_ms > now_ms) t->expire = 0; // Handle time wrap
t->prev_ms = now_ms;
if (t->expire == 0) t->expire = now_ms + t->period_ms;
if (t->expire == 0 && (t->flags & MG_TIMER_RUN_NOW) &&
!(t->flags & MG_TIMER_CALLED)) {
// Handle MG_TIMER_NOW only once
} else if (t->expire == 0) {
t->expire = now_ms + t->period_ms;
}
if (t->expire > now_ms) continue;
t->fn(t->arg);
if ((t->flags & MG_TIMER_REPEAT) || !(t->flags & MG_TIMER_CALLED)) {
t->fn(t->arg);
}
t->flags |= MG_TIMER_CALLED;
// Try to tick timers with the given period as accurate as possible,
// even if this polling function is called with some random period.
t->expire = now_ms - t->expire > t->period_ms ? now_ms + t->period_ms
: t->expire + t->period_ms;
if (!(t->flags & MG_TIMER_REPEAT)) mg_timer_free(head, t);
}
}

View File

@ -7,15 +7,14 @@ struct mg_timer {
uint64_t prev_ms; // Timestamp of a previous poll
uint64_t expire; // Expiration timestamp in milliseconds
unsigned flags; // Possible flags values below
#define MG_TIMER_REPEAT 1 // Call function periodically, otherwise run once
#define MG_TIMER_ONCE 0 // Call function once
#define MG_TIMER_REPEAT 1 // Call function periodically
#define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set
void (*fn)(void *); // Function to call
void *arg; // Function argument
struct mg_timer *next; // Linkage in g_timers list
struct mg_timer *next; // Linkage
};
extern struct mg_timer *g_timers; // Global list of timers
void mg_timer_init(struct mg_timer **head, struct mg_timer *timer,
uint64_t milliseconds, unsigned flags, void (*fn)(void *),
void *arg);

View File

@ -1171,13 +1171,17 @@ static void test_timer(void) {
struct mg_timer t1, t2, t3, *head = NULL;
mg_timer_init(&head, &t1, 5, MG_TIMER_REPEAT, f1, &v1);
mg_timer_init(&head, &t2, 15, 0, f1, &v2);
mg_timer_init(&head, &t2, 15, MG_TIMER_ONCE, f1, &v2);
mg_timer_init(&head, &t3, 10, MG_TIMER_REPEAT | MG_TIMER_RUN_NOW, f1, &v3);
ASSERT(head == &t3);
ASSERT(head->next == &t2);
mg_timer_poll(&head, 0);
ASSERT(v1 == 0);
ASSERT(v2 == 0);
ASSERT(v3 == 1);
mg_timer_poll(&head, 1);
ASSERT(v1 == 0);
ASSERT(v2 == 0);
@ -1188,19 +1192,12 @@ static void test_timer(void) {
ASSERT(v2 == 0);
ASSERT(v3 == 1);
ASSERT(head == &t3);
ASSERT(head->next == &t2);
// Simulate long delay - timers must invalidate expiration times
mg_timer_poll(&head, 100);
ASSERT(v1 == 2);
ASSERT(v2 == 1);
ASSERT(v3 == 2);
ASSERT(head == &t3);
ASSERT(head->next == &t1); // t2 should be removed
ASSERT(head->next->next == NULL);
mg_timer_poll(&head, 107);
ASSERT(v1 == 3);
ASSERT(v2 == 1);
@ -1216,6 +1213,7 @@ static void test_timer(void) {
ASSERT(v2 == 1);
ASSERT(v3 == 3);
mg_timer_free(&head, &t2);
mg_timer_init(&head, &t2, 3, 0, f1, &v2);
ASSERT(head == &t2);
ASSERT(head->next == &t3);
@ -1238,10 +1236,6 @@ static void test_timer(void) {
ASSERT(v2 == 2);
ASSERT(v3 == 4);
ASSERT(head == &t3);
ASSERT(head->next == &t1);
ASSERT(head->next->next == NULL);
mg_timer_poll(&head, 7);
ASSERT(v1 == 8);
ASSERT(v2 == 2);
@ -1253,8 +1247,9 @@ static void test_timer(void) {
ASSERT(v3 == 5);
mg_timer_free(&head, &t1);
ASSERT(head == &t3);
ASSERT(head->next == NULL);
ASSERT(head == &t2);
ASSERT(head->next == &t3);
ASSERT(head->next->next == NULL);
mg_timer_free(&head, &t2);
ASSERT(head == &t3);
@ -1366,7 +1361,7 @@ static void fn1(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
(void) c;
}
static void test_dns_timeout(const char *dns_server_url, const char *errstr) {
static void test_dns_error(const char *dns_server_url, const char *errstr) {
// Test timeout
struct mg_mgr mgr;
char buf[100] = "";
@ -1374,12 +1369,12 @@ static void test_dns_timeout(const char *dns_server_url, const char *errstr) {
mg_mgr_init(&mgr);
mgr.dns4.url = dns_server_url;
mgr.dnstimeout = 10;
MG_DEBUG(("opening dummy DNS listener..."));
MG_DEBUG(("opening dummy DNS listener @ [%s]...", dns_server_url));
mg_listen(&mgr, mgr.dns4.url, NULL, NULL); // Just discard our queries
mg_http_connect(&mgr, "http://google.com", fn1, buf);
for (i = 0; i < 50 && buf[0] == '\0'; i++) mg_mgr_poll(&mgr, 1);
mg_mgr_free(&mgr);
MG_DEBUG(("buf: [%s]", buf));
// MG_DEBUG(("buf: [%s] [%s]", buf, errstr));
ASSERT(strcmp(buf, errstr) == 0);
}
@ -1429,9 +1424,9 @@ static void test_dns(void) {
ASSERT(strcmp(dm.name, "new-fp-shed.wg1.b.yahoo.com") == 0);
}
test_dns_timeout("udp://127.0.0.1:12345", "DNS timeout");
test_dns_timeout("", "resolver");
test_dns_timeout("tcp://0.0.0.0:0", "DNS error");
test_dns_error("udp://127.0.0.1:12345", "DNS timeout");
test_dns_error("", "resolver");
test_dns_error("tcp://0.0.0.0:0", "DNS error");
}
static void test_util(void) {