BUG/MINOR: cli: avoid O(bufsize) parsing cost on pipelined commands

Sending pipelined commands on the CLI using a semi-colon as a delimiter
has a cost that grows linearly with the buffer size, because co_getline()
is called for each word and looks up a '\n' in the whole buffer while
copying its contents into a temporary buffer.

This causes huge parsing delays, for example 3s for 100k "show version"
versus 110ms if parsed only once for a default 16k buffer.

This patch makes use of the new co_getdelim() function to support both
an LF and a semi-colon as delimiters so that it's no more needed to parse
the whole buffer, and that commands are instantly retrieved. We still
need to rely on co_getline() in payload mode as escapes and semi-colons
are not used there.

It should likely be backported where CLI processing speed matters, but
will require to also backport previous patch "MINOR: channel: add new
function co_getdelim() to support multiple delimiters". It's worth noting
that backporting it without "MEDIUM: cli: yield between each pipelined
command" would significantly increase the ratio of disconnections caused
by empty request buffers, for the sole reason that the currently slow
parsing grants more time to request data to come in. As such it would
be better to backport the patch above before taking this one.
This commit is contained in:
Willy Tarreau 2022-01-19 17:23:52 +01:00
parent c514365317
commit 0011c25144

View File

@ -890,9 +890,20 @@ static void cli_io_handler(struct appctx *appctx)
break;
}
/* '- 1' is to ensure a null byte can always be inserted at the end */
reql = co_getline(si_oc(si), str,
appctx->chunk->size - appctx->chunk->data - 1);
/* payload doesn't take escapes nor does it end on semi-colons, so
* we use the regular getline. Normal mode however must stop on
* LFs and semi-colons that are not prefixed by a backslash. Note
* that we reserve one byte at the end to insert a trailing nul byte.
*/
if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)
reql = co_getline(si_oc(si), str,
appctx->chunk->size - appctx->chunk->data - 1);
else
reql = co_getdelim(si_oc(si), str,
appctx->chunk->size - appctx->chunk->data - 1,
"\n;", '\\');
if (reql <= 0) { /* closed or EOL not found */
if (reql == 0)
break;