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.
This commit is contained in:
Willy Tarreau 2025-09-03 12:03:40 +00:00
parent 8da6ed6b6a
commit 324f0a60ab

View File

@ -341,13 +341,14 @@ int stktable_trash_oldest(struct stktable *t, int to_batch)
ts = eb32_entry(eb, struct stksess, exp); ts = eb32_entry(eb, struct stksess, exp);
eb = eb32_next(eb); eb = eb32_next(eb);
/* don't delete an entry which is currently referenced */ /* This entry's key is expired, we must delete it. It
if (HA_ATOMIC_LOAD(&ts->ref_cnt) != 0) * may be properly requeued if the element is still in
continue; * use or not really expired though.
*/
eb32_delete(&ts->exp); 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)) if (!tick_isset(ts->expire))
continue; continue;
@ -385,7 +386,7 @@ int stktable_trash_oldest(struct stktable *t, int to_batch)
* existing ones already have the ref_cnt. * existing ones already have the ref_cnt.
*/ */
if (HA_ATOMIC_LOAD(&ts->ref_cnt)) if (HA_ATOMIC_LOAD(&ts->ref_cnt))
continue; goto requeue;
/* session expired, trash it */ /* session expired, trash it */
ebmb_delete(&ts->key); 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); ts = eb32_entry(eb, struct stksess, exp);
eb = eb32_next(eb); 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) { if (updt_locked == 1) {
expired++; expired++;
if (expired == STKTABLE_MAX_UPDATES_AT_ONCE) { 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); 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)) if (!tick_isset(ts->expire))
continue; continue;
@ -991,7 +993,7 @@ struct task *process_table_expire(struct task *task, void *context, unsigned int
* existing ones already have the ref_cnt. * existing ones already have the ref_cnt.
*/ */
if (HA_ATOMIC_LOAD(&ts->ref_cnt)) if (HA_ATOMIC_LOAD(&ts->ref_cnt))
continue; goto requeue;
/* session expired, trash it */ /* session expired, trash it */
ebmb_delete(&ts->key); ebmb_delete(&ts->key);