From 324f0a60aba52ed5d80b714bf4b6f0b819efd12e Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Wed, 3 Sep 2025 12:03:40 +0000 Subject: [PATCH] BUG/MINOR: stick-tables: never leave used entries without expiration When trying to kill/expire entries, if a ref-counted entry is found, let's requeue it with its expiration timer instead of leaving it out, because other ref-counters (e.g. peers) will not purge it otherwise, leaving it orphan. This one seems trickier to trigger, though it seems to happen sometimes when peers are late and a long resync is active and competing with intense calls to process_table_expire() (i.e. when no other acitvity is there). This must be backported to 3.2. It's likely that older versions are affected as well, but possibly differently since the expiration mechanism changed between 3.1 and 3.2, so better not take unneeded risks there. --- src/stick_table.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/stick_table.c b/src/stick_table.c index 7d516c8a5..f9334213e 100644 --- a/src/stick_table.c +++ b/src/stick_table.c @@ -341,13 +341,14 @@ int stktable_trash_oldest(struct stktable *t, int to_batch) ts = eb32_entry(eb, struct stksess, exp); eb = eb32_next(eb); - /* don't delete an entry which is currently referenced */ - if (HA_ATOMIC_LOAD(&ts->ref_cnt) != 0) - continue; - + /* This entry's key is expired, we must delete it. It + * may be properly requeued if the element is still in + * use or not really expired though. + */ eb32_delete(&ts->exp); - if (ts->expire != ts->exp.key) { + if (ts->expire != ts->exp.key || HA_ATOMIC_LOAD(&ts->ref_cnt) != 0) { + requeue: if (!tick_isset(ts->expire)) continue; @@ -385,7 +386,7 @@ int stktable_trash_oldest(struct stktable *t, int to_batch) * existing ones already have the ref_cnt. */ if (HA_ATOMIC_LOAD(&ts->ref_cnt)) - continue; + goto requeue; /* session expired, trash it */ ebmb_delete(&ts->key); @@ -939,10 +940,6 @@ struct task *process_table_expire(struct task *task, void *context, unsigned int ts = eb32_entry(eb, struct stksess, exp); eb = eb32_next(eb); - /* don't delete an entry which is currently referenced */ - if (HA_ATOMIC_LOAD(&ts->ref_cnt) != 0) - continue; - if (updt_locked == 1) { expired++; if (expired == STKTABLE_MAX_UPDATES_AT_ONCE) { @@ -952,9 +949,14 @@ struct task *process_table_expire(struct task *task, void *context, unsigned int } } + /* This entry's key is expired, we must delete it. It + * may be properly requeued if the element is still in + * use or not really expired though. + */ eb32_delete(&ts->exp); - if (!tick_is_expired(ts->expire, now_ms)) { + if (!tick_is_expired(ts->expire, now_ms) || HA_ATOMIC_LOAD(&ts->ref_cnt) != 0) { + requeue: if (!tick_isset(ts->expire)) continue; @@ -991,7 +993,7 @@ struct task *process_table_expire(struct task *task, void *context, unsigned int * existing ones already have the ref_cnt. */ if (HA_ATOMIC_LOAD(&ts->ref_cnt)) - continue; + goto requeue; /* session expired, trash it */ ebmb_delete(&ts->key);