Fix issue with argv not being shrunk (#13698)

Found by @ShooterIT 

## Describe
If a client first creates a command with a very large number of
parameters, such as 10,000 parameters, the argv will be expanded to
accommodate 10,000. If the subsequent commands have fewer than 10,000
parameters, this argv will continue to be reused and will never be
shrunk.

## Solution
When determining whether it is necessary to rebuild argv, if the length
of the previous argv has already exceeded 1024, we will progressively
create argv regardless.

## Free argv in cron
Add a new condition to determine whether argv needs to be resized in
cron. When the number of parameters exceeds 128, we will resize it
regardless to avoid a single client consuming too much memory. It will
now occupy a maximum of (128 * 8 bytes).

---------

Co-authored-by: Yuan Wang <wangyuancode@163.com>
This commit is contained in:
debing.sun 2025-01-08 16:12:52 +08:00 committed by GitHub
parent 08d714d0e5
commit 21aee83abd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 10 additions and 5 deletions

View File

@ -2428,8 +2428,12 @@ int processMultibulkBuffer(client *c) {
c->multibulklen = ll;
/* Setup argv array on client structure.
* Create new argv if space is insufficient or if we need to allocate it gradually. */
if (unlikely(c->multibulklen > c->argv_len || c->multibulklen > 1024)) {
* Create new argv in the following cases:
* 1) When the requested size is greater than the current size.
* 2) When the requested size is less than the current size, because
* we always allocate argv gradually with a maximum size of 1024,
* Therefore, if argv_len exceeds this limit, we always reallocate. */
if (unlikely(c->multibulklen > c->argv_len || c->argv_len > 1024)) {
zfree(c->argv);
c->argv_len = min(c->multibulklen, 1024);
c->argv = zmalloc(sizeof(robj*)*c->argv_len);

View File

@ -791,8 +791,11 @@ int clientsCronFreeArgvIfIdle(client *c) {
/* If the client is in the middle of parsing a command, or if argv is in use
* (e.g. parsed in the IO thread but not yet executed, or blocked), exit ASAP. */
if (!c->argv || c->multibulklen || c->argc) return 0;
/* Free argv if the client has been idle for more than 2 seconds or if argv
* size is too large. */
time_t idletime = server.unixtime - c->lastinteraction;
if (idletime > 2) {
if (idletime > 2 || c->argv_len > 128) {
c->argv_len = 0;
zfree(c->argv);
c->argv = NULL;

View File

@ -10,7 +10,6 @@ start_server {tags {"lazyfree"}} {
set peak_mem [s used_memory]
assert {[r unlink myset] == 1}
assert {$peak_mem > $orig_mem+1000000}
reconnect ;# free the memory of reused argv of client
wait_for_condition 50 100 {
[s used_memory] < $peak_mem &&
[s used_memory] < $orig_mem*2
@ -33,7 +32,6 @@ start_server {tags {"lazyfree"}} {
set peak_mem [s used_memory]
r flushdb async
assert {$peak_mem > $orig_mem+1000000}
reconnect ;# free the memory of reused argv of client
wait_for_condition 50 100 {
[s used_memory] < $peak_mem &&
[s used_memory] < $orig_mem*2