]> git.kaiwu.me - haproxy.git/commitdiff
BUG/MEDIUM: hlua/filters: Never arm the analysis timer for the lua wake-ups
authorChristopher Faulet <cfaulet@haproxy.com>
Fri, 23 Oct 2026 14:52:40 +0000 (16:52 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 23 Oct 2026 17:43:38 +0000 (19:43 +0200)
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.

src/hlua.c

index ee38cf46126b9e1fbe9e192304aa920463a2b0c3..37cf03fa5ca838af5577044448cd8ce67f990163 100644 (file)
@@ -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
+ * <flt_hlua> 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]);