From 4a9b4f667cdf4caf53e5ef3a032801819b2dc9a4 Mon Sep 17 00:00:00 2001
From: "zhaozhao.zz" <zhaozhao.zz@alibaba-inc.com>
Date: Thu, 29 Aug 2024 19:20:53 +0800
Subject: [PATCH] free client's multi state when it becomes dirty (#961)

Release the client's MULTI state when the transaction becomes dirty to
save memory.

---------

Signed-off-by: zhaozhao.zz <zhaozhao.zz@alibaba-inc.com>
---
 src/multi.c | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/src/multi.c b/src/multi.c
index 14688f80b..bcffb9091 100644
--- a/src/multi.c
+++ b/src/multi.c
@@ -55,6 +55,13 @@ void freeClientMultiState(client *c) {
     zfree(c->mstate.commands);
 }
 
+void resetClientMultiState(client *c) {
+    if (c->mstate.commands) {
+        freeClientMultiState(c);
+        initClientMultiState(c);
+    }
+}
+
 /* Add a new command into the MULTI commands queue */
 void queueMultiCommand(client *c, uint64_t cmd_flags) {
     multiCmd *mc;
@@ -94,8 +101,7 @@ void queueMultiCommand(client *c, uint64_t cmd_flags) {
 }
 
 void discardTransaction(client *c) {
-    freeClientMultiState(c);
-    initClientMultiState(c);
+    resetClientMultiState(c);
     c->flag.multi = 0;
     c->flag.dirty_cas = 0;
     c->flag.dirty_exec = 0;
@@ -105,7 +111,10 @@ void discardTransaction(client *c) {
 /* Flag the transaction as DIRTY_EXEC so that EXEC will fail.
  * Should be called every time there is an error while queueing a command. */
 void flagTransaction(client *c) {
-    if (c->flag.multi) c->flag.dirty_exec = 1;
+    if (c->flag.multi) {
+        c->flag.dirty_exec = 1;
+        resetClientMultiState(c);
+    }
 }
 
 void multiCommand(client *c) {
@@ -391,6 +400,7 @@ void touchWatchedKey(serverDb *db, robj *key) {
         }
 
         c->flag.dirty_cas = 1;
+        resetClientMultiState(c);
         /* As the client is marked as dirty, there is no point in getting here
          * again in case that key (or others) are modified again (or keep the
          * memory overhead till EXEC). */
@@ -442,6 +452,7 @@ void touchAllWatchedKeysInDb(serverDb *emptied, serverDb *replaced_with) {
                 }
                 client *c = wk->client;
                 c->flag.dirty_cas = 1;
+                resetClientMultiState(c);
                 /* Note - we could potentially call unwatchAllKeys for this specific client in order to reduce
                  * the total number of iterations. BUT this could also free the current next entry pointer
                  * held by the iterator and can lead to use-after-free. */