]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: stats: protect proxy iteration via watcher
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Tue, 10 Feb 2026 10:20:12 +0000 (11:20 +0100)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 27 Feb 2026 09:28:24 +0000 (10:28 +0100)
Define a new <px_watch> watcher member in stats applet context. It is
used to register the applet on a proxy when iterating over the proxies
list. <obj1> is automatically updated via the watcher interaction.
Watcher is first initialized prior to stats_dump_proxies() invocation.

This guarantees that stats dump is safe even if applet yields and a
backend is removed in parallel.

include/haproxy/stats-t.h
src/http_ana.c
src/stats-html.c
src/stats-proxy.c
src/stats.c

index 1479c4efd1ad4f0a3a24c70e2bd38c55eee59355..ffdd0b15a29e112ae80bda459fa73e649969de30 100644 (file)
@@ -578,6 +578,7 @@ struct show_stat_ctx {
        int iid, type, sid;     /* proxy id, type and service id if bounding of stats is enabled */
        int st_code;            /* the status code returned by an action */
        struct buffer chunk;    /* temporary buffer which holds a single-line output */
+       struct watcher px_watch;  /* watcher to automatically update obj1 on backend deletion */
        struct watcher srv_watch; /* watcher to automatically update obj2 on server deletion */
        enum stat_state state;  /* phase of output production */
 };
index f004e41d94776a9548ed23887781cf4981d15b32..fa061b69824a8231d919ae8228cbf3eb5776cf50 100644 (file)
@@ -4089,6 +4089,8 @@ static int http_handle_stats(struct stream *s, struct channel *req, struct proxy
        ctx->flags |= STAT_F_FMT_HTML; /* assume HTML mode by default */
        if ((msg->flags & HTTP_MSGF_VER_11) && (txn->meth != HTTP_METH_HEAD))
                ctx->flags |= STAT_F_CHUNKED;
+
+       watcher_init(&ctx->px_watch,  &ctx->obj1, offsetof(struct proxy, watcher_list));
        watcher_init(&ctx->srv_watch, &ctx->obj2, offsetof(struct server, watcher_list));
 
        htx = htxbuf(&req->buf);
index 79ffc9731d12c907b02f38826651d89d6f36d3cd..cd9e37e9a55f8f82720b2f5effa3cf4dbdf4c508 100644 (file)
@@ -2116,6 +2116,8 @@ static size_t http_stats_fastfwd(struct appctx *appctx, struct buffer *buf,
 static void http_stats_release(struct appctx *appctx)
 {
        struct show_stat_ctx *ctx = appctx->svcctx;
+       if (ctx->domain == STATS_DOMAIN_PROXY && ctx->obj1)
+               watcher_detach(&ctx->px_watch);
        if (ctx->px_st == STAT_PX_ST_SV && ctx->obj2)
                watcher_detach(&ctx->srv_watch);
 }
index 278a4bf9c7694a63d761d37edfc665ccec41272f..5b6a70628bcd9eed9391fdf00a8c7e408ba5f5c3 100644 (file)
@@ -1616,11 +1616,13 @@ int stats_dump_proxies(struct stconn *sc, struct buffer *buf, struct htx *htx)
        struct proxy *px;
 
        /* dump proxies */
-       while (ctx->obj1) {
+       /* obj1 is updated and returned through watcher_next() */
+       for (px = ctx->obj1; px;
+            px = watcher_next(&ctx->px_watch, px->next)) {
+
                if (stats_is_full(appctx, buf, htx))
                        goto full;
 
-               px = ctx->obj1;
                /* Skip the global frontend proxies and non-networked ones.
                 * Also skip proxies that were disabled in the configuration
                 * This change allows retrieving stats from "old" proxies after a reload.
@@ -1631,7 +1633,6 @@ int stats_dump_proxies(struct stconn *sc, struct buffer *buf, struct htx *htx)
                                return 0;
                }
 
-               ctx->obj1 = px->next;
                ctx->px_st = STAT_PX_ST_INIT;
                ctx->field = 0;
        }
index b4f29696ef7e2c8843b12818925bbbb5541992c7..4ce6d5e95308ebeca07c5957c69deda66c006318 100644 (file)
@@ -590,12 +590,13 @@ int stats_dump_stat_to_buffer(struct stconn *sc, struct buffer *buf, struct htx
                                goto full;
                }
 
-               if (domain == STATS_DOMAIN_PROXY)
-                       ctx->obj1 = proxies_list;
-
                ctx->px_st = STAT_PX_ST_INIT;
                ctx->field = 0;
                ctx->state = STAT_STATE_LIST;
+               /* Update ctx->obj1 via watcher to point on the first proxy. */
+               if (domain == STATS_DOMAIN_PROXY)
+                       watcher_attach(&ctx->px_watch, proxies_list);
+
                __fallthrough;
 
        case STAT_STATE_LIST:
@@ -944,6 +945,8 @@ static int cli_parse_show_stat(char **args, char *payload, struct appctx *appctx
        ctx->scope_len = 0;
        ctx->http_px = NULL; // not under http context
        ctx->flags = STAT_F_SHNODE | STAT_F_SHDESC;
+
+       watcher_init(&ctx->px_watch,  &ctx->obj1, offsetof(struct proxy, watcher_list));
        watcher_init(&ctx->srv_watch, &ctx->obj2, offsetof(struct server, watcher_list));
 
        if ((strm_li(appctx_strm(appctx))->bind_conf->level & ACCESS_LVL_MASK) >= ACCESS_LVL_OPER)
@@ -1028,8 +1031,12 @@ static int cli_io_handler_dump_stat(struct appctx *appctx)
 static void cli_io_handler_release_stat(struct appctx *appctx)
 {
        struct show_stat_ctx *ctx = appctx->svcctx;
-       if (ctx->px_st == STAT_PX_ST_SV && ctx->obj2)
-               watcher_detach(&ctx->srv_watch);
+
+       if (ctx->state == STAT_STATE_LIST && ctx->domain == STATS_DOMAIN_PROXY) {
+               watcher_detach(&ctx->px_watch);
+               if (ctx->px_st == STAT_PX_ST_SV)
+                       watcher_detach(&ctx->srv_watch);
+       }
 }
 
 static int cli_io_handler_dump_json_schema(struct appctx *appctx)
@@ -1089,8 +1096,12 @@ static int cli_io_handler_dump_stat_file(struct appctx *appctx)
 static void cli_io_handler_release_dump_stat_file(struct appctx *appctx)
 {
        struct show_stat_ctx *ctx = appctx->svcctx;
-       if (ctx->px_st == STAT_PX_ST_SV && ctx->obj2)
-               watcher_detach(&ctx->srv_watch);
+
+       if (ctx->state == STAT_STATE_LIST && ctx->domain == STATS_DOMAIN_PROXY) {
+               watcher_detach(&ctx->px_watch);
+               if (ctx->px_st == STAT_PX_ST_SV)
+                       watcher_detach(&ctx->srv_watch);
+       }
 }
 
 int stats_allocate_proxy_counters_internal(struct extra_counters **counters,