From 42518fbfd71a8426fd8e3f3b0bea8567c3fff162 Mon Sep 17 00:00:00 2001 From: Patrick Ribbsaeter Date: Tue, 27 Oct 2026 03:22:57 +0100 Subject: [PATCH] Avoid preallocating fat querybuf after big-arg steal (#4977) After stealing a bulk argument >= PROTO_MBULK_BIG_ARG (32 KiB) into argv, parseMultibulk() previously preallocated a new querybuf of size bulklen + 2. Because readToQueryBuf() performs a greedy read into any buffer with sdsalloc >= PROTO_IOBUF_LEN (16 KiB), subsequent commands read both the RESP header and up to ~bulklen bytes of the next value in a single read, forcing sdsrange() to memmove almost the entire payload back to offset 0 before stealing the buffer. Assign sdsempty() after stealing c->querybuf so the next command starts with a non-greedy PROTO_IOBUF_LEN read, bounding the header realignment memmove to at most 16 KiB while preserving sdsMakeRoomForNonGreedy() allocation for the remainder of the big argument. Signed-off-by: Patrick Ribbsaeter --- src/networking.c | 5 +---- tests/unit/querybuf.tcl | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/networking.c b/src/networking.c index 134f54d..b51df12 100644 --- a/src/networking.c +++ b/src/networking.c @@ -4336,10 +4336,7 @@ static int parseMultibulk(client *c, (*argv)[(*argc)++] = createObject(OBJ_STRING, c->querybuf); *argv_len_sum += c->bulklen; sdsIncrLen(c->querybuf, -2); /* remove CRLF */ - /* Assume that if we saw a fat argument we'll see another one - * likely... */ - c->querybuf = sdsnewlen(SDS_NOINIT, c->bulklen + 2); - sdsclear(c->querybuf); + c->querybuf = sdsempty(); } else { (*argv)[(*argc)++] = createStringObject(c->querybuf + c->qb_pos, c->bulklen); *argv_len_sum += c->bulklen; diff --git a/tests/unit/querybuf.tcl b/tests/unit/querybuf.tcl index f0f432b..5c11e70 100644 --- a/tests/unit/querybuf.tcl +++ b/tests/unit/querybuf.tcl @@ -78,7 +78,9 @@ start_server {tags {"querybuf slow"}} { $rd client setname test_client # Create a large query buffer (more than PROTO_RESIZE_THRESHOLD - 32k) - $rd set x [string repeat A 400000] + # using an argument below PROTO_MBULK_BIG_ARG (32k) so the buffer grows + # greedily past 32k without being stolen by the big-arg zero-copy path. + $rd set x [string repeat A 30000] # Make sure query buff is larger than the peak resize threshold (PROTO_RESIZE_THRESHOLD) 32k set orig_test_client_qbuf [client_query_buffer test_client] @@ -127,4 +129,27 @@ start_server {tags {"querybuf slow"}} { $rd close } + test "query buffer is not preallocated after big arg steal" { + r debug pause-cron 1 + set rd [valkey_client] + $rd client setname test_client + + # A bulk argument >= PROTO_MBULK_BIG_ARG (32k) is stolen into argv and + # replaced with an empty sds rather than preallocating bulklen+2 bytes. + set big [string repeat A 400000] + assert_equal {OK} [$rd set x $big] + assert_equal $big [r get x] + assert {[client_query_buffer test_client] < 16384} + + # Back-to-back big arg commands continue to steal cleanly without + # retaining a fat query buffer. + set big2 [string repeat B 400000] + assert_equal {OK} [$rd set x $big2] + assert_equal $big2 [r get x] + assert {[client_query_buffer test_client] < 16384} + + r debug pause-cron 0 + $rd close + } {0} {needs:debug} + } -- 2.55.0.windows.3