From: Christopher Faulet Date: Fri, 23 Oct 2026 14:52:40 +0000 (+0200) Subject: BUG/MEDIUM: hlua/filters: Never arm the analysis timer for the lua wake-ups X-Git-Tag: v3.5-dev10~58 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=0023120796cc86c7ea685c05a79607245e1e5459;p=haproxy.git BUG/MEDIUM: hlua/filters: Never arm the analysis timer for the lua wake-ups A lua filter may set a wake-up date from a callback, with filter.wake_time(), to be called back later to continue its processing. This date was armed on the analysis timer of the filtered channel. But this timer is a real timer, shared by all the filters and the analyzers attached to the channel, and it is single-owner: it must only be manipulated by one entity at a time, which consumes its own expiry. Setting a wake-up date on it was thus wrong. In practice, the date was systematically preserved when it was not expired, another filter or an analyzer may rely on it, especially during the message analysis, when the analyzers use it as a blocking timer (the "pause" action for instance). But the value could be left armed after the filter was done with the channel: when it expired later, without any owner, nothing could reset or rearm it. The stream task was then woken up on the expired timer and looped forever rearming it, a busy loop until the watchdog killed the process. This may happen long after the filter finished its processing, for instance a wake-up date armed during the payload analysis may be left behind when the payload analysis ends, while the analysis of the other channel is still in progress. The wake-up date is not a real timer, only an upper bound on the wait duration: the filter must be woken up at most at this date, earlier is fine, for instance on a data event. So now it is armed on the stream task expiration date instead, which is not a real timer either, it is only used to wake up the task at the lowest armed date. The stream task expiration date is always recomputed from all its sources, so nothing can be left behind on it. The lua filters remember nothing. But a wake-up on the stream task alone does not report any event on the channels. So, when the task is woken up because a timer expired, the check_timeouts callback of the lua filters is called: it reports a read event on the channels filtered by the lua filter when the wake-up date expired, so its analyzers and its filters are called back. Otherwise, the wake-up date is re-armed on the stream task expiration date, in case it was dropped from it. This way, the analysis timers are only used by their owners, and the lua filters get their wake-ups without interfering with them. The http-request/response actions, which also support act.wake_time(), continue to arm the analysis timer: they are called back by the analyzers of the ruleset they belong to, which consume their own expiry on this timer. This patch should be backported to all supported versions. --- diff --git a/src/hlua.c b/src/hlua.c index ee38cf461..37cf03fa5 100644 --- a/src/hlua.c +++ b/src/hlua.c @@ -12351,6 +12351,39 @@ static int hlua_filter_from_payload(struct filter *filter, struct channel *chn) return (flt_ctx && !!(flt_ctx->flags & HLUA_FLT_CTX_FL_PAYLOAD_BIT(CHN_IDX(chn)))); } +/* Arm the wake-up date set by a lua filter callback on the stream task: the + * runtime set a wake-up date with filter.wake_time(), to be called + * back later to continue its processing + */ +static void hlua_flt_set_wake_time(struct stream *s, struct filter *filter, struct hlua *flt_hlua) +{ + s->task->expire = tick_first((tick_is_expired(s->task->expire, now_ms) ? + TICK_ETERNITY : s->task->expire), + flt_hlua->wake_time); +} + +/* Called when the stream task is woken up because a timer expired. Rearm any + * wake-up date if not expired. Otherwise, report a read event on the channel to + * resume the filter evaluation. + */ +static void hlua_filter_check_timeouts(struct stream *s, struct filter *filter) +{ + struct hlua_flt_ctx *flt_ctx = filter->ctx; + + if (tick_isset(flt_ctx->hlua[0]->wake_time)) { + if (tick_is_expired(flt_ctx->hlua[0]->wake_time, now_ms)) + s->req.flags |= CF_READ_EVENT; + else + hlua_flt_set_wake_time(s, filter, flt_ctx->hlua[0]); + } + if (tick_isset(flt_ctx->hlua[1]->wake_time)) { + if (tick_is_expired(flt_ctx->hlua[1]->wake_time, now_ms)) + s->res.flags |= CF_READ_EVENT; + else + hlua_flt_set_wake_time(s, filter, flt_ctx->hlua[1]); + } +} + static int hlua_filter_callback(struct stream *s, struct filter *filter, const char *fun, int dir, unsigned int flags) { @@ -12459,22 +12492,13 @@ static int hlua_filter_callback(struct stream *s, struct filter *filter, const c } hlua_unlock(flt_hlua); - /* Set timeout in the required channel. */ - if (flt_hlua->wake_time != TICK_ETERNITY) { - if (dir == SMP_OPT_DIR_REQ) - s->req.analyse_exp = flt_hlua->wake_time; - else - s->res.analyse_exp = flt_hlua->wake_time; - } + /* Set the wake-up date on the stream task. */ + hlua_flt_set_wake_time(s, filter, flt_hlua); break; case HLUA_E_AGAIN: - /* Set timeout in the required channel. */ - if (flt_hlua->wake_time != TICK_ETERNITY) { - if (dir == SMP_OPT_DIR_REQ) - s->req.analyse_exp = flt_hlua->wake_time; - else - s->res.analyse_exp = flt_hlua->wake_time; - } + /* Set the wake-up date on the stream task. */ + hlua_flt_set_wake_time(s, filter, flt_hlua); + /* Some actions can be wake up when a "write" event * is detected on a response channel. This is useful * only for actions targeted on the requests. @@ -12634,6 +12658,7 @@ static int hlua_filter_parse_fct(char **args, int *cur_arg, struct proxy *px, } hlua_flt_ops->attach = hlua_filter_new; hlua_flt_ops->detach = hlua_filter_delete; + hlua_flt_ops->check_timeouts = hlua_filter_check_timeouts; /* Push the filter class on the stack and resolve all callbacks */ hlua_pushref(L, reg_flt->flt_ref[state_id]);