]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: cli: implement basic mechanism for uninterruptible jobs
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Thu, 29 Oct 2026 14:38:59 +0000 (15:38 +0100)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Mon, 9 Nov 2026 16:15:29 +0000 (17:15 +0100)
Interactive CLI commands will be interrupted when the CLI input
is disconnected. This is a safety measure to ensure a long running
command won't consume unnecessarily a CLI connection slot without any
means of shutting it down.

A disconnection is detected by the closure of the remote write side as
this is the only way to detect it when there is nothing to send.
However, this causes issues with utilities such as socat running with a
timeout which perform a half-close on their write channel when the
command has been sent. For example, it is currently impossible to
execute "wait" with socat, unless the special option "ignoreeof" is set.

Introduce a new concept of CLI uninterruptible jobs. This mode of
execution will only be available to long-running commands with a
dedicated argument. Under this mode of execution, the execution will
continue despite the remote write channel being already closed.

A limit of concurrent uninterruptible jobs is set to 3 to ensure they
won't exhaust the default CLI maxconn of 10.

doc/management.txt
include/haproxy/applet-t.h
include/haproxy/cli-t.h
include/haproxy/cli.h
src/cli.c

index 4520745a1c4425c51b06bdb15cb939018aa094f3..8f0682fef856e53609b784789953291bbaab34e8 100644 (file)
@@ -1696,6 +1696,16 @@ the pattern marking the end of the payload is part of this limit.
 When entering a payload while in interactive mode, the prompt will change from
 "> " to "+ ".
 
+Most commands should run rapidly before reporting their final status. Some
+commands though may take longer to wait for external events. By default, the
+handler is interrupted as soon as the CLI client disconnects which is the
+safest behavior. However, this causes issues with utilities such as 'socat'
+which close their output channel only, as haproxy is unable to differentiate
+between a full and a partial close. These commands may support an extra
+argument to run in the so-called uninterruptible execution mode. To prevent
+excessive usage, the number of concurrent uninterruptible jobs is fixed to a
+low value and extra requests will be rejected with an error.
+
 It is important to understand that when multiple haproxy processes are started
 on the same sockets, any process may pick up the request and will output its
 own stats.
index 9a9268f98006ccdd83afe9385cdda9df389423ac..8aaa9a3e0be35a325a815800262641cfd9676cb9 100644 (file)
@@ -137,6 +137,7 @@ struct appctx {
                                                              if the command is terminated or the session released */
                struct cli_kw *kw;      /* the keyword being processed */
                struct mt_list sess_el; /* attach point in global <cli_session_list> */
+               uint cmd_flags;         /* flags for the active command */
        } cli_ctx; /* context dedicated to the CLI applet */
 
        struct buffer_wait buffer_wait; /* position in the list of objects waiting for a buffer */
index 331e3ede7eef70979d6a84b13dbb0054697ab643..833d73a5a73e6dd5fefe5d61a0b5e896c08597e2 100644 (file)
@@ -55,6 +55,8 @@
 #define CLI_MAX_MATCHES 5
 #define CLI_MAX_HELP_ENTRIES 1024
 
+#define CLI_CMD_FL_UNINTR        0x0001  /* runs as uninterruptible */
+
 /* CLI states */
 enum {
        CLI_ST_INIT = 0,   /* initial state, must leave to zero ! */
index 4d94ea6fbd4143ee0c33907d7a484f3cf355ec13..6627646ff428a56e16f595b9eb7a5256a3e02508 100644 (file)
@@ -35,6 +35,9 @@ void cli_register_kw(struct cli_kw_list *kw_list);
 struct cli_kw* cli_find_kw_exact(char **args);
 void cli_list_keywords(void);
 
+int cli_inc_unintr(struct appctx *appctx);
+void cli_dec_unintr(struct appctx *appctx);
+
 int cli_has_level(struct appctx *appctx, int level);
 
 int cli_parse_default(char **args, char *payload, struct appctx *appctx, void *private);
index edd20b3a65e11c710bc277503aed64453d6c229b..edf43b1857b91d14d616420d04d9058f8addbf29 100644 (file)
--- a/src/cli.c
+++ b/src/cli.c
@@ -99,6 +99,35 @@ struct show_env_ctx {
 /* Currently active CLI sessions. */
 static struct mt_list cli_session_list = MT_LIST_HEAD_INIT(cli_session_list);
 
+/* Limit of concurrent uninterruptible jobs. */
+#define CLI_MAX_JOBS_UNINTR   3
+/* Current number of concurrent uninterruptible jobs. */
+static uint cli_jobs_unintr = 0;
+
+/* Prepare for the execution of a new uninterruptible job.
+ * Returns 0 on success or non zero if concurrent job limit exceeded.
+ */
+int cli_inc_unintr(struct appctx *appctx)
+{
+       if (HA_ATOMIC_FETCH_ADD(&cli_jobs_unintr, 1) >= CLI_MAX_JOBS_UNINTR) {
+               HA_ATOMIC_DEC(&cli_jobs_unintr);
+               return 1;
+       }
+
+       appctx->cli_ctx.cmd_flags |= CLI_CMD_FL_UNINTR;
+       return 0;
+}
+
+/* Notify about the end of an uninterruptible job. */
+void cli_dec_unintr(struct appctx *appctx)
+{
+       if (!(appctx->cli_ctx.cmd_flags & CLI_CMD_FL_UNINTR))
+               return;
+
+       HA_ATOMIC_DEC(&cli_jobs_unintr);
+       appctx->cli_ctx.cmd_flags &= ~CLI_CMD_FL_UNINTR;
+}
+
 /* CLI context for the "show fd" command */
 /* flags for show_fd_ctx->show_mask */
 #define CLI_SHOWFD_F_PI  0x00000001   /* pipes             */
@@ -807,6 +836,7 @@ static void cli_reset_cmd(struct appctx *appctx)
        appctx->cli_ctx.kw = NULL;
        appctx->cli_ctx.io_handler = NULL;
        appctx->cli_ctx.io_release = NULL;
+       appctx->cli_ctx.cmd_flags = 0;
 }
 
 /* Processes the CLI interpreter on the stats socket. This function is called
@@ -1015,6 +1045,7 @@ int cli_init(struct appctx *appctx)
        appctx->cli_ctx.payload = BUF_NULL;
        appctx->cli_ctx.max_payload_sz = global.tune.cli_max_payload_sz;
        MT_LIST_APPEND(&cli_session_list, &appctx->cli_ctx.sess_el);
+       appctx->cli_ctx.cmd_flags = 0;
 
        /* Wakeup the applet ASAP. */
         applet_need_more_data(appctx);