From: Amaury Denoyelle Date: Thu, 29 Oct 2026 14:38:59 +0000 (+0100) Subject: MINOR: cli: implement basic mechanism for uninterruptible jobs X-Git-Url: https://git.kaiwu.me/$%7BGITURL%7D$%7Bcid%7D?a=commitdiff_plain;h=105d93dbda38a4a6a3fb7331dd69acb1783d39af;p=haproxy.git MINOR: cli: implement basic mechanism for uninterruptible jobs 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. --- diff --git a/doc/management.txt b/doc/management.txt index 4520745a1..8f0682fef 100644 --- a/doc/management.txt +++ b/doc/management.txt @@ -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. diff --git a/include/haproxy/applet-t.h b/include/haproxy/applet-t.h index 9a9268f98..8aaa9a3e0 100644 --- a/include/haproxy/applet-t.h +++ b/include/haproxy/applet-t.h @@ -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 */ + 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 */ diff --git a/include/haproxy/cli-t.h b/include/haproxy/cli-t.h index 331e3ede7..833d73a5a 100644 --- a/include/haproxy/cli-t.h +++ b/include/haproxy/cli-t.h @@ -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 ! */ diff --git a/include/haproxy/cli.h b/include/haproxy/cli.h index 4d94ea6fb..6627646ff 100644 --- a/include/haproxy/cli.h +++ b/include/haproxy/cli.h @@ -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); diff --git a/src/cli.c b/src/cli.c index edd20b3a6..edf43b185 100644 --- 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);