mirror of
https://github.com/haproxy/haproxy.git
synced 2026-02-03 20:39:41 -05:00
Since 3.0 where the CLI started to use rcv_buf, it appears that some
external tools sending chained commands are randomly experiencing
failures. Each time this happens when the whole command is sent as a
single packet, immediately followed by a close. This is not a correct
way to use the CLI but this has been working for ages for simple
netcat-based scripts, so we should at least try to preserve this.
The cause of the failure is that the first LF that acks a command is
immediately sent back to the client and rejected due to the closed
connection. This in turn forwards the error back to the applet which
aborts its processing.
Before 3.0 the responses would be queued into the buffer, then sent
back to the channel, and would all fail at once. This changed when
snd_buf/rcv_buf were implemented because the applets are much more
responsive and since they yield between each command, they can
deliver one ACK at a time that is immediately forwarded down the
chain.
An easy way to observe the problem is to send 5 map updates, a shutdown,
and immediately close via tcploop, and in parallel run a periodic
"show map" to count the number of elements:
$ tcploop -U /tmp/sock1 C S:"add map #0 1 1; add map #0 2 2; add map #0 3 3; add map #0 4 4; add map #0 5 5\n" F K
Before 3.0, there would always be 5 elements. Since 3.0 and before
20ec1de214 ("MAJOR: cli: Refacor parsing and execution of pipelined
commands"), almost always 2. And since that commit above in 3.2, almost
always one. Doing the same using socat or netcat shows almost always 5...
It's entirely timing-dependent, and might even vary based on the RTT
between the client and haproxy!
The approach taken here consists in doing the same principle as MSG_MORE
or Nagle but on the response buffer: the applet doesn't need to send a
single ACK for each command when it has already been woken up and is
scheduled to come back to work. It's fine (and even desirable) that
ACKs are grouped in a single packet as much as possible.
For this reason, this patch implements APPCTX_CLI_ST1_YIELD, a new CLI
flag which indicates that the applet left in yielding condition, i.e.
it has not finished its work. This flag is used by .rcv_buf to hold
pending data. This way we won't return partial responses for no reason,
and we can continue to emulate the previous behavior.
One very nice benefit to this is that it saves huge amounts of CPU on
the client. In the test below that tries to update 1M map entries, the
CPU used by socat went from 100% to 0% and the total transfer time
dropped by 28%:
before:
$ time awk 'BEGIN{ printf "prompt i\n"; for (i=0;i<1000000;i++) { \
printf "add map #0 %d %d\n",i,i,i }}' | socat /tmp/sock1 - >/dev/null
real 0m2.407s
user 0m1.485s
sys 0m1.682s
after:
$ time awk 'BEGIN{ printf "prompt i\n"; for (i=0;i<1000000;i++) { \
printf "add map #0 %d %d\n",i,i,i }}' | socat /tmp/sock1 - >/dev/null
real 0m1.721s
user 0m0.952s
sys 0m0.057s
The difference is also quite visible on the number of syscalls during
the test (for 1k updates):
before:
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.071691 0 100001 sendmsg
after:
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00 0.000011 1 9 sendmsg
This patch will need to be backported to 3.0, and depends on these two
patches to be backported as well:
MINOR: applet: do not put SE_FL_WANT_ROOM on rcv_buf() if the channel is empty
MINOR: cli: create cli_raw_rcv_buf() from the generic applet_raw_rcv_buf()
129 lines
5.4 KiB
C
129 lines
5.4 KiB
C
/*
|
|
* include/haproxy/cli-t.h
|
|
* This file provides structures and types for CLI.
|
|
*
|
|
* Copyright (C) 2000-2020 Willy Tarreau - w@1wt.eu
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation, version 2.1
|
|
* exclusively.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#ifndef _HAPROXY_CLI_T_H
|
|
#define _HAPROXY_CLI_T_H
|
|
|
|
#include <haproxy/applet-t.h>
|
|
|
|
/* Access level for a stats socket (appctx->cli_ctx.level) */
|
|
#define ACCESS_LVL_NONE 0x0000
|
|
#define ACCESS_LVL_USER 0x0001
|
|
#define ACCESS_LVL_OPER 0x0002
|
|
#define ACCESS_LVL_ADMIN 0x0003
|
|
#define ACCESS_LVL_MASK 0x0003
|
|
|
|
#define ACCESS_FD_LISTENERS 0x0004 /* expose listeners FDs on stats socket */
|
|
#define ACCESS_MASTER 0x0008 /* works with the master (and every other processes) */
|
|
#define ACCESS_MASTER_ONLY 0x0010 /* only works with the master */
|
|
#define ACCESS_EXPERT 0x0020 /* access to dangerous commands reserved to experts */
|
|
#define ACCESS_EXPERIMENTAL 0x0040
|
|
#define ACCESS_MCLI_DEBUG 0x0080 /* allow the master CLI to use any command without the flag ACCESS_MASTER */
|
|
#define ACCESS_MCLI_SEVERITY_NB 0x0100 /* 'set severity-output number' on master CLI */
|
|
#define ACCESS_MCLI_SEVERITY_STR 0x0200 /* 'set severity-output string' on master CLI */
|
|
|
|
/* flags for appctx->st1 */
|
|
#define APPCTX_CLI_ST1_PAYLOAD (1 << 0)
|
|
#define APPCTX_CLI_ST1_NOLF (1 << 1)
|
|
#define APPCTX_CLI_ST1_LASTCMD (1 << 2)
|
|
#define APPCTX_CLI_ST1_INTER (1 << 3) /* interactive mode (i.e. don't close after 1st cmd) */
|
|
#define APPCTX_CLI_ST1_PROMPT (1 << 4) /* display prompt */
|
|
#define APPCTX_CLI_ST1_TIMED (1 << 5) /* display timer in prompt */
|
|
#define APPCTX_CLI_ST1_YIELD (1 << 6) /* forced yield between commands */
|
|
|
|
#define CLI_PREFIX_KW_NB 5
|
|
#define CLI_MAX_MATCHES 5
|
|
#define CLI_MAX_HELP_ENTRIES 1024
|
|
|
|
/* CLI states */
|
|
enum {
|
|
CLI_ST_INIT = 0, /* initial state, must leave to zero ! */
|
|
CLI_ST_END, /* final state, let's close */
|
|
CLI_ST_PARSE_CMDLINE, /* wait for a full command line */
|
|
CLI_ST_PROCESS_CMDLINE, /* process all commands on the command line */
|
|
CLI_ST_OUTPUT, /* all states after this one are responses */
|
|
CLI_ST_PROMPT, /* display the prompt (first output, same code) */
|
|
CLI_ST_PRINT, /* display const message in cli->msg */
|
|
CLI_ST_PRINT_ERR, /* display const error in cli->msg */
|
|
CLI_ST_PRINT_DYN, /* display dynamic message in cli->err. After the display, free the pointer */
|
|
CLI_ST_PRINT_DYNERR, /* display dynamic error in cli->err. After the display, free the pointer */
|
|
CLI_ST_PRINT_UMSG, /* display usermsgs_ctx buffer. After the display, usermsgs_ctx is reset. */
|
|
CLI_ST_PRINT_UMSGERR, /* display usermsgs_ctx buffer as error. After the display, usermsgs_ctx is reset. */
|
|
CLI_ST_CALLBACK, /* custom callback pointer */
|
|
};
|
|
|
|
/* CLI severity output formats */
|
|
enum {
|
|
CLI_SEVERITY_UNDEFINED = 0, /* undefined severity format */
|
|
CLI_SEVERITY_NONE, /* no severity information prepended */
|
|
CLI_SEVERITY_NUMBER, /* prepend informational cli messages with a severity as number */
|
|
CLI_SEVERITY_STRING, /* prepend informational cli messages with a severity as string */
|
|
};
|
|
|
|
/* CLI context for printing command responses. */
|
|
struct cli_print_ctx {
|
|
const char *msg; /* pointer to a persistent message to be returned in CLI_ST_PRINT state */
|
|
char *err; /* pointer to a 'must free' message to be returned in CLI_ST_PRINT_DYN state */
|
|
int severity; /* severity of the message to be returned according to (syslog) rfc5424 */
|
|
};
|
|
|
|
/* context for the "wait" command that's used to wait for some time on a
|
|
* condition. We store the start date and the expiration date. The error
|
|
* value is set by the I/O handler to be printed by the release handler at
|
|
* the end.
|
|
*/
|
|
enum cli_wait_err {
|
|
CLI_WAIT_ERR_DONE, // condition satisfied
|
|
CLI_WAIT_ERR_INTR, // interrupted
|
|
CLI_WAIT_ERR_EXP, // finished on wait expiration
|
|
CLI_WAIT_ERR_FAIL, // finished early (unrecoverable)
|
|
};
|
|
|
|
enum cli_wait_cond {
|
|
CLI_WAIT_COND_NONE, // no condition to wait on
|
|
CLI_WAIT_COND_SRV_UNUSED,// wait for server to become unused
|
|
};
|
|
|
|
struct cli_wait_ctx {
|
|
uint start, deadline; // both are in ticks.
|
|
enum cli_wait_cond cond; // CLI_WAIT_COND_*
|
|
enum cli_wait_err error; // CLI_WAIT_ERR_*
|
|
char *args[4]; // up to 4 args taken at parse time, all strduped
|
|
const char *msg; // static error message for failures if not NULL
|
|
};
|
|
|
|
struct cli_kw {
|
|
const char *str_kw[CLI_PREFIX_KW_NB]; /* keywords ended by NULL, limited to CLI_PREFIX_KW_NB
|
|
separated keywords combination */
|
|
const char *usage; /* usage message */
|
|
int (*parse)(char **args, char *payload, struct appctx *appctx, void *private);
|
|
int (*io_handler)(struct appctx *appctx);
|
|
void (*io_release)(struct appctx *appctx);
|
|
void *private;
|
|
int level; /* this is the level needed to show the keyword usage and to use it */
|
|
};
|
|
|
|
struct cli_kw_list {
|
|
struct list list;
|
|
struct cli_kw kw[VAR_ARRAY];
|
|
};
|
|
|
|
#endif /* _HAPROXY_CLI_T_H */
|