BUG/MEDIUM: apppet: Improve spinning loop detection with the new API
authorChristopher Faulet <cfaulet@haproxy.com>
Fri, 10 Oct 2025 12:32:04 +0000 (14:32 +0200)
committerWilly Tarreau <w@1wt.eu>
Fri, 10 Oct 2025 16:16:16 +0000 (18:16 +0200)
Conditions to detect the spinning loop for applets based on the new API are
not accurrate. We cannot continue to check the channel's buffers state to
know if an applet has made some progress. At least, we must also check the
applet's buffers.

After digging to find the right way to do, it was clear that the best is to
use something similar to what is performed for the streams, namely, checking
read and write events. And in fact, it is quite easy to do with the new
API. So let's do so.

This patch must be backported as far as 3.0.

(cherry picked from commit 54b7539d64d70f033955a0bfe7d0f19d7eef2328)
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit 20623769917780b8fb7a01a7aa1a2539cc60961b)
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit ff753da75357e2595d69570b455f8c782297eb27)
Signed-off-by: Willy Tarreau <w@1wt.eu>

src/applet.c

index 2bc3eb5..9ab14bd 100644 (file)
@@ -873,6 +873,7 @@ struct task *task_process_applet(struct task *t, void *context, unsigned int sta
        struct appctx *app = context;
        struct stconn *sc;
        unsigned int rate;
+       int did_send, did_recv;
 
        TRACE_ENTER(APPLET_EV_PROCESS, app);
 
@@ -900,6 +901,7 @@ struct task *task_process_applet(struct task *t, void *context, unsigned int sta
        sc = appctx_sc(app);
 
        sc_applet_sync_send(sc);
+       did_send = (sc_oc(sc)->flags & CF_WRITE_EVENT);
 
        /* We always pretend the applet can't get and doesn't want to
         * put, it's up to it to change this if needed. This ensures
@@ -916,7 +918,7 @@ struct task *task_process_applet(struct task *t, void *context, unsigned int sta
            applet_fl_test(app, APPCTX_FL_EOI|APPCTX_FL_EOS|APPCTX_FL_ERROR))
                applet_have_more_data(app);
 
-       sc_applet_sync_recv(sc);
+       did_recv = sc_applet_sync_recv(sc);
 
        /* TODO: May be move in appctx_rcv_buf or sc_applet_process ? */
        if (sc_waiting_room(sc) && (sc->flags & SC_FL_ABRT_DONE)) {
@@ -924,11 +926,7 @@ struct task *task_process_applet(struct task *t, void *context, unsigned int sta
        }
 
        /* measure the call rate and check for anomalies when too high */
-       if (((b_size(sc_ib(sc)) && sc->flags & SC_FL_NEED_BUFF) || // asks for a buffer which is present
-            (b_size(sc_ib(sc)) && !b_data(sc_ib(sc)) && sc->flags & SC_FL_NEED_ROOM) || // asks for room in an empty buffer
-            (b_data(sc_ob(sc)) && sc_is_send_allowed(sc)) || // asks for data already present
-            (!b_data(sc_ib(sc)) && b_data(sc_ob(sc)) && // didn't return anything ...
-             (!(sc_oc(sc)->flags & CF_WRITE_EVENT) && (sc->flags & SC_FL_SHUT_WANTED))))) { // ... and left data pending after a shut
+       if (!did_recv && !did_send) {
                rate = update_freq_ctr(&app->call_rate, 1);
                if (rate >= 100000 && app->call_rate.prev_ctr) // looped like this more than 100k times over last second
                        stream_dump_and_crash(&app->obj_type, read_freq_ctr(&app->call_rate));