Repository: redis/redis

Problem statement:
[BUG] EVAL "return _G" 0  leads to immediate panic
**Describe the bug**

redis-server crashes with message:

`PANIC: unprotected error in call to Lua API (Script attempted to access nonexistent global variable 'err')`

**To reproduce**

```
$ redis-cli -h localhost
localhost:6379> EVAL "return _G" 0
Error: Server closed the connection
```

**Expected behavior**

Server just returns error

**Additional information**

The problem reproduced on version 6.2 and 7.0.4 on x86_64

[BUG] Mem-leak in moduleFreeCommand
currently, we call `zfree(cmd->args);` but the argument array needs to be freed recursively (there might be sub-args)

valgrind should have caught this when unloading cmdintrospection.so


Hints:

Patch:
diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml
index f524d65238f..3afe8b935a9 100644
--- a/.github/workflows/daily.yml
+++ b/.github/workflows/daily.yml
@@ -674,7 +674,7 @@ jobs:
       run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
 
   test-freebsd:
-    runs-on: macos-10.15
+    runs-on: macos-12
     if: |
       (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
       !contains(github.event.inputs.skipjobs, 'freebsd') && !(contains(github.event.inputs.skiptests, 'redis') && contains(github.event.inputs.skiptests, 'modules'))
@@ -690,7 +690,7 @@ jobs:
         repository: ${{ env.GITHUB_REPOSITORY }}
         ref: ${{ env.GITHUB_HEAD_REF }}
     - name: test
-      uses: vmactions/freebsd-vm@v0.1.6
+      uses: vmactions/freebsd-vm@v0.2.4
       with:
         usesh: true
         sync: rsync
@@ -702,7 +702,7 @@ jobs:
           if echo "${{github.event.inputs.skiptests}}" | grep -vq modules ; then MAKE=gmake ./runtest-moduleapi --verbose --timeout 2400 --no-latency --dump-logs ${{github.event.inputs.test_args}} || exit 1 ; fi ;
 
   test-freebsd-sentinel:
-    runs-on: macos-10.15
+    runs-on: macos-12
     if: |
       (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
       !contains(github.event.inputs.skipjobs, 'freebsd') && !contains(github.event.inputs.skiptests, 'sentinel')
@@ -718,7 +718,7 @@ jobs:
         repository: ${{ env.GITHUB_REPOSITORY }}
         ref: ${{ env.GITHUB_HEAD_REF }}
     - name: test
-      uses: vmactions/freebsd-vm@v0.1.6
+      uses: vmactions/freebsd-vm@v0.2.4
       with:
         usesh: true
         sync: rsync
@@ -729,7 +729,7 @@ jobs:
           if echo "${{github.event.inputs.skiptests}}" | grep -vq sentinel ; then ./runtest-sentinel ${{github.event.inputs.cluster_test_args}} || exit 1 ; fi ;
 
   test-freebsd-cluster:
-    runs-on: macos-10.15
+    runs-on: macos-12
     if: |
       (github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
       !contains(github.event.inputs.skipjobs, 'freebsd') && !contains(github.event.inputs.skiptests, 'cluster')
@@ -745,7 +745,7 @@ jobs:
         repository: ${{ env.GITHUB_REPOSITORY }}
         ref: ${{ env.GITHUB_HEAD_REF }}
     - name: test
-      uses: vmactions/freebsd-vm@v0.1.6
+      uses: vmactions/freebsd-vm@v0.2.4
       with:
         usesh: true
         sync: rsync
diff --git a/00-RELEASENOTES b/00-RELEASENOTES
index a8f766b1c98..0ff3cd08e38 100644
--- a/00-RELEASENOTES
+++ b/00-RELEASENOTES
@@ -12,6 +12,49 @@ SECURITY: There are security fixes in the release.
 --------------------------------------------------------------------------------
 
 
+================================================================================
+Redis 7.0.5 Released Wed Sep 21 20:00:00 IST 2022
+================================================================================
+
+Upgrade urgency: SECURITY, contains fixes to security issues.
+
+Security Fixes:
+* (CVE-2022-35951) Executing a XAUTOCLAIM command on a stream key in a specific
+  state, with a specially crafted COUNT argument, may cause an integer overflow,
+  a subsequent heap overflow, and potentially lead to remote code execution.
+  The problem affects Redis versions 7.0.0 or newer
+  [reported by Xion (SeungHyun Lee) of KAIST GoN].
+
+Module API changes
+==================
+
+* Fix RM_Call execution of scripts when used with M/W/S flags to properly
+  handle script flags (#11159)
+* Fix RM_SetAbsExpire and RM_GetAbsExpire API registration (#11025, #8564)
+
+Bug Fixes
+=========
+
+* Fix a hang when eviction is combined with lazy-free and maxmemory-eviction-tenacity is set to 100 (#11237)
+* Fix a crash when a replica may attempt to set itself as its master as a result of a manual failover (#11263)
+* Fix a bug where a cluster-enabled replica node may permanently set its master's hostname to '?' (#10696)
+* Fix a crash when a Lua script returns a meta-table (#11032)
+
+Fixes for issues in previous releases of Redis 7.0
+--------------------------------------------------
+
+* Fix redis-cli to do DNS lookup before sending CLUSTER MEET (#11151)
+* Fix crash when a key is lazy expired during cluster key migration (#11176)
+* Fix AOF rewrite to fsync the old AOF file when a new one is created (#11004)
+* Fix some crashes involving a list containing entries larger than 1GB (#11242)
+* Correctly handle scripts with a non-read-only shebang on a cluster replica (#11223)
+* Fix memory leak when unloading a module (#11147)
+* Fix bug with scripts ignoring client tracking NOLOOP (#11052)
+* Fix client-side tracking breaking protocol when FLUSHDB / FLUSHALL / SWAPDB is used inside MULTI-EXEC (#11038)
+* Fix ACL: BITFIELD with GET and also SET / INCRBY can be executed with read-only key permission (#11086)
+* Fix missing sections for INFO ALL when also requesting a module info section (#11291)
+
+
 ================================================================================
 Redis 7.0.4 Released Monday Jul 18 12:00:00 IST 2022
 ================================================================================
diff --git a/src/aof.c b/src/aof.c
index 150404bc346..482ec786dc1 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -49,6 +49,7 @@ int aofFileExist(char *filename);
 int rewriteAppendOnlyFile(char *filename);
 aofManifest *aofLoadManifestFromFile(sds am_filepath);
 void aofManifestFreeAndUpdate(aofManifest *am);
+void aof_background_fsync_and_close(int fd);
 
 /* ----------------------------------------------------------------------------
  * AOF Manifest file implementation.
@@ -826,8 +827,14 @@ int openNewIncrAofForAppend(void) {
     /* If reaches here, we can safely modify the `server.aof_manifest`
      * and `server.aof_fd`. */
 
-    /* Close old aof_fd if needed. */
-    if (server.aof_fd != -1) bioCreateCloseJob(server.aof_fd);
+    /* fsync and close old aof_fd if needed. In fsync everysec it's ok to delay
+     * the fsync as long as we grantee it happens, and in fsync always the file
+     * is already synced at this point so fsync doesn't matter. */
+    if (server.aof_fd != -1) {
+        aof_background_fsync_and_close(server.aof_fd);
+        server.aof_fsync_offset = server.aof_current_size;
+        server.aof_last_fsync = server.unixtime;
+    }
     server.aof_fd = newfd;
 
     /* Reset the aof_last_incr_size. */
@@ -904,6 +911,9 @@ int aofRewriteLimited(void) {
 /* Return true if an AOf fsync is currently already in progress in a
  * BIO thread. */
 int aofFsyncInProgress(void) {
+    /* Note that we don't care about aof_background_fsync_and_close because
+     * server.aof_fd has been replaced by the new INCR AOF file fd,
+     * see openNewIncrAofForAppend. */
     return bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
 }
 
@@ -913,6 +923,11 @@ void aof_background_fsync(int fd) {
     bioCreateFsyncJob(fd);
 }
 
+/* Close the fd on the basis of aof_background_fsync. */
+void aof_background_fsync_and_close(int fd) {
+    bioCreateCloseJob(fd, 1);
+}
+
 /* Kills an AOFRW child process if exists */
 void killAppendOnlyChild(void) {
     int statloc;
diff --git a/src/bio.c b/src/bio.c
index f023890195f..9242e51ed14 100644
--- a/src/bio.c
+++ b/src/bio.c
@@ -76,12 +76,19 @@ static unsigned long long bio_pending[BIO_NUM_OPS];
 
 /* This structure represents a background Job. It is only used locally to this
  * file as the API does not expose the internals at all. */
-struct bio_job {
+typedef union bio_job {
     /* Job specific arguments.*/
-    int fd; /* Fd for file based background jobs */
-    lazy_free_fn *free_fn; /* Function that will free the provided arguments */
-    void *free_args[]; /* List of arguments to be passed to the free function */
-};
+    struct {
+        int fd; /* Fd for file based background jobs */
+        unsigned need_fsync:1; /* A flag to indicate that a fsync is required before
+                                * the file is closed. */
+    } fd_args;
+
+    struct {
+        lazy_free_fn *free_fn; /* Function that will free the provided arguments */
+        void *free_args[]; /* List of arguments to be passed to the free function */
+    } free_args;
+} bio_job;
 
 void *bioProcessBackgroundJobs(void *arg);
 
@@ -125,7 +132,7 @@ void bioInit(void) {
     }
 }
 
-void bioSubmitJob(int type, struct bio_job *job) {
+void bioSubmitJob(int type, bio_job *job) {
     pthread_mutex_lock(&bio_mutex[type]);
     listAddNodeTail(bio_jobs[type],job);
     bio_pending[type]++;
@@ -137,33 +144,34 @@ void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...) {
     va_list valist;
     /* Allocate memory for the job structure and all required
      * arguments */
-    struct bio_job *job = zmalloc(sizeof(*job) + sizeof(void *) * (arg_count));
-    job->free_fn = free_fn;
+    bio_job *job = zmalloc(sizeof(*job) + sizeof(void *) * (arg_count));
+    job->free_args.free_fn = free_fn;
 
     va_start(valist, arg_count);
     for (int i = 0; i < arg_count; i++) {
-        job->free_args[i] = va_arg(valist, void *);
+        job->free_args.free_args[i] = va_arg(valist, void *);
     }
     va_end(valist);
     bioSubmitJob(BIO_LAZY_FREE, job);
 }
 
-void bioCreateCloseJob(int fd) {
-    struct bio_job *job = zmalloc(sizeof(*job));
-    job->fd = fd;
+void bioCreateCloseJob(int fd, int need_fsync) {
+    bio_job *job = zmalloc(sizeof(*job));
+    job->fd_args.fd = fd;
+    job->fd_args.need_fsync = need_fsync;
 
     bioSubmitJob(BIO_CLOSE_FILE, job);
 }
 
 void bioCreateFsyncJob(int fd) {
-    struct bio_job *job = zmalloc(sizeof(*job));
-    job->fd = fd;
+    bio_job *job = zmalloc(sizeof(*job));
+    job->fd_args.fd = fd;
 
     bioSubmitJob(BIO_AOF_FSYNC, job);
 }
 
 void *bioProcessBackgroundJobs(void *arg) {
-    struct bio_job *job;
+    bio_job *job;
     unsigned long type = (unsigned long) arg;
     sigset_t sigset;
 
@@ -216,12 +224,15 @@ void *bioProcessBackgroundJobs(void *arg) {
 
         /* Process the job accordingly to its type. */
         if (type == BIO_CLOSE_FILE) {
-            close(job->fd);
+            if (job->fd_args.need_fsync) {
+                redis_fsync(job->fd_args.fd);
+            }
+            close(job->fd_args.fd);
         } else if (type == BIO_AOF_FSYNC) {
             /* The fd may be closed by main thread and reused for another
              * socket, pipe, or file. We just ignore these errno because
              * aof fsync did not really fail. */
-            if (redis_fsync(job->fd) == -1 &&
+            if (redis_fsync(job->fd_args.fd) == -1 &&
                 errno != EBADF && errno != EINVAL)
             {
                 int last_status;
@@ -236,7 +247,7 @@ void *bioProcessBackgroundJobs(void *arg) {
                 atomicSet(server.aof_bio_fsync_status,C_OK);
             }
         } else if (type == BIO_LAZY_FREE) {
-            job->free_fn(job->free_args);
+            job->free_args.free_fn(job->free_args.free_args);
         } else {
             serverPanic("Wrong job type in bioProcessBackgroundJobs().");
         }
diff --git a/src/bio.h b/src/bio.h
index 9eff6d16ed2..4a4c98ad020 100644
--- a/src/bio.h
+++ b/src/bio.h
@@ -37,7 +37,7 @@ void bioInit(void);
 unsigned long long bioPendingJobsOfType(int type);
 unsigned long long bioWaitStepOfType(int type);
 void bioKillThreads(void);
-void bioCreateCloseJob(int fd);
+void bioCreateCloseJob(int fd, int need_fsync);
 void bioCreateFsyncJob(int fd);
 void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...);
 
diff --git a/src/cluster.c b/src/cluster.c
index ae353e1dd93..5719ea0b8a7 100644
--- a/src/cluster.c
+++ b/src/cluster.c
@@ -813,10 +813,15 @@ void setClusterNodeToInboundClusterLink(clusterNode *node, clusterLink *link) {
         /* A peer may disconnect and then reconnect with us, and it's not guaranteed that
          * we would always process the disconnection of the existing inbound link before
          * accepting a new existing inbound link. Therefore, it's possible to have more than
-         * one inbound link from the same node at the same time. */
+         * one inbound link from the same node at the same time. Our cleanup logic assumes
+         * a one to one relationship between nodes and inbound links, so we need to kill
+         * one of the links. The existing link is more likely the outdated one, but it's
+         * possible the the other node may need to open another link. */
         serverLog(LL_DEBUG, "Replacing inbound link fd %d from node %.40s with fd %d",
                 node->inbound_link->conn->fd, node->name, link->conn->fd);
+        freeClusterLink(node->inbound_link);
     }
+    serverAssert(!node->inbound_link);
     node->inbound_link = link;
     link->node = node;
 }
@@ -1747,12 +1752,18 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) {
 /* IP -> string conversion. 'buf' is supposed to at least be 46 bytes.
  * If 'announced_ip' length is non-zero, it is used instead of extracting
  * the IP from the socket peer address. */
-void nodeIp2String(char *buf, clusterLink *link, char *announced_ip) {
+int nodeIp2String(char *buf, clusterLink *link, char *announced_ip) {
     if (announced_ip[0] != '\0') {
         memcpy(buf,announced_ip,NET_IP_STR_LEN);
         buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */
+        return C_OK;
     } else {
-        connPeerToString(link->conn, buf, NET_IP_STR_LEN, NULL);
+        if (connPeerToString(link->conn, buf, NET_IP_STR_LEN, NULL) == C_ERR) {
+            serverLog(LL_NOTICE, "Error converting peer IP to string: %s",
+                link->conn ? connGetLastError(link->conn) : "no link");
+            return C_ERR;
+        }
+        return C_OK;
     }
 }
 
@@ -1784,7 +1795,11 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link,
      * it is safe to call during packet processing. */
     if (link == node->link) return 0;
 
-    nodeIp2String(ip,link,hdr->myip);
+    /* If the peer IP is unavailable for some reasons like invalid fd or closed
+     * link, just give up the update this time, and the update will be retried
+     * in the next round of PINGs */
+    if (nodeIp2String(ip,link,hdr->myip) == C_ERR) return 0;
+
     if (node->port == port && node->cport == cport && node->pport == pport &&
         strcmp(ip,node->ip) == 0) return 0;
 
@@ -1930,7 +1945,13 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
         clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|
                              CLUSTER_TODO_UPDATE_STATE|
                              CLUSTER_TODO_FSYNC_CONFIG);
-    } else if (myself->slaveof && myself->slaveof->slaveof) {
+    } else if (myself->slaveof && myself->slaveof->slaveof &&
+               /* In some rare case when CLUSTER FAILOVER TAKEOVER is used, it
+                * can happen that myself is a replica of a replica of myself. If
+                * this happens, we do nothing to avoid a crash and wait for the
+                * admin to repair the cluster. */
+               myself->slaveof->slaveof != myself)
+    {
         /* Safeguard against sub-replicas. A replica's master can turn itself
          * into a replica if its last slot is removed. If no other node takes
          * over the slot, there is nothing else to trigger replica migration. */
@@ -2237,7 +2258,7 @@ int clusterProcessPacket(clusterLink *link) {
             clusterNode *node;
 
             node = createClusterNode(NULL,CLUSTER_NODE_HANDSHAKE);
-            nodeIp2String(node->ip,link,hdr->myip);
+            serverAssert(nodeIp2String(node->ip,link,hdr->myip) == C_OK);
             node->port = ntohs(hdr->port);
             node->pport = ntohs(hdr->pport);
             node->cport = ntohs(hdr->cport);
@@ -6692,7 +6713,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
              * slot migration, the channel will be served from the source
              * node until the migration completes with CLUSTER SETSLOT <slot>
              * NODE <node-id>. */
-            int flags = LOOKUP_NOTOUCH | LOOKUP_NOSTATS | LOOKUP_NONOTIFY;
+            int flags = LOOKUP_NOTOUCH | LOOKUP_NOSTATS | LOOKUP_NONOTIFY | LOOKUP_NOEXPIRE;
             if ((migrating_slot || importing_slot) && !is_pubsubshard)
             {
                 if (lookupKeyReadWithFlags(&server.db[0], thiskey, flags) == NULL) missing_keys++;
@@ -6706,6 +6727,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
      * without redirections or errors in all the cases. */
     if (n == NULL) return myself;
 
+    uint64_t cmd_flags = getCommandFlags(c);
     /* Cluster is globally down but we got keys? We only serve the request
      * if it is a read command and when allow_reads_when_down is enabled. */
     if (server.cluster->state != CLUSTER_OK) {
@@ -6719,7 +6741,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
              * cluster is down. */
             if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
             return NULL;
-        } else if (cmd->flags & CMD_WRITE) {
+        } else if (cmd_flags & CMD_WRITE) {
             /* The cluster is configured to allow read only commands */
             if (error_code) *error_code = CLUSTER_REDIR_DOWN_RO_STATE;
             return NULL;
@@ -6757,7 +6779,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
      * involves multiple keys and we don't have them all, the only option is
      * to send a TRYAGAIN error. */
     if (importing_slot &&
-        (c->flags & CLIENT_ASKING || cmd->flags & CMD_ASKING))
+        (c->flags & CLIENT_ASKING || cmd_flags & CMD_ASKING))
     {
         if (multiple_keys && missing_keys) {
             if (error_code) *error_code = CLUSTER_REDIR_UNSTABLE;
@@ -6770,7 +6792,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
     /* Handle the read-only client case reading from a slave: if this
      * node is a slave and the request is about a hash slot our master
      * is serving, we can reply without redirection. */
-    int is_write_command = (c->cmd->flags & CMD_WRITE) ||
+    int is_write_command = (cmd_flags & CMD_WRITE) ||
                            (c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE));
     if (((c->flags & CLIENT_READONLY) || is_pubsubshard) &&
         !is_write_command &&
diff --git a/src/commands.c b/src/commands.c
index 63dfd92e37c..a6b26a65f22 100644
--- a/src/commands.c
+++ b/src/commands.c
@@ -229,10 +229,7 @@ struct redisCommandArg SETBIT_Args[] = {
 #define CLUSTER_ADDSLOTS_History NULL
 
 /* CLUSTER ADDSLOTS tips */
-const char *CLUSTER_ADDSLOTS_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_ADDSLOTS_tips NULL
 
 /* CLUSTER ADDSLOTS argument table */
 struct redisCommandArg CLUSTER_ADDSLOTS_Args[] = {
@@ -246,10 +243,7 @@ struct redisCommandArg CLUSTER_ADDSLOTS_Args[] = {
 #define CLUSTER_ADDSLOTSRANGE_History NULL
 
 /* CLUSTER ADDSLOTSRANGE tips */
-const char *CLUSTER_ADDSLOTSRANGE_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_ADDSLOTSRANGE_tips NULL
 
 /* CLUSTER ADDSLOTSRANGE start_slot_end_slot argument table */
 struct redisCommandArg CLUSTER_ADDSLOTSRANGE_start_slot_end_slot_Subargs[] = {
@@ -298,10 +292,7 @@ struct redisCommandArg CLUSTER_COUNT_FAILURE_REPORTS_Args[] = {
 #define CLUSTER_COUNTKEYSINSLOT_History NULL
 
 /* CLUSTER COUNTKEYSINSLOT tips */
-const char *CLUSTER_COUNTKEYSINSLOT_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_COUNTKEYSINSLOT_tips NULL
 
 /* CLUSTER COUNTKEYSINSLOT argument table */
 struct redisCommandArg CLUSTER_COUNTKEYSINSLOT_Args[] = {
@@ -315,10 +306,7 @@ struct redisCommandArg CLUSTER_COUNTKEYSINSLOT_Args[] = {
 #define CLUSTER_DELSLOTS_History NULL
 
 /* CLUSTER DELSLOTS tips */
-const char *CLUSTER_DELSLOTS_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_DELSLOTS_tips NULL
 
 /* CLUSTER DELSLOTS argument table */
 struct redisCommandArg CLUSTER_DELSLOTS_Args[] = {
@@ -332,10 +320,7 @@ struct redisCommandArg CLUSTER_DELSLOTS_Args[] = {
 #define CLUSTER_DELSLOTSRANGE_History NULL
 
 /* CLUSTER DELSLOTSRANGE tips */
-const char *CLUSTER_DELSLOTSRANGE_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_DELSLOTSRANGE_tips NULL
 
 /* CLUSTER DELSLOTSRANGE start_slot_end_slot argument table */
 struct redisCommandArg CLUSTER_DELSLOTSRANGE_start_slot_end_slot_Subargs[] = {
@@ -356,10 +341,7 @@ struct redisCommandArg CLUSTER_DELSLOTSRANGE_Args[] = {
 #define CLUSTER_FAILOVER_History NULL
 
 /* CLUSTER FAILOVER tips */
-const char *CLUSTER_FAILOVER_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_FAILOVER_tips NULL
 
 /* CLUSTER FAILOVER options argument table */
 struct redisCommandArg CLUSTER_FAILOVER_options_Subargs[] = {
@@ -380,10 +362,7 @@ struct redisCommandArg CLUSTER_FAILOVER_Args[] = {
 #define CLUSTER_FLUSHSLOTS_History NULL
 
 /* CLUSTER FLUSHSLOTS tips */
-const char *CLUSTER_FLUSHSLOTS_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_FLUSHSLOTS_tips NULL
 
 /********** CLUSTER FORGET ********************/
 
@@ -391,10 +370,7 @@ NULL
 #define CLUSTER_FORGET_History NULL
 
 /* CLUSTER FORGET tips */
-const char *CLUSTER_FORGET_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_FORGET_tips NULL
 
 /* CLUSTER FORGET argument table */
 struct redisCommandArg CLUSTER_FORGET_Args[] = {
@@ -445,10 +421,7 @@ NULL
 #define CLUSTER_KEYSLOT_History NULL
 
 /* CLUSTER KEYSLOT tips */
-const char *CLUSTER_KEYSLOT_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_KEYSLOT_tips NULL
 
 /* CLUSTER KEYSLOT argument table */
 struct redisCommandArg CLUSTER_KEYSLOT_Args[] = {
@@ -476,10 +449,7 @@ commandHistory CLUSTER_MEET_History[] = {
 };
 
 /* CLUSTER MEET tips */
-const char *CLUSTER_MEET_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_MEET_tips NULL
 
 /* CLUSTER MEET argument table */
 struct redisCommandArg CLUSTER_MEET_Args[] = {
@@ -495,10 +465,7 @@ struct redisCommandArg CLUSTER_MEET_Args[] = {
 #define CLUSTER_MYID_History NULL
 
 /* CLUSTER MYID tips */
-const char *CLUSTER_MYID_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_MYID_tips NULL
 
 /********** CLUSTER NODES ********************/
 
@@ -534,10 +501,7 @@ struct redisCommandArg CLUSTER_REPLICAS_Args[] = {
 #define CLUSTER_REPLICATE_History NULL
 
 /* CLUSTER REPLICATE tips */
-const char *CLUSTER_REPLICATE_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_REPLICATE_tips NULL
 
 /* CLUSTER REPLICATE argument table */
 struct redisCommandArg CLUSTER_REPLICATE_Args[] = {
@@ -551,10 +515,7 @@ struct redisCommandArg CLUSTER_REPLICATE_Args[] = {
 #define CLUSTER_RESET_History NULL
 
 /* CLUSTER RESET tips */
-const char *CLUSTER_RESET_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_RESET_tips NULL
 
 /* CLUSTER RESET hard_soft argument table */
 struct redisCommandArg CLUSTER_RESET_hard_soft_Subargs[] = {
@@ -575,10 +536,7 @@ struct redisCommandArg CLUSTER_RESET_Args[] = {
 #define CLUSTER_SAVECONFIG_History NULL
 
 /* CLUSTER SAVECONFIG tips */
-const char *CLUSTER_SAVECONFIG_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_SAVECONFIG_tips NULL
 
 /********** CLUSTER SET_CONFIG_EPOCH ********************/
 
@@ -586,10 +544,7 @@ NULL
 #define CLUSTER_SET_CONFIG_EPOCH_History NULL
 
 /* CLUSTER SET_CONFIG_EPOCH tips */
-const char *CLUSTER_SET_CONFIG_EPOCH_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_SET_CONFIG_EPOCH_tips NULL
 
 /* CLUSTER SET_CONFIG_EPOCH argument table */
 struct redisCommandArg CLUSTER_SET_CONFIG_EPOCH_Args[] = {
@@ -603,10 +558,7 @@ struct redisCommandArg CLUSTER_SET_CONFIG_EPOCH_Args[] = {
 #define CLUSTER_SETSLOT_History NULL
 
 /* CLUSTER SETSLOT tips */
-const char *CLUSTER_SETSLOT_tips[] = {
-"nondeterministic_output",
-NULL
-};
+#define CLUSTER_SETSLOT_tips NULL
 
 /* CLUSTER SETSLOT subcommand argument table */
 struct redisCommandArg CLUSTER_SETSLOT_subcommand_Subargs[] = {
@@ -820,8 +772,8 @@ commandHistory CLIENT_KILL_History[] = {
 /* CLIENT KILL tips */
 #define CLIENT_KILL_tips NULL
 
-/* CLIENT KILL normal_master_slave_pubsub argument table */
-struct redisCommandArg CLIENT_KILL_normal_master_slave_pubsub_Subargs[] = {
+/* CLIENT KILL filter new_format normal_master_slave_pubsub argument table */
+struct redisCommandArg CLIENT_KILL_filter_new_format_normal_master_slave_pubsub_Subargs[] = {
 {"normal",ARG_TYPE_PURE_TOKEN,-1,"NORMAL",NULL,NULL,CMD_ARG_NONE},
 {"master",ARG_TYPE_PURE_TOKEN,-1,"MASTER",NULL,"3.2.0",CMD_ARG_NONE},
 {"slave",ARG_TYPE_PURE_TOKEN,-1,"SLAVE",NULL,NULL,CMD_ARG_NONE},
@@ -830,11 +782,10 @@ struct redisCommandArg CLIENT_KILL_normal_master_slave_pubsub_Subargs[] = {
 {0}
 };
 
-/* CLIENT KILL argument table */
-struct redisCommandArg CLIENT_KILL_Args[] = {
-{"ip:port",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL},
+/* CLIENT KILL filter new_format argument table */
+struct redisCommandArg CLIENT_KILL_filter_new_format_Subargs[] = {
 {"client-id",ARG_TYPE_INTEGER,-1,"ID",NULL,"2.8.12",CMD_ARG_OPTIONAL},
-{"normal_master_slave_pubsub",ARG_TYPE_ONEOF,-1,"TYPE",NULL,"2.8.12",CMD_ARG_OPTIONAL,.subargs=CLIENT_KILL_normal_master_slave_pubsub_Subargs},
+{"normal_master_slave_pubsub",ARG_TYPE_ONEOF,-1,"TYPE",NULL,"2.8.12",CMD_ARG_OPTIONAL,.subargs=CLIENT_KILL_filter_new_format_normal_master_slave_pubsub_Subargs},
 {"username",ARG_TYPE_STRING,-1,"USER",NULL,NULL,CMD_ARG_OPTIONAL},
 {"ip:port",ARG_TYPE_STRING,-1,"ADDR",NULL,NULL,CMD_ARG_OPTIONAL},
 {"ip:port",ARG_TYPE_STRING,-1,"LADDR",NULL,"6.2.0",CMD_ARG_OPTIONAL},
@@ -842,6 +793,19 @@ struct redisCommandArg CLIENT_KILL_Args[] = {
 {0}
 };
 
+/* CLIENT KILL filter argument table */
+struct redisCommandArg CLIENT_KILL_filter_Subargs[] = {
+{"ip:port",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE,.deprecated_since="2.8.12"},
+{"new-format",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE,.subargs=CLIENT_KILL_filter_new_format_Subargs},
+{0}
+};
+
+/* CLIENT KILL argument table */
+struct redisCommandArg CLIENT_KILL_Args[] = {
+{"filter",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_NONE,.subargs=CLIENT_KILL_filter_Subargs},
+{0}
+};
+
 /********** CLIENT LIST ********************/
 
 /* CLIENT LIST history */
@@ -1922,6 +1886,7 @@ struct redisCommandArg GEOPOS_Args[] = {
 /* GEORADIUS history */
 commandHistory GEORADIUS_History[] = {
 {"6.2.0","Added the `ANY` option for `COUNT`."},
+{"7.0.0","Added support for uppercase unit names."},
 {0}
 };
 
@@ -1971,7 +1936,10 @@ struct redisCommandArg GEORADIUS_Args[] = {
 /********** GEORADIUSBYMEMBER ********************/
 
 /* GEORADIUSBYMEMBER history */
-#define GEORADIUSBYMEMBER_History NULL
+commandHistory GEORADIUSBYMEMBER_History[] = {
+{"7.0.0","Added support for uppercase unit names."},
+{0}
+};
 
 /* GEORADIUSBYMEMBER tips */
 #define GEORADIUSBYMEMBER_tips NULL
@@ -2112,7 +2080,10 @@ struct redisCommandArg GEORADIUS_RO_Args[] = {
 /********** GEOSEARCH ********************/
 
 /* GEOSEARCH history */
-#define GEOSEARCH_History NULL
+commandHistory GEOSEARCH_History[] = {
+{"7.0.0","Added support for uppercase unit names."},
+{0}
+};
 
 /* GEOSEARCH tips */
 #define GEOSEARCH_tips NULL
@@ -2201,7 +2172,10 @@ struct redisCommandArg GEOSEARCH_Args[] = {
 /********** GEOSEARCHSTORE ********************/
 
 /* GEOSEARCHSTORE history */
-#define GEOSEARCHSTORE_History NULL
+commandHistory GEOSEARCHSTORE_History[] = {
+{"7.0.0","Added support for uppercase unit names."},
+{0}
+};
 
 /* GEOSEARCHSTORE tips */
 #define GEOSEARCHSTORE_tips NULL
@@ -3318,8 +3292,8 @@ struct redisCommandArg EVALSHA_Args[] = {
 struct redisCommandArg EVALSHA_RO_Args[] = {
 {"sha1",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
 {"numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE},
-{"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE},
-{"arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE},
+{"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE},
+{"arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE},
 {0}
 };
 
@@ -3335,8 +3309,8 @@ struct redisCommandArg EVALSHA_RO_Args[] = {
 struct redisCommandArg EVAL_RO_Args[] = {
 {"script",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
 {"numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE},
-{"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE},
-{"arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE},
+{"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE},
+{"arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE},
 {0}
 };
 
@@ -3352,8 +3326,8 @@ struct redisCommandArg EVAL_RO_Args[] = {
 struct redisCommandArg FCALL_Args[] = {
 {"function",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
 {"numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE},
-{"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE},
-{"arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE},
+{"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE},
+{"arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE},
 {0}
 };
 
@@ -3369,8 +3343,8 @@ struct redisCommandArg FCALL_Args[] = {
 struct redisCommandArg FCALL_RO_Args[] = {
 {"function",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_NONE},
 {"numkeys",ARG_TYPE_INTEGER,-1,NULL,NULL,NULL,CMD_ARG_NONE},
-{"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_MULTIPLE},
-{"arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_MULTIPLE},
+{"key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE},
+{"arg",ARG_TYPE_STRING,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL|CMD_ARG_MULTIPLE},
 {0}
 };
 
@@ -6286,6 +6260,7 @@ struct redisCommandArg XCLAIM_Args[] = {
 {"count",ARG_TYPE_INTEGER,-1,"RETRYCOUNT",NULL,NULL,CMD_ARG_OPTIONAL},
 {"force",ARG_TYPE_PURE_TOKEN,-1,"FORCE",NULL,NULL,CMD_ARG_OPTIONAL},
 {"justid",ARG_TYPE_PURE_TOKEN,-1,"JUSTID",NULL,NULL,CMD_ARG_OPTIONAL},
+{"id",ARG_TYPE_STRING,-1,"LASTID",NULL,NULL,CMD_ARG_OPTIONAL},
 {0}
 };
 
@@ -7341,7 +7316,7 @@ struct redisCommand redisCommandTable[] = {
 {"sunion","Add multiple sets","O(N) where N is the total number of elements in all given sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SET,SUNION_History,SUNION_tips,sunionCommand,-2,CMD_READONLY,ACL_CATEGORY_SET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-1,1,0}}},.args=SUNION_Args},
 {"sunionstore","Add multiple sets and store the resulting set in a key","O(N) where N is the total number of elements in all given sets.","1.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SET,SUNIONSTORE_History,SUNIONSTORE_tips,sunionstoreCommand,-3,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SET,{{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_RANGE,.fk.range={-1,1,0}}},.args=SUNIONSTORE_Args},
 /* sorted_set */
-{"bzmpop","Remove and return members with scores in a sorted set or block until one is available","O(K) + O(N*log(M)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,BZMPOP_History,BZMPOP_tips,bzmpopCommand,-5,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},blmpopGetKeys,.args=BZMPOP_Args},
+{"bzmpop","Remove and return members with scores in a sorted set or block until one is available","O(K) + O(M*log(N)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,BZMPOP_History,BZMPOP_tips,bzmpopCommand,-5,CMD_WRITE|CMD_BLOCKING,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},blmpopGetKeys,.args=BZMPOP_Args},
 {"bzpopmax","Remove and return the member with the highest score from one or more sorted sets, or block until one is available","O(log(N)) with N being the number of elements in the sorted set.","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,BZPOPMAX_History,BZPOPMAX_tips,bzpopmaxCommand,-3,CMD_WRITE|CMD_NOSCRIPT|CMD_FAST|CMD_BLOCKING,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-2,1,0}}},.args=BZPOPMAX_Args},
 {"bzpopmin","Remove and return the member with the lowest score from one or more sorted sets, or block until one is available","O(log(N)) with N being the number of elements in the sorted set.","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,BZPOPMIN_History,BZPOPMIN_tips,bzpopminCommand,-3,CMD_WRITE|CMD_NOSCRIPT|CMD_FAST|CMD_BLOCKING,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={-2,1,0}}},.args=BZPOPMIN_Args},
 {"zadd","Add one or more members to a sorted set, or update its score if it already exists","O(log(N)) for each item added, where N is the number of elements in the sorted set.","1.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZADD_History,ZADD_tips,zaddCommand,-4,CMD_WRITE|CMD_DENYOOM|CMD_FAST,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZADD_Args},
@@ -7354,7 +7329,7 @@ struct redisCommand redisCommandTable[] = {
 {"zintercard","Intersect multiple sorted sets and return the cardinality of the result","O(N*K) worst case with N being the smallest input sorted set, K being the number of input sorted sets.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZINTERCARD_History,ZINTERCARD_tips,zinterCardCommand,-3,CMD_READONLY,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},zunionInterDiffGetKeys,.args=ZINTERCARD_Args},
 {"zinterstore","Intersect multiple sorted sets and store the resulting sorted set in a new key","O(N*K)+O(M*log(M)) worst case with N being the smallest input sorted set, K being the number of input sorted sets and M being the number of elements in the resulting sorted set.","2.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZINTERSTORE_History,ZINTERSTORE_tips,zinterstoreCommand,-4,CMD_WRITE|CMD_DENYOOM,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_OW|CMD_KEY_UPDATE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}},{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={2},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},zunionInterDiffStoreGetKeys,.args=ZINTERSTORE_Args},
 {"zlexcount","Count the number of members in a sorted set between a given lexicographical range","O(log(N)) with N being the number of elements in the sorted set.","2.8.9",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZLEXCOUNT_History,ZLEXCOUNT_tips,zlexcountCommand,4,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZLEXCOUNT_Args},
-{"zmpop","Remove and return members with scores in a sorted set","O(K) + O(N*log(M)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZMPOP_History,ZMPOP_tips,zmpopCommand,-4,CMD_WRITE,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},zmpopGetKeys,.args=ZMPOP_Args},
+{"zmpop","Remove and return members with scores in a sorted set","O(K) + O(M*log(N)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.","7.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZMPOP_History,ZMPOP_tips,zmpopCommand,-4,CMD_WRITE,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_KEYNUM,.fk.keynum={0,1,1}}},zmpopGetKeys,.args=ZMPOP_Args},
 {"zmscore","Get the score associated with the given members in a sorted set","O(N) where N is the number of members being requested.","6.2.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZMSCORE_History,ZMSCORE_tips,zmscoreCommand,-3,CMD_READONLY|CMD_FAST,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RO|CMD_KEY_ACCESS,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZMSCORE_Args},
 {"zpopmax","Remove and return members with the highest scores in a sorted set","O(log(N)*M) with N being the number of elements in the sorted set, and M being the number of elements popped.","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZPOPMAX_History,ZPOPMAX_tips,zpopmaxCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZPOPMAX_Args},
 {"zpopmin","Remove and return members with the lowest scores in a sorted set","O(log(N)*M) with N being the number of elements in the sorted set, and M being the number of elements popped.","5.0.0",CMD_DOC_NONE,NULL,NULL,COMMAND_GROUP_SORTED_SET,ZPOPMIN_History,ZPOPMIN_tips,zpopminCommand,-2,CMD_WRITE|CMD_FAST,ACL_CATEGORY_SORTEDSET,{{NULL,CMD_KEY_RW|CMD_KEY_ACCESS|CMD_KEY_DELETE,KSPEC_BS_INDEX,.bs.index={1},KSPEC_FK_RANGE,.fk.range={0,1,0}}},.args=ZPOPMIN_Args},
diff --git a/src/commands/bzmpop.json b/src/commands/bzmpop.json
index d5ba57c586a..87a1cd8b3be 100644
--- a/src/commands/bzmpop.json
+++ b/src/commands/bzmpop.json
@@ -1,7 +1,7 @@
 {
     "BZMPOP": {
         "summary": "Remove and return members with scores in a sorted set or block until one is available",
-        "complexity": "O(K) + O(N*log(M)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.",
+        "complexity": "O(K) + O(M*log(N)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.",
         "group": "sorted_set",
         "since": "7.0.0",
         "arity": -5,
diff --git a/src/commands/client-kill.json b/src/commands/client-kill.json
index 452724fe2e5..6303c747fdd 100644
--- a/src/commands/client-kill.json
+++ b/src/commands/client-kill.json
@@ -41,77 +41,90 @@
         ],
         "arguments": [
             {
-                "name": "ip:port",
-                "type": "string",
-                "optional": true
-            },
-            {
-                "token": "ID",
-                "name": "client-id",
-                "type": "integer",
-                "optional": true,
-                "since": "2.8.12"
-            },
-            {
-                "token": "TYPE",
-                "name": "normal_master_slave_pubsub",
+                "name": "filter",
                 "type": "oneof",
-                "optional": true,
-                "since": "2.8.12",
                 "arguments": [
                     {
-                        "name": "normal",
-                        "type": "pure-token",
-                        "token": "normal"
-                    },
-                    {
-                        "name": "master",
-                        "type": "pure-token",
-                        "token": "master",
-                        "since": "3.2.0"
-                    },
-                    {
-                        "name": "slave",
-                        "type": "pure-token",
-                        "token": "slave"
+                        "name": "ip:port",
+                        "type": "string",
+                        "deprecated_since": "2.8.12"
                     },
                     {
-                        "name": "replica",
-                        "type": "pure-token",
-                        "token": "replica",
-                        "since": "5.0.0"
-                    },
-                    {
-                        "name": "pubsub",
-                        "type": "pure-token",
-                        "token": "pubsub"
+                        "name": "new-format",
+                        "type": "oneof",
+                        "multiple": true,
+                        "arguments": [
+                            {
+                                "token": "ID",
+                                "name": "client-id",
+                                "type": "integer",
+                                "optional": true,
+                                "since": "2.8.12"
+                            },
+                            {
+                                "token": "TYPE",
+                                "name": "normal_master_slave_pubsub",
+                                "type": "oneof",
+                                "optional": true,
+                                "since": "2.8.12",
+                                "arguments": [
+                                    {
+                                        "name": "normal",
+                                        "type": "pure-token",
+                                        "token": "normal"
+                                    },
+                                    {
+                                        "name": "master",
+                                        "type": "pure-token",
+                                        "token": "master",
+                                        "since": "3.2.0"
+                                    },
+                                    {
+                                        "name": "slave",
+                                        "type": "pure-token",
+                                        "token": "slave"
+                                    },
+                                    {
+                                        "name": "replica",
+                                        "type": "pure-token",
+                                        "token": "replica",
+                                        "since": "5.0.0"
+                                    },
+                                    {
+                                        "name": "pubsub",
+                                        "type": "pure-token",
+                                        "token": "pubsub"
+                                    }
+                                ]
+                            },
+                            {
+                                "token": "USER",
+                                "name": "username",
+                                "type": "string",
+                                "optional": true
+                            },
+                            {
+                                "token": "ADDR",
+                                "name": "ip:port",
+                                "type": "string",
+                                "optional": true
+                            },
+                            {
+                                "token": "LADDR",
+                                "name": "ip:port",
+                                "type": "string",
+                                "optional": true,
+                                "since": "6.2.0"
+                            },
+                            {
+                                "token": "SKIPME",
+                                "name": "yes/no",
+                                "type": "string",
+                                "optional": true
+                            }
+                        ]
                     }
                 ]
-            },
-            {
-                "token": "USER",
-                "name": "username",
-                "type": "string",
-                "optional": true
-            },
-            {
-                "token": "ADDR",
-                "name": "ip:port",
-                "type": "string",
-                "optional": true
-            },
-            {
-                "token": "LADDR",
-                "name": "ip:port",
-                "type": "string",
-                "optional": true,
-                "since": "6.2.0"
-            },
-            {
-                "token": "SKIPME",
-                "name": "yes/no",
-                "type": "string",
-                "optional": true
             }
         ]
     }
diff --git a/src/commands/cluster-addslots.json b/src/commands/cluster-addslots.json
index 518d67e0969..0a2d0a82f68 100644
--- a/src/commands/cluster-addslots.json
+++ b/src/commands/cluster-addslots.json
@@ -12,9 +12,6 @@
             "ADMIN",
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "slot",
diff --git a/src/commands/cluster-addslotsrange.json b/src/commands/cluster-addslotsrange.json
index ee58776d391..756a0a6729e 100644
--- a/src/commands/cluster-addslotsrange.json
+++ b/src/commands/cluster-addslotsrange.json
@@ -12,9 +12,6 @@
             "ADMIN",
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "start-slot_end-slot",
diff --git a/src/commands/cluster-countkeysinslot.json b/src/commands/cluster-countkeysinslot.json
index 5a946462655..eefae6e1609 100644
--- a/src/commands/cluster-countkeysinslot.json
+++ b/src/commands/cluster-countkeysinslot.json
@@ -10,9 +10,6 @@
         "command_flags": [
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "slot",
diff --git a/src/commands/cluster-delslots.json b/src/commands/cluster-delslots.json
index 862299b7eec..89c147b35e8 100644
--- a/src/commands/cluster-delslots.json
+++ b/src/commands/cluster-delslots.json
@@ -12,9 +12,6 @@
             "ADMIN",
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "slot",
diff --git a/src/commands/cluster-delslotsrange.json b/src/commands/cluster-delslotsrange.json
index d13bf0ab8db..96776d177e6 100644
--- a/src/commands/cluster-delslotsrange.json
+++ b/src/commands/cluster-delslotsrange.json
@@ -12,9 +12,6 @@
             "ADMIN",
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "start-slot_end-slot",
diff --git a/src/commands/cluster-failover.json b/src/commands/cluster-failover.json
index c57a1861bd6..e7daf241cb9 100644
--- a/src/commands/cluster-failover.json
+++ b/src/commands/cluster-failover.json
@@ -12,9 +12,6 @@
             "ADMIN",
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "options",
diff --git a/src/commands/cluster-flushslots.json b/src/commands/cluster-flushslots.json
index 2478713d8c8..214aa396c98 100644
--- a/src/commands/cluster-flushslots.json
+++ b/src/commands/cluster-flushslots.json
@@ -11,9 +11,6 @@
             "NO_ASYNC_LOADING",
             "ADMIN",
             "STALE"
-        ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
         ]
     }
 }
diff --git a/src/commands/cluster-forget.json b/src/commands/cluster-forget.json
index 8991f76667d..6668eab21eb 100644
--- a/src/commands/cluster-forget.json
+++ b/src/commands/cluster-forget.json
@@ -12,9 +12,6 @@
             "ADMIN",
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "node-id",
diff --git a/src/commands/cluster-keyslot.json b/src/commands/cluster-keyslot.json
index d078aa3c4a5..10645477fab 100644
--- a/src/commands/cluster-keyslot.json
+++ b/src/commands/cluster-keyslot.json
@@ -10,9 +10,6 @@
         "command_flags": [
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "key",
diff --git a/src/commands/cluster-meet.json b/src/commands/cluster-meet.json
index 9a85be76d57..d79ece21de6 100644
--- a/src/commands/cluster-meet.json
+++ b/src/commands/cluster-meet.json
@@ -18,9 +18,6 @@
             "ADMIN",
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "ip",
diff --git a/src/commands/cluster-myid.json b/src/commands/cluster-myid.json
index ccb94eb13b1..dc4f8a77369 100644
--- a/src/commands/cluster-myid.json
+++ b/src/commands/cluster-myid.json
@@ -9,9 +9,6 @@
         "function": "clusterCommand",
         "command_flags": [
             "STALE"
-        ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
         ]
     }
 }
diff --git a/src/commands/cluster-replicate.json b/src/commands/cluster-replicate.json
index 87c6cc9aa88..beda5e7881e 100644
--- a/src/commands/cluster-replicate.json
+++ b/src/commands/cluster-replicate.json
@@ -12,9 +12,6 @@
             "ADMIN",
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "node-id",
diff --git a/src/commands/cluster-reset.json b/src/commands/cluster-reset.json
index 630f458e760..c7711e97daf 100644
--- a/src/commands/cluster-reset.json
+++ b/src/commands/cluster-reset.json
@@ -12,9 +12,6 @@
             "STALE",
             "NOSCRIPT"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "hard_soft",
diff --git a/src/commands/cluster-saveconfig.json b/src/commands/cluster-saveconfig.json
index 1bf49e7f731..991286d09c7 100644
--- a/src/commands/cluster-saveconfig.json
+++ b/src/commands/cluster-saveconfig.json
@@ -11,9 +11,6 @@
             "NO_ASYNC_LOADING",
             "ADMIN",
             "STALE"
-        ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
         ]
     }
 }
diff --git a/src/commands/cluster-set-config-epoch.json b/src/commands/cluster-set-config-epoch.json
index 2f819052d79..5f07f63c2a4 100644
--- a/src/commands/cluster-set-config-epoch.json
+++ b/src/commands/cluster-set-config-epoch.json
@@ -12,9 +12,6 @@
             "ADMIN",
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "config-epoch",
diff --git a/src/commands/cluster-setslot.json b/src/commands/cluster-setslot.json
index 5d1aa45fc5c..ad0e9b5d216 100644
--- a/src/commands/cluster-setslot.json
+++ b/src/commands/cluster-setslot.json
@@ -12,9 +12,6 @@
             "ADMIN",
             "STALE"
         ],
-        "command_tips": [
-            "NONDETERMINISTIC_OUTPUT"
-        ],
         "arguments": [
             {
                 "name": "slot",
diff --git a/src/commands/eval_ro.json b/src/commands/eval_ro.json
index 65fd866a06d..2911521d717 100644
--- a/src/commands/eval_ro.json
+++ b/src/commands/eval_ro.json
@@ -51,11 +51,13 @@
                 "name": "key",
                 "type": "key",
                 "key_spec_index": 0,
+		"optional":true,
                 "multiple": true
             },
             {
                 "name": "arg",
                 "type": "string",
+		"optional":true,
                 "multiple": true
             }
         ]
diff --git a/src/commands/evalsha_ro.json b/src/commands/evalsha_ro.json
index d76313540f6..b08ce67adef 100644
--- a/src/commands/evalsha_ro.json
+++ b/src/commands/evalsha_ro.json
@@ -50,11 +50,13 @@
                 "name": "key",
                 "type": "key",
                 "key_spec_index": 0,
+		"optional":true,
                 "multiple": true
             },
             {
                 "name": "arg",
                 "type": "string",
+		"optional":true,
                 "multiple": true
             }
         ]
diff --git a/src/commands/fcall.json b/src/commands/fcall.json
index 27b7b4e3538..9e7a905ecd5 100644
--- a/src/commands/fcall.json
+++ b/src/commands/fcall.json
@@ -52,11 +52,13 @@
                 "name": "key",
                 "type": "key",
                 "key_spec_index": 0,
+                "optional": true,
                 "multiple": true
             },
             {
                 "name": "arg",
                 "type": "string",
+                "optional": true,
                 "multiple": true
             }
         ]
diff --git a/src/commands/fcall_ro.json b/src/commands/fcall_ro.json
index 46085ebb020..6ba2736c07b 100644
--- a/src/commands/fcall_ro.json
+++ b/src/commands/fcall_ro.json
@@ -51,11 +51,13 @@
                 "name": "key",
                 "type": "key",
                 "key_spec_index": 0,
+                "optional": true,
                 "multiple": true
             },
             {
                 "name": "arg",
                 "type": "string",
+                "optional": true,
                 "multiple": true
             }
         ]
diff --git a/src/commands/georadius.json b/src/commands/georadius.json
index ba58db562c1..c53efa438c7 100644
--- a/src/commands/georadius.json
+++ b/src/commands/georadius.json
@@ -11,6 +11,10 @@
             [
                 "6.2.0",
                 "Added the `ANY` option for `COUNT`."
+            ],
+            [
+                "7.0.0",
+                "Added support for uppercase unit names."
             ]
         ],
         "deprecated_since": "6.2.0",
diff --git a/src/commands/georadiusbymember.json b/src/commands/georadiusbymember.json
index c7a399fc366..d22038a424d 100644
--- a/src/commands/georadiusbymember.json
+++ b/src/commands/georadiusbymember.json
@@ -7,6 +7,12 @@
         "arity": -5,
         "function": "georadiusbymemberCommand",
         "get_keys_function": "georadiusGetKeys",
+        "history": [
+            [
+                "7.0.0",
+                "Added support for uppercase unit names."
+            ]
+        ],
         "deprecated_since": "6.2.0",
         "replaced_by": "`GEOSEARCH` and `GEOSEARCHSTORE` with the `BYRADIUS` and `FROMMEMBER` arguments",
         "doc_flags": [
diff --git a/src/commands/geosearch.json b/src/commands/geosearch.json
index a83dcaadb46..b6e9dc03639 100644
--- a/src/commands/geosearch.json
+++ b/src/commands/geosearch.json
@@ -6,6 +6,12 @@
         "since": "6.2.0",
         "arity": -7,
         "function": "geosearchCommand",
+        "history": [
+            [
+                "7.0.0",
+                "Added support for uppercase unit names."
+            ]
+        ],
         "command_flags": [
             "READONLY"
         ],
@@ -203,4 +209,4 @@
             }
         ]
     }
-}
\ No newline at end of file
+}
diff --git a/src/commands/geosearchstore.json b/src/commands/geosearchstore.json
index 16db5d37e79..feaed57b8b7 100644
--- a/src/commands/geosearchstore.json
+++ b/src/commands/geosearchstore.json
@@ -6,6 +6,12 @@
         "since": "6.2.0",
         "arity": -8,
         "function": "geosearchstoreCommand",
+        "history": [
+            [
+                "7.0.0",
+                "Added support for uppercase unit names."
+            ]
+        ],
         "command_flags": [
             "WRITE",
             "DENYOOM"
@@ -215,4 +221,4 @@
             }
         ]
     }
-}
\ No newline at end of file
+}
diff --git a/src/commands/xclaim.json b/src/commands/xclaim.json
index 5dcf8d508b9..0456109005f 100644
--- a/src/commands/xclaim.json
+++ b/src/commands/xclaim.json
@@ -88,6 +88,12 @@
                 "token": "JUSTID",
                 "type": "pure-token",
                 "optional": true
+            },
+            {
+                "name": "id",
+                "token": "LASTID",
+                "type": "string",
+                "optional": true
             }
         ]
     }
diff --git a/src/commands/zmpop.json b/src/commands/zmpop.json
index 964d5c842b4..2edeaf2ccce 100644
--- a/src/commands/zmpop.json
+++ b/src/commands/zmpop.json
@@ -1,7 +1,7 @@
 {
     "ZMPOP": {
         "summary": "Remove and return members with scores in a sorted set",
-        "complexity": "O(K) + O(N*log(M)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.",
+        "complexity": "O(K) + O(M*log(N)) where K is the number of provided keys, N being the number of elements in the sorted set, and M being the number of elements popped.",
         "group": "sorted_set",
         "since": "7.0.0",
         "arity": -4,
diff --git a/src/connection.c b/src/connection.c
index 11fc4ba2834..f61ed2404a3 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -389,7 +389,11 @@ int connGetSocketError(connection *conn) {
 }
 
 int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
-    return anetFdToString(conn ? conn->fd : -1, ip, ip_len, port, FD_TO_PEER_NAME);
+    if (anetFdToString(conn ? conn->fd : -1, ip, ip_len, port, FD_TO_PEER_NAME) == -1) {
+        if (conn) conn->last_errno = errno;
+        return C_ERR;
+    }
+    return C_OK;
 }
 
 int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
diff --git a/src/db.c b/src/db.c
index 99212bac4ee..3fd4938af3f 100644
--- a/src/db.c
+++ b/src/db.c
@@ -41,7 +41,11 @@
  * C-level DB API
  *----------------------------------------------------------------------------*/
 
-int expireIfNeeded(redisDb *db, robj *key, int force_delete_expired);
+/* Flags for expireIfNeeded */
+#define EXPIRE_FORCE_DELETE_EXPIRED 1
+#define EXPIRE_AVOID_DELETE_EXPIRED 2
+
+int expireIfNeeded(redisDb *db, robj *key, int flags);
 int keyIsExpired(redisDb *db, robj *key);
 
 /* Update LFU when an object is accessed.
@@ -72,6 +76,8 @@ void updateLFU(robj *val) {
  *  LOOKUP_NOSTATS: Don't increment key hits/misses counters.
  *  LOOKUP_WRITE: Prepare the key for writing (delete expired keys even on
  *                replicas, use separate keyspace stats and events (TODO)).
+ *  LOOKUP_NOEXPIRE: Perform expiration check, but avoid deleting the key,
+ *                   so that we don't have to propagate the deletion.
  *
  * Note: this function also returns NULL if the key is logically expired but
  * still existing, in case this is a replica and the LOOKUP_WRITE is not set.
@@ -92,8 +98,12 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
          * command, since the command may trigger events that cause modules to
          * perform additional writes. */
         int is_ro_replica = server.masterhost && server.repl_slave_ro;
-        int force_delete_expired = flags & LOOKUP_WRITE && !is_ro_replica;
-        if (expireIfNeeded(db, key, force_delete_expired)) {
+        int expire_flags = 0;
+        if (flags & LOOKUP_WRITE && !is_ro_replica)
+            expire_flags |= EXPIRE_FORCE_DELETE_EXPIRED;
+        if (flags & LOOKUP_NOEXPIRE)
+            expire_flags |= EXPIRE_AVOID_DELETE_EXPIRED;
+        if (expireIfNeeded(db, key, expire_flags)) {
             /* The key is no longer valid. */
             val = NULL;
         }
@@ -1657,13 +1667,17 @@ int keyIsExpired(redisDb *db, robj *key) {
  *
  * On replicas, this function does not delete expired keys by default, but
  * it still returns 1 if the key is logically expired. To force deletion
- * of logically expired keys even on replicas, set force_delete_expired to
- * a non-zero value. Note though that if the current client is executing
+ * of logically expired keys even on replicas, use the EXPIRE_FORCE_DELETE_EXPIRED
+ * flag. Note though that if the current client is executing
  * replicated commands from the master, keys are never considered expired.
  *
+ * On the other hand, if you just want expiration check, but need to avoid
+ * the actual key deletion and propagation of the deletion, use the
+ * EXPIRE_AVOID_DELETE_EXPIRED flag.
+ *
  * The return value of the function is 0 if the key is still valid,
  * otherwise the function returns 1 if the key is expired. */
-int expireIfNeeded(redisDb *db, robj *key, int force_delete_expired) {
+int expireIfNeeded(redisDb *db, robj *key, int flags) {
     if (!keyIsExpired(db,key)) return 0;
 
     /* If we are running in the context of a replica, instead of
@@ -1681,9 +1695,14 @@ int expireIfNeeded(redisDb *db, robj *key, int force_delete_expired) {
      * expired. */
     if (server.masterhost != NULL) {
         if (server.current_client == server.master) return 0;
-        if (!force_delete_expired) return 1;
+        if (!(flags & EXPIRE_FORCE_DELETE_EXPIRED)) return 1;
     }
 
+    /* In some cases we're explicitly instructed to return an indication of a
+     * missing key without actually deleting it, even on masters. */
+    if (flags & EXPIRE_AVOID_DELETE_EXPIRED)
+        return 1;
+
     /* If clients are paused, we keep the current dataset constant,
      * but return to the client what we believe is the right state. Typically,
      * at the end of the pause we will properly expire the key OR we will
@@ -2389,6 +2408,7 @@ int setGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *r
  * read-only if the BITFIELD GET subcommand is used. */
 int bitfieldGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result) {
     keyReference *keys;
+    int readonly = 1;
     UNUSED(cmd);
 
     keys = getKeysPrepareResult(result, 1);
@@ -2399,11 +2419,23 @@ int bitfieldGetKeys(struct redisCommand *cmd, robj **argv, int argc, getKeysResu
         int remargs = argc - i - 1; /* Remaining args other than current. */
         char *arg = argv[i]->ptr;
         if (!strcasecmp(arg, "get") && remargs >= 2) {
-            keys[0].flags = CMD_KEY_RO | CMD_KEY_ACCESS;
-            return 1;
+            i += 2;
+        } else if ((!strcasecmp(arg, "set") || !strcasecmp(arg, "incrby")) && remargs >= 3) {
+            readonly = 0;
+            i += 3;
+            break;
+        } else if (!strcasecmp(arg, "overflow") && remargs >= 1) {
+            i += 1;
+        } else {
+            readonly = 0; /* Syntax error. safer to assume non-RO. */
+            break;
         }
     }
 
-    keys[0].flags = CMD_KEY_RW | CMD_KEY_ACCESS | CMD_KEY_UPDATE;
+    if (readonly) {
+        keys[0].flags = CMD_KEY_RO | CMD_KEY_ACCESS;
+    } else {
+        keys[0].flags = CMD_KEY_RW | CMD_KEY_ACCESS | CMD_KEY_UPDATE;
+    }
     return 1;
 }
diff --git a/src/debug.c b/src/debug.c
index f394f867c2c..1500eb35b4a 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -466,7 +466,7 @@ void debugCommand(client *c) {
 "    default.",
 "QUICKLIST-PACKED-THRESHOLD <size>",
 "    Sets the threshold for elements to be inserted as plain vs packed nodes",
-"    Default value is 1GB, allows values up to 4GB",
+"    Default value is 1GB, allows values up to 4GB. Setting to 0 restores to default.",
 "SET-SKIP-CHECKSUM-VALIDATION <0|1>",
 "    Enables or disables checksum checks for RDB files and RESTORE's payload.",
 "SLEEP <seconds>",
diff --git a/src/evict.c b/src/evict.c
index 6ee85ede567..a9e24a762dd 100644
--- a/src/evict.c
+++ b/src/evict.c
@@ -590,7 +590,7 @@ int performEvictions(void) {
         {
             struct evictionPoolEntry *pool = EvictionPoolLRU;
 
-            while(bestkey == NULL) {
+            while (bestkey == NULL) {
                 unsigned long total_keys = 0, keys;
 
                 /* We don't want to make local-db choices when expiring keys,
@@ -732,12 +732,18 @@ int performEvictions(void) {
         /* At this point, we have run out of evictable items.  It's possible
          * that some items are being freed in the lazyfree thread.  Perform a
          * short wait here if such jobs exist, but don't wait long.  */
-        if (bioPendingJobsOfType(BIO_LAZY_FREE)) {
-            usleep(eviction_time_limit_us);
+        mstime_t lazyfree_latency;
+        latencyStartMonitor(lazyfree_latency);
+        while (bioPendingJobsOfType(BIO_LAZY_FREE) &&
+              elapsedUs(evictionTimer) < eviction_time_limit_us) {
             if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
                 result = EVICT_OK;
+                break;
             }
+            usleep(eviction_time_limit_us < 1000 ? eviction_time_limit_us : 1000);
         }
+        latencyEndMonitor(lazyfree_latency);
+        latencyAddSampleIfNeeded("eviction-lazyfree",lazyfree_latency);
     }
 
     serverAssert(server.core_propagates); /* This function should not be re-entrant */
diff --git a/src/functions.c b/src/functions.c
index 688d9f5c67d..1e81bf6bc59 100644
--- a/src/functions.c
+++ b/src/functions.c
@@ -245,7 +245,7 @@ functionsLibCtx* functionsLibCtxCreate() {
  */
 int functionLibCreateFunction(sds name, void *function, functionLibInfo *li, sds desc, uint64_t f_flags, sds *err) {
     if (functionsVerifyName(name) != C_OK) {
-        *err = sdsnew("Function names can only contain letters and numbers and must be at least one character long");
+        *err = sdsnew("Library names can only contain letters, numbers, or underscores(_) and must be at least one character long");
         return C_ERR;
     }
 
@@ -819,7 +819,7 @@ void functionFlushCommand(client *c) {
 /* FUNCTION HELP */
 void functionHelpCommand(client *c) {
     const char *help[] = {
-"LOAD <ENGINE NAME> <LIBRARY NAME> [REPLACE] [DESCRIPTION <LIBRARY DESCRIPTION>] <LIBRARY CODE>",
+"LOAD [REPLACE] <FUNCTION CODE>",
 "    Create a new library with the given library name and code.",
 "DELETE <LIBRARY NAME>",
 "    Delete the given library.",
@@ -847,7 +847,7 @@ void functionHelpCommand(client *c) {
 "    * ASYNC: Asynchronously flush the libraries.",
 "    * SYNC: Synchronously flush the libraries.",
 "DUMP",
-"    Returns a serialized payload representing the current libraries, can be restored using FUNCTION RESTORE command",
+"    Return a serialized payload representing the current libraries, can be restored using FUNCTION RESTORE command",
 "RESTORE <PAYLOAD> [FLUSH|APPEND|REPLACE]",
 "    Restore the libraries represented by the given payload, it is possible to give a restore policy to",
 "    control how to handle existing libraries (default APPEND):",
@@ -961,7 +961,7 @@ sds functionsCreateWithLibraryCtx(sds code, int replace, sds* err, functionsLibC
     }
 
     if (functionsVerifyName(md.name)) {
-        *err = sdsnew("Library names can only contain letters and numbers and must be at least one character long");
+        *err = sdsnew("Library names can only contain letters, numbers, or underscores(_) and must be at least one character long");
         goto error;
     }
 
diff --git a/src/help.h b/src/help.h
index a95da3695a7..45ce3b3886e 100644
--- a/src/help.h
+++ b/src/help.h
@@ -225,7 +225,7 @@ struct commandHelp {
     8,
     "6.2.0" },
     { "CLIENT KILL",
-    "[ip:port] [ID client-id] [TYPE NORMAL|MASTER|SLAVE|REPLICA|PUBSUB] [USER username] [ADDR ip:port] [LADDR ip:port] [SKIPME yes/no]",
+    "ip:port|[ID client-id]|[TYPE NORMAL|MASTER|SLAVE|REPLICA|PUBSUB]|[USER username]|[ADDR ip:port]|[LADDR ip:port]|[SKIPME yes/no] [[ID client-id]|[TYPE NORMAL|MASTER|SLAVE|REPLICA|PUBSUB]|[USER username]|[ADDR ip:port]|[LADDR ip:port]|[SKIPME yes/no] ...]",
     "Kill the connection of a client",
     8,
     "2.4.0" },
@@ -540,12 +540,12 @@ struct commandHelp {
     10,
     "2.6.0" },
     { "EVALSHA_RO",
-    "sha1 numkeys key [key ...] arg [arg ...]",
+    "sha1 numkeys [key [key ...]] [arg [arg ...]]",
     "Execute a read-only Lua script server side",
     10,
     "7.0.0" },
     { "EVAL_RO",
-    "script numkeys key [key ...] arg [arg ...]",
+    "script numkeys [key [key ...]] [arg [arg ...]]",
     "Execute a read-only Lua script server side",
     10,
     "7.0.0" },
@@ -580,12 +580,12 @@ struct commandHelp {
     9,
     "6.2.0" },
     { "FCALL",
-    "function numkeys key [key ...] arg [arg ...]",
+    "function numkeys [key [key ...]] [arg [arg ...]]",
     "Invoke a function",
     10,
     "7.0.0" },
     { "FCALL_RO",
-    "function numkeys key [key ...] arg [arg ...]",
+    "function numkeys [key [key ...]] [arg [arg ...]]",
     "Invoke a read-only function",
     10,
     "7.0.0" },
@@ -1590,7 +1590,7 @@ struct commandHelp {
     14,
     "6.2.0" },
     { "XCLAIM",
-    "key group consumer min-idle-time id [id ...] [IDLE ms] [TIME unix-time-milliseconds] [RETRYCOUNT count] [FORCE] [JUSTID]",
+    "key group consumer min-idle-time id [id ...] [IDLE ms] [TIME unix-time-milliseconds] [RETRYCOUNT count] [FORCE] [JUSTID] [LASTID id]",
     "Changes (or acquires) ownership of a message in a consumer group, as if the message was delivered to the specified consumer.",
     14,
     "5.0.0" },
diff --git a/src/module.c b/src/module.c
index 3ea8433f843..571eda9c885 100644
--- a/src/module.c
+++ b/src/module.c
@@ -592,6 +592,7 @@ void moduleReleaseTempClient(client *c) {
     c->bufpos = 0;
     c->flags = CLIENT_MODULE;
     c->user = NULL; /* Root user */
+    c->cmd = c->lastcmd = c->realcmd = NULL;
     moduleTempClients[moduleTempClientCount++] = c;
 }
 
@@ -5759,7 +5760,6 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
  * This API is documented here: https://redis.io/topics/modules-intro
  */
 RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
-    struct redisCommand *cmd;
     client *c = NULL;
     robj **argv = NULL;
     int argc = 0, argv_len = 0, flags = 0;
@@ -5767,6 +5767,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
     RedisModuleCallReply *reply = NULL;
     int replicate = 0; /* Replicate this command? */
     int error_as_call_replies = 0; /* return errors as RedisModuleCallReply object */
+    uint64_t cmd_flags;
 
     /* Handle arguments. */
     va_start(ap, fmt);
@@ -5809,7 +5810,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
     /* Lookup command now, after filters had a chance to make modifications
      * if necessary.
      */
-    cmd = c->cmd = c->lastcmd = c->realcmd = lookupCommand(c->argv,c->argc);
+    c->cmd = c->lastcmd = c->realcmd = lookupCommand(c->argv,c->argc);
     sds err;
     if (!commandCheckExistence(c, error_as_call_replies? &err : NULL)) {
         errno = ENOENT;
@@ -5824,10 +5825,12 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
         goto cleanup;
     }
 
+    cmd_flags = getCommandFlags(c);
+
     if (flags & REDISMODULE_ARGV_SCRIPT_MODE) {
         /* Basically on script mode we want to only allow commands that can
          * be executed on scripts (CMD_NOSCRIPT is not set on the command flags) */
-        if (cmd->flags & CMD_NOSCRIPT) {
+        if (cmd_flags & CMD_NOSCRIPT) {
             errno = ESPIPE;
             if (error_as_call_replies) {
                 sds msg = sdscatfmt(sdsempty(), "command '%S' is not allowed on script mode", c->cmd->fullname);
@@ -5838,7 +5841,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
     }
 
     if (flags & REDISMODULE_ARGV_RESPECT_DENY_OOM) {
-        if (cmd->flags & CMD_DENYOOM) {
+        if (cmd_flags & CMD_DENYOOM) {
             int oom_state;
             if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) {
                 /* On background thread we can not count on server.pre_command_oom_state.
@@ -5860,7 +5863,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
     }
 
     if (flags & REDISMODULE_ARGV_NO_WRITES) {
-        if (cmd->flags & CMD_WRITE) {
+        if (cmd_flags & CMD_WRITE) {
             errno = ENOSPC;
             if (error_as_call_replies) {
                 sds msg = sdscatfmt(sdsempty(), "Write command '%S' was "
@@ -5873,7 +5876,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
 
     /* Script mode tests */
     if (flags & REDISMODULE_ARGV_SCRIPT_MODE) {
-        if (cmd->flags & CMD_WRITE) {
+        if (cmd_flags & CMD_WRITE) {
             /* on script mode, if a command is a write command,
              * We will not run it if we encounter disk error
              * or we do not have enough replicas */
@@ -5910,7 +5913,7 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
         }
 
         if (server.masterhost && server.repl_state != REPL_STATE_CONNECTED &&
-            server.repl_serve_stale_data == 0 && !(cmd->flags & CMD_STALE)) {
+            server.repl_serve_stale_data == 0 && !(cmd_flags & CMD_STALE)) {
             errno = ESPIPE;
             if (error_as_call_replies) {
                 sds msg = sdsdup(shared.masterdownerr->ptr);
@@ -11027,6 +11030,21 @@ void moduleFreeModuleStructure(struct RedisModule *module) {
     zfree(module);
 }
 
+void moduleFreeArgs(struct redisCommandArg *args, int num_args) {
+    for (int j = 0; j < num_args; j++) {
+        zfree((char *)args[j].name);
+        zfree((char *)args[j].token);
+        zfree((char *)args[j].summary);
+        zfree((char *)args[j].since);
+        zfree((char *)args[j].deprecated_since);
+
+        if (args[j].subargs) {
+            moduleFreeArgs(args[j].subargs, args[j].num_args);
+        }
+    }
+    zfree(args);
+}
+
 /* Free the command registered with the specified module.
  * On success C_OK is returned, otherwise C_ERR is returned.
  *
@@ -11052,10 +11070,12 @@ int moduleFreeCommand(struct RedisModule *module, struct redisCommand *cmd) {
         zfree(cmd->key_specs);
     for (int j = 0; cmd->tips && cmd->tips[j]; j++)
         zfree((char *)cmd->tips[j]);
+    zfree(cmd->tips);
     for (int j = 0; cmd->history && cmd->history[j].since; j++) {
         zfree((char *)cmd->history[j].since);
         zfree((char *)cmd->history[j].changes);
     }
+    zfree(cmd->history);
     zfree((char *)cmd->summary);
     zfree((char *)cmd->since);
     zfree((char *)cmd->deprecated_since);
@@ -11064,7 +11084,7 @@ int moduleFreeCommand(struct RedisModule *module, struct redisCommand *cmd) {
         hdr_close(cmd->latency_histogram);
         cmd->latency_histogram = NULL;
     }
-    zfree(cmd->args);
+    moduleFreeArgs(cmd->args, cmd->num_args);
     zfree(cp);
 
     if (cmd->subcommands_dict) {
@@ -12484,6 +12504,8 @@ void moduleRegisterCoreAPI(void) {
     REGISTER_API(StringTruncate);
     REGISTER_API(SetExpire);
     REGISTER_API(GetExpire);
+    REGISTER_API(SetAbsExpire);
+    REGISTER_API(GetAbsExpire);
     REGISTER_API(ResetDataset);
     REGISTER_API(DbSize);
     REGISTER_API(RandomKey);
diff --git a/src/quicklist.c b/src/quicklist.c
index 1731e93364a..e82d8551ea2 100644
--- a/src/quicklist.c
+++ b/src/quicklist.c
@@ -57,6 +57,8 @@ int quicklistisSetPackedThreshold(size_t sz) {
     /* Don't allow threshold to be set above or even slightly below 4GB */
     if (sz > (1ull<<32) - (1<<20)) {
         return 0;
+    } else if (sz == 0) { /* 0 means restore threshold */
+        sz = (1 << 30);
     }
     packed_threshold = sz;
     return 1;
@@ -177,6 +179,7 @@ REDIS_STATIC quicklistNode *quicklistCreateNode(void) {
     node->encoding = QUICKLIST_NODE_ENCODING_RAW;
     node->container = QUICKLIST_NODE_CONTAINER_PACKED;
     node->recompress = 0;
+    node->dont_compress = 0;
     return node;
 }
 
@@ -212,6 +215,7 @@ REDIS_STATIC int __quicklistCompressNode(quicklistNode *node) {
 #ifdef REDIS_TEST
     node->attempted_compress = 1;
 #endif
+    if (node->dont_compress) return 0;
 
     /* validate that the node is neither
      * tail nor head (it has prev and next)*/
@@ -748,12 +752,15 @@ void quicklistReplaceEntry(quicklistIter *iter, quicklistEntry *entry,
             __quicklistDelNode(quicklist, entry->node);
         }
     } else {
+        entry->node->dont_compress = 1; /* Prevent compression in quicklistInsertAfter() */
         quicklistInsertAfter(iter, entry, data, sz);
         if (entry->node->count == 1) {
             __quicklistDelNode(quicklist, entry->node);
         } else {
             unsigned char *p = lpSeek(entry->node->entry, -1);
             quicklistDelIndex(quicklist, entry->node, &p);
+            entry->node->dont_compress = 0; /* Re-enable compression */
+            quicklistCompress(quicklist, entry->node);
             quicklistCompress(quicklist, entry->node->next);
         }
     }
@@ -905,6 +912,9 @@ REDIS_STATIC quicklistNode *_quicklistSplitNode(quicklistNode *node, int offset,
     /* Copy original listpack so we can split it */
     memcpy(new_node->entry, node->entry, zl_sz);
 
+    /* Need positive offset for calculating extent below. */
+    if (offset < 0) offset = node->count + offset;
+
     /* Ranges to be trimmed: -1 here means "continue deleting until the list ends" */
     int orig_start = after ? offset + 1 : 0;
     int orig_extent = after ? -1 : offset;
@@ -1608,10 +1618,11 @@ void quicklistRepr(unsigned char *ql, int full) {
 
     while(node != NULL) {
         printf("{quicklist node(%d)\n", i++);
-        printf("{container : %s, encoding: %s, size: %zu, recompress: %d, attempted_compress: %d}\n",
+        printf("{container : %s, encoding: %s, size: %zu, count: %d, recompress: %d, attempted_compress: %d}\n",
                QL_NODE_IS_PLAIN(node) ? "PLAIN": "PACKED",
                (node->encoding == QUICKLIST_NODE_ENCODING_RAW) ? "RAW": "LZF",
                node->sz,
+               node->count,
                node->recompress,
                node->attempted_compress);
 
diff --git a/src/quicklist.h b/src/quicklist.h
index 4d87fea6f7b..41123182137 100644
--- a/src/quicklist.h
+++ b/src/quicklist.h
@@ -53,7 +53,8 @@ typedef struct quicklistNode {
     unsigned int container : 2;  /* PLAIN==1 or PACKED==2 */
     unsigned int recompress : 1; /* was this node previous compressed? */
     unsigned int attempted_compress : 1; /* node can't compress; too small */
-    unsigned int extra : 10; /* more bits to steal for future usage */
+    unsigned int dont_compress : 1; /* prevent compression of entry that will be used later */
+    unsigned int extra : 9; /* more bits to steal for future usage */
 } quicklistNode;
 
 /* quicklistLZF is a 8+N byte struct holding 'sz' followed by 'compressed'.
diff --git a/src/redis-cli.c b/src/redis-cli.c
index 0de78c67ef9..0f19a7fac24 100644
--- a/src/redis-cli.c
+++ b/src/redis-cli.c
@@ -6331,10 +6331,10 @@ static int clusterManagerCommandCreate(int argc, char **argv) {
                  * So if (bus_port == 0) or (bus_port == port + CLUSTER_MANAGER_PORT_INCR),
                  * we just call CLUSTER MEET with 2 arguments, using the old form. */
                 reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d",
-                                                first->ip, first->port);
+                                                first_ip, first->port);
             } else {
                 reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d %d",
-                                                first->ip, first->port, first->bus_port);
+                                                first_ip, first->port, first->bus_port);
             }
             int is_err = 0;
             if (reply != NULL) {
@@ -6525,7 +6525,7 @@ static int clusterManagerCommandAddNode(int argc, char **argv) {
                                         first_ip, first->port);
     } else {
         reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d %d",
-                                        first->ip, first->port, first->bus_port);
+                                        first_ip, first->port, first->bus_port);
     }
 
     if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL)))
diff --git a/src/replication.c b/src/replication.c
index b929b046045..cf053779d37 100644
--- a/src/replication.c
+++ b/src/replication.c
@@ -103,7 +103,7 @@ int bg_unlink(const char *filename) {
             errno = old_errno;
             return -1;
         }
-        bioCreateCloseJob(fd);
+        bioCreateCloseJob(fd, 0);
         return 0; /* Success. */
     }
 }
@@ -2149,7 +2149,7 @@ void readSyncBulkPayload(connection *conn) {
             return;
         }
         /* Close old rdb asynchronously. */
-        if (old_rdb_fd != -1) bioCreateCloseJob(old_rdb_fd);
+        if (old_rdb_fd != -1) bioCreateCloseJob(old_rdb_fd, 0);
 
         /* Sync the directory to ensure rename is persisted */
         if (fsyncFileDir(server.rdb_filename) == -1) {
diff --git a/src/script_lua.c b/src/script_lua.c
index 22eb58102af..29edf344b3f 100644
--- a/src/script_lua.c
+++ b/src/script_lua.c
@@ -628,7 +628,7 @@ static void luaReplyToRedisReply(client *c, client* script_client, lua_State *lu
         /* Handle error reply. */
         /* we took care of the stack size on function start */
         lua_pushstring(lua,"err");
-        lua_gettable(lua,-2);
+        lua_rawget(lua,-2);
         t = lua_type(lua,-1);
         if (t == LUA_TSTRING) {
             lua_pop(lua, 1); /* pop the error message, we will use luaExtractErrorInformation to get error information */
@@ -646,7 +646,7 @@ static void luaReplyToRedisReply(client *c, client* script_client, lua_State *lu
 
         /* Handle status reply. */
         lua_pushstring(lua,"ok");
-        lua_gettable(lua,-2);
+        lua_rawget(lua,-2);
         t = lua_type(lua,-1);
         if (t == LUA_TSTRING) {
             sds ok = sdsnew(lua_tostring(lua,-1));
@@ -660,7 +660,7 @@ static void luaReplyToRedisReply(client *c, client* script_client, lua_State *lu
 
         /* Handle double reply. */
         lua_pushstring(lua,"double");
-        lua_gettable(lua,-2);
+        lua_rawget(lua,-2);
         t = lua_type(lua,-1);
         if (t == LUA_TNUMBER) {
             addReplyDouble(c,lua_tonumber(lua,-1));
@@ -671,7 +671,7 @@ static void luaReplyToRedisReply(client *c, client* script_client, lua_State *lu
 
         /* Handle big number reply. */
         lua_pushstring(lua,"big_number");
-        lua_gettable(lua,-2);
+        lua_rawget(lua,-2);
         t = lua_type(lua,-1);
         if (t == LUA_TSTRING) {
             sds big_num = sdsnewlen(lua_tostring(lua,-1), lua_strlen(lua,-1));
@@ -685,16 +685,16 @@ static void luaReplyToRedisReply(client *c, client* script_client, lua_State *lu
 
         /* Handle verbatim reply. */
         lua_pushstring(lua,"verbatim_string");
-        lua_gettable(lua,-2);
+        lua_rawget(lua,-2);
         t = lua_type(lua,-1);
         if (t == LUA_TTABLE) {
             lua_pushstring(lua,"format");
-            lua_gettable(lua,-2);
+            lua_rawget(lua,-2);
             t = lua_type(lua,-1);
             if (t == LUA_TSTRING){
                 char* format = (char*)lua_tostring(lua,-1);
                 lua_pushstring(lua,"string");
-                lua_gettable(lua,-3);
+                lua_rawget(lua,-3);
                 t = lua_type(lua,-1);
                 if (t == LUA_TSTRING){
                     size_t len;
@@ -711,7 +711,7 @@ static void luaReplyToRedisReply(client *c, client* script_client, lua_State *lu
 
         /* Handle map reply. */
         lua_pushstring(lua,"map");
-        lua_gettable(lua,-2);
+        lua_rawget(lua,-2);
         t = lua_type(lua,-1);
         if (t == LUA_TTABLE) {
             int maplen = 0;
@@ -734,7 +734,7 @@ static void luaReplyToRedisReply(client *c, client* script_client, lua_State *lu
 
         /* Handle set reply. */
         lua_pushstring(lua,"set");
-        lua_gettable(lua,-2);
+        lua_rawget(lua,-2);
         t = lua_type(lua,-1);
         if (t == LUA_TTABLE) {
             int setlen = 0;
@@ -761,7 +761,7 @@ static void luaReplyToRedisReply(client *c, client* script_client, lua_State *lu
         while(1) {
             /* we took care of the stack size on function start */
             lua_pushnumber(lua,j++);
-            lua_gettable(lua,-2);
+            lua_rawget(lua,-2);
             t = lua_type(lua,-1);
             if (t == LUA_TNIL) {
                 lua_pop(lua,1);
diff --git a/src/server.c b/src/server.c
index de32ff8479b..b0cef887041 100644
--- a/src/server.c
+++ b/src/server.c
@@ -3584,6 +3584,23 @@ int commandCheckArity(client *c, sds *err) {
     return 1;
 }
 
+/* If we're executing a script, try to extract a set of command flags from
+ * it, in case it declared them. Note this is just an attempt, we don't yet
+ * know the script command is well formed.*/
+uint64_t getCommandFlags(client *c) {
+    uint64_t cmd_flags = c->cmd->flags;
+
+    if (c->cmd->proc == fcallCommand || c->cmd->proc == fcallroCommand) {
+        cmd_flags = fcallGetCommandFlags(c, cmd_flags);
+    } else if (c->cmd->proc == evalCommand || c->cmd->proc == evalRoCommand ||
+               c->cmd->proc == evalShaCommand || c->cmd->proc == evalShaRoCommand)
+    {
+        cmd_flags = evalGetCommandFlags(c, cmd_flags);
+    }
+
+    return cmd_flags;
+}
+
 /* If this function gets called we already read a whole
  * command, arguments are in the client argv/argc fields.
  * processCommand() execute the command or prepare the
@@ -3648,19 +3665,7 @@ int processCommand(client *c) {
         }
     }
 
-    /* If we're executing a script, try to extract a set of command flags from
-     * it, in case it declared them. Note this is just an attempt, we don't yet
-     * know the script command is well formed.*/
-    uint64_t cmd_flags = c->cmd->flags;
-    if (c->cmd->proc == evalCommand || c->cmd->proc == evalShaCommand ||
-        c->cmd->proc == evalRoCommand || c->cmd->proc == evalShaRoCommand ||
-        c->cmd->proc == fcallCommand || c->cmd->proc == fcallroCommand)
-    {
-        if (c->cmd->proc == fcallCommand || c->cmd->proc == fcallroCommand)
-            cmd_flags = fcallGetCommandFlags(c, cmd_flags);
-        else
-            cmd_flags = evalGetCommandFlags(c, cmd_flags);
-    }
+    uint64_t cmd_flags = getCommandFlags(c);
 
     int is_read_command = (cmd_flags & CMD_READONLY) ||
                            (c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_READONLY));
@@ -5969,9 +5974,13 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
     }
 
     /* Get info from modules.
-     * if user asked for "everything" or "modules", or a specific section
-     * that's not found yet. */
-    if (everything || dictFind(section_dict, "modules") != NULL || sections < (int)dictSize(section_dict)) {
+     * Returned when the user asked for "everything", "modules", or a specific module section.
+     * We're not aware of the module section names here, and we rather avoid the search when we can.
+     * so we proceed if there's a requested section name that's not found yet, or when the user asked
+     * for "all" with any additional section names. */
+    if (everything || dictFind(section_dict, "modules") != NULL || sections < (int)dictSize(section_dict) ||
+        (all_sections && dictSize(section_dict)))
+    {
 
         info = modulesCollectInfo(info,
                                   everything || dictFind(section_dict, "modules") != NULL ? NULL: section_dict,
diff --git a/src/server.h b/src/server.h
index f33eaa5cefc..3f8f15336da 100644
--- a/src/server.h
+++ b/src/server.h
@@ -2876,6 +2876,7 @@ int zslLexValueLteMax(sds value, zlexrangespec *spec);
 int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level);
 size_t freeMemoryGetNotCountedMemory();
 int overMaxmemoryAfterAlloc(size_t moremem);
+uint64_t getCommandFlags(client *c);
 int processCommand(client *c);
 int processPendingCommandAndInputBuffer(client *c);
 void setupSignalHandlers(void);
@@ -3099,6 +3100,7 @@ int objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
 #define LOOKUP_NONOTIFY (1<<1) /* Don't trigger keyspace event on key misses. */
 #define LOOKUP_NOSTATS (1<<2)  /* Don't update keyspace hits/misses counters. */
 #define LOOKUP_WRITE (1<<3)    /* Delete expired keys even in replicas. */
+#define LOOKUP_NOEXPIRE (1<<4) /* Avoid deleting lazy expired keys. */
 
 void dbAdd(redisDb *db, robj *key, robj *val);
 int dbAddRDBLoad(redisDb *db, sds key, robj *val);
diff --git a/src/t_stream.c b/src/t_stream.c
index 2bcae25b4a3..ea38ab45d9d 100644
--- a/src/t_stream.c
+++ b/src/t_stream.c
@@ -3334,6 +3334,7 @@ void xautoclaimCommand(client *c) {
     robj *o = lookupKeyRead(c->db,c->argv[1]);
     long long minidle; /* Minimum idle time argument, in milliseconds. */
     long count = 100; /* Maximum entries to claim. */
+    const unsigned attempts_factor = 10;
     streamID startid;
     int startex;
     int justid = 0;
@@ -3356,7 +3357,8 @@ void xautoclaimCommand(client *c) {
         int moreargs = (c->argc-1) - j; /* Number of additional arguments. */
         char *opt = c->argv[j]->ptr;
         if (!strcasecmp(opt,"COUNT") && moreargs) {
-            if (getRangeLongFromObjectOrReply(c,c->argv[j+1],1,LONG_MAX,&count,"COUNT must be > 0") != C_OK)
+            long max_count = LONG_MAX / (max(sizeof(streamID), attempts_factor));
+            if (getRangeLongFromObjectOrReply(c,c->argv[j+1],1,max_count,&count,"COUNT must be > 0") != C_OK)
                 return;
             j++;
         } else if (!strcasecmp(opt,"JUSTID")) {
@@ -3383,9 +3385,15 @@ void xautoclaimCommand(client *c) {
         return;
     }
 
+    streamID *deleted_ids = ztrymalloc(count * sizeof(streamID));
+    if (!deleted_ids) {
+        addReplyError(c, "Insufficient memory, failed allocating transient memory, COUNT too high.");
+        return;
+    }
+
     /* Do the actual claiming. */
     streamConsumer *consumer = NULL;
-    long long attempts = count*10;
+    long long attempts = count * attempts_factor;
 
     addReplyArrayLen(c, 3); /* We add another reply later */
     void *endidptr = addReplyDeferredLen(c); /* reply[0] */
@@ -3399,7 +3407,6 @@ void xautoclaimCommand(client *c) {
     size_t arraylen = 0;
     mstime_t now = mstime();
     sds name = c->argv[3]->ptr;
-    streamID *deleted_ids = zmalloc(count * sizeof(streamID));
     int deleted_id_num = 0;
     while (attempts-- && count && raxNext(&ri)) {
         streamNACK *nack = ri.data;
diff --git a/src/tracking.c b/src/tracking.c
index a659e98dd68..c4b534a623a 100644
--- a/src/tracking.c
+++ b/src/tracking.c
@@ -388,7 +388,7 @@ void trackingInvalidateKey(client *c, robj *keyobj, int bcast) {
         /* If the client enabled the NOLOOP mode, don't send notifications
          * about keys changed by the client itself. */
         if (target->flags & CLIENT_TRACKING_NOLOOP &&
-            target == c)
+            target == server.current_client)
         {
             continue;
         }
@@ -423,9 +423,15 @@ void trackingHandlePendingKeyInvalidations() {
         robj *key = listNodeValue(ln);
         /* current_client maybe freed, so we need to send invalidation
          * message only when current_client is still alive */
-        if (server.current_client != NULL)
-            sendTrackingMessage(server.current_client,(char *)key->ptr,sdslen(key->ptr),0);
-        decrRefCount(key);
+        if (server.current_client != NULL) {
+            if (key != NULL) {
+                sendTrackingMessage(server.current_client,(char *)key->ptr,sdslen(key->ptr),0);
+            } else {
+                sendTrackingMessage(server.current_client,shared.null[server.current_client->resp]->ptr,
+                    sdslen(shared.null[server.current_client->resp]->ptr),1);
+            }
+        }
+        if (key != NULL) decrRefCount(key);
     }
     listEmpty(server.tracking_pending_keys);
 }
@@ -454,7 +460,12 @@ void trackingInvalidateKeysOnFlush(int async) {
         while ((ln = listNext(&li)) != NULL) {
             client *c = listNodeValue(ln);
             if (c->flags & CLIENT_TRACKING) {
-                sendTrackingMessage(c,shared.null[c->resp]->ptr,sdslen(shared.null[c->resp]->ptr),1);
+                if (c == server.current_client) {
+                    /* We use a special NULL to indicate that we should send null */
+                    listAddNodeTail(server.tracking_pending_keys,NULL);
+                } else {
+                    sendTrackingMessage(c,shared.null[c->resp]->ptr,sdslen(shared.null[c->resp]->ptr),1);
+                }
             }
         }
     }
diff --git a/src/version.h b/src/version.h
index fd58375e917..f0c184d0f7e 100644
--- a/src/version.h
+++ b/src/version.h
@@ -1,2 +1,2 @@
-#define REDIS_VERSION "7.0.4"
-#define REDIS_VERSION_NUM 0x00070004
+#define REDIS_VERSION "7.0.5"
+#define REDIS_VERSION_NUM 0x00070005


Test patch:
diff --git a/tests/cluster/tests/16-transactions-on-replica.tcl b/tests/cluster/tests/16-transactions-on-replica.tcl
index ee9f7c6e6a3..ec5699c9899 100644
--- a/tests/cluster/tests/16-transactions-on-replica.tcl
+++ b/tests/cluster/tests/16-transactions-on-replica.tcl
@@ -69,3 +69,11 @@ test "read-only blocking operations from replica" {
     assert {$res eq {foo bar}}
     $rd close
 }
+
+test "reply MOVED when eval from replica for update" {
+    catch {[$replica eval {#!lua
+        return redis.call('del','a')
+        } 1 a
+    ]} err
+    assert {[string range $err 0 4] eq {MOVED}}
+}
\ No newline at end of file
diff --git a/tests/support/cluster_helper.tcl b/tests/support/cluster_helper.tcl
new file mode 100644
index 00000000000..42b99ca833c
--- /dev/null
+++ b/tests/support/cluster_helper.tcl
@@ -0,0 +1,96 @@
+# Helper functions specifically for setting up and configuring redis
+# clusters.
+
+# Check if cluster configuration is consistent.
+proc cluster_config_consistent {} {
+    for {set j 0} {$j < [llength $::servers]} {incr j} {
+        if {$j == 0} {
+            set base_cfg [R $j cluster slots]
+        } else {
+            if {[R $j cluster slots] != $base_cfg} {
+                return 0
+            }
+        }
+    }
+
+    return 1
+}
+
+# Wait for cluster configuration to propagate and be consistent across nodes.
+proc wait_for_cluster_propagation {} {
+    wait_for_condition 50 100 {
+        [cluster_config_consistent] eq 1
+    } else {
+        fail "cluster config did not reach a consistent state"
+    }
+}
+
+# Check that cluster nodes agree about "state", or raise an error.
+proc wait_for_cluster_state {state} {
+    for {set j 0} {$j < [llength $::servers]} {incr j} {
+        wait_for_condition 100 50 {
+            [CI $j cluster_state] eq $state
+        } else {
+            fail "Cluster node $j cluster_state:[CI $j cluster_state]"
+        }
+    }
+}
+
+# Default slot allocation for clusters, each master has a continuous block
+# and approximately equal number of slots.
+proc continuous_slot_allocation {masters} {
+    set avg [expr double(16384) / $masters]
+    set slot_start 0
+    for {set j 0} {$j < $masters} {incr j} {
+        set slot_end [expr int(ceil(($j + 1) * $avg) - 1)]
+        R $j cluster addslotsrange $slot_start $slot_end
+        set slot_start [expr $slot_end + 1]
+    }
+}
+
+# Setup method to be executed to configure the cluster before the
+# tests run.
+proc cluster_setup {masters node_count slot_allocator code} {
+    # Have all nodes meet
+    for {set i 1} {$i < $node_count} {incr i} {
+        R 0 CLUSTER MEET [srv -$i host] [srv -$i port]
+    }
+
+    $slot_allocator $masters
+
+    wait_for_cluster_propagation
+
+    # Setup master/replica relationships
+    for {set i 0} {$i < $masters} {incr i} {
+        set nodeid [R $i CLUSTER MYID]
+        for {set j [expr $i + $masters]} {$j < $node_count} {incr j $masters} {
+            R $j CLUSTER REPLICATE $nodeid
+        }
+    }
+
+    wait_for_cluster_propagation
+    wait_for_cluster_state "ok"
+
+    uplevel 1 $code
+}
+
+# Start a cluster with the given number of masters and replicas. Replicas
+# will be allocated to masters by round robin.
+proc start_cluster {masters replicas options code {slot_allocator continuous_slot_allocation}} {
+    set node_count [expr $masters + $replicas]
+
+    # Set the final code to be the tests + cluster setup
+    set code [list cluster_setup $masters $node_count $slot_allocator $code]
+
+    # Configure the starting of multiple servers. Set cluster node timeout
+    # aggressively since many tests depend on ping/pong messages. 
+    set cluster_options [list overrides [list cluster-enabled yes cluster-node-timeout 500]]
+    set options [concat $cluster_options $options]
+
+    # Cluster mode only supports a single database, so before executing the tests
+    # it needs to be configured correctly and needs to be reset after the tests. 
+    set old_singledb $::singledb
+    set ::singledb 1
+    start_multiple_servers $node_count $options $code
+    set ::singledb $old_singledb
+}
diff --git a/tests/support/server.tcl b/tests/support/server.tcl
index 4a993f55289..b673b70ae93 100644
--- a/tests/support/server.tcl
+++ b/tests/support/server.tcl
@@ -253,12 +253,15 @@ proc tags {tags code} {
 
 # Write the configuration in the dictionary 'config' in the specified
 # file name.
-proc create_server_config_file {filename config} {
+proc create_server_config_file {filename config config_lines} {
     set fp [open $filename w+]
     foreach directive [dict keys $config] {
         puts -nonewline $fp "$directive "
         puts $fp [dict get $config $directive]
     }
+    foreach {config_line_directive config_line_args} $config_lines {
+        puts $fp "$config_line_directive $config_line_args"
+    }
     close $fp
 }
 
@@ -406,6 +409,7 @@ proc start_server {options {code undefined}} {
     set tags {}
     set args {}
     set keep_persistence false
+    set config_lines {}
 
     # parse options
     foreach {option value} $options {
@@ -416,6 +420,9 @@ proc start_server {options {code undefined}} {
             "overrides" {
                 set overrides $value
             }
+            "config_lines" {
+                set config_lines $value
+            }
             "args" {
                 set args $value
             }
@@ -503,7 +510,7 @@ proc start_server {options {code undefined}} {
 
     # write new configuration to temporary file
     set config_file [tmpfile redis.conf]
-    create_server_config_file $config_file $config
+    create_server_config_file $config_file $config $config_lines
 
     set stdout [format "%s/%s" [dict get $config "dir"] "stdout"]
     set stderr [format "%s/%s" [dict get $config "dir"] "stderr"]
@@ -544,7 +551,7 @@ proc start_server {options {code undefined}} {
             } else {
                 dict set config port $port
             }
-            create_server_config_file $config_file $config
+            create_server_config_file $config_file $config $config_lines
 
             # Truncate log so wait_server_started will not be looking at
             # output of the failed server.
diff --git a/tests/support/util.tcl b/tests/support/util.tcl
index fd72dcf7567..db21dea96b5 100644
--- a/tests/support/util.tcl
+++ b/tests/support/util.tcl
@@ -934,6 +934,12 @@ proc config_set {param value {options {}}} {
     }
 }
 
+proc config_get_set {param value {options {}}} {
+    set config [lindex [r config get $param] 1]
+    config_set $param $value $options
+    return $config
+}
+
 proc delete_lines_with_pattern {filename tmpfilename pattern} {
     set fh_in [open $filename r]
     set fh_out [open $tmpfilename w]
diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl
index 2dcccb8cff7..6f7d14782f6 100644
--- a/tests/test_helper.tcl
+++ b/tests/test_helper.tcl
@@ -8,6 +8,7 @@ set tcl_precision 17
 source tests/support/redis.tcl
 source tests/support/aofmanifest.tcl
 source tests/support/server.tcl
+source tests/support/cluster_helper.tcl
 source tests/support/tmpfile.tcl
 source tests/support/test.tcl
 source tests/support/util.tcl
@@ -95,6 +96,7 @@ set ::all_tests {
     unit/violations
     unit/replybufsize
     unit/cluster-scripting
+    unit/cluster/misc
 }
 # Index to the next test to run in the ::all_tests list.
 set ::next_test 0
@@ -197,6 +199,13 @@ proc r {args} {
     [srv $level "client"] {*}$args
 }
 
+# Provide easy access to a client for an inner server. Requires a positive
+# index, unlike r which uses an optional negative index.
+proc R {n args} {
+    set level [expr -1*$n]
+    [srv $level "client"] {*}$args
+}
+
 proc reconnect {args} {
     set level [lindex $args 0]
     if {[string length $level] == 0 || ![string is integer $level]} {
@@ -275,6 +284,11 @@ proc s {args} {
     status [srv $level "client"] [lindex $args 0]
 }
 
+# Get the specified field from the givens instances cluster info output.
+proc CI {index field} {
+    getInfoProperty [R $index cluster info] $field
+}
+
 # Provide easy access to CLUSTER INFO properties. Same semantic as "proc s".
 proc csi {args} {
     set level 0
diff --git a/tests/unit/acl-v2.tcl b/tests/unit/acl-v2.tcl
index 500a7c7296d..451b5883e2d 100644
--- a/tests/unit/acl-v2.tcl
+++ b/tests/unit/acl-v2.tcl
@@ -195,6 +195,7 @@ start_server {tags {"acl external:skip"}} {
 
         # We don't have the permission to WRITE key.
         assert_error {*NOPERM*keys*} {$r2 bitfield readstr set u4 0 1}
+        assert_error {*NOPERM*keys*} {$r2 bitfield readstr get u4 0 set u4 0 1}
         assert_error {*NOPERM*keys*} {$r2 bitfield readstr incrby u4 0 1}
     }
 
diff --git a/tests/unit/cluster/misc.tcl b/tests/unit/cluster/misc.tcl
new file mode 100644
index 00000000000..35308b81ab8
--- /dev/null
+++ b/tests/unit/cluster/misc.tcl
@@ -0,0 +1,16 @@
+start_cluster 2 2 {tags {external:skip cluster}} {
+    test {Key lazy expires during key migration} {
+        R 0 DEBUG SET-ACTIVE-EXPIRE 0
+
+        set key_slot [R 0 CLUSTER KEYSLOT FOO]
+        R 0 set FOO BAR PX 10
+        set src_id [R 0 CLUSTER MYID]
+        set trg_id [R 1 CLUSTER MYID]
+        R 0 CLUSTER SETSLOT $key_slot MIGRATING $trg_id
+        R 1 CLUSTER SETSLOT $key_slot IMPORTING $src_id
+        after 11
+        assert_error {ASK*} {R 0 GET FOO}
+        R 0 ping
+    } {PONG}
+}
+
diff --git a/tests/unit/functions.tcl b/tests/unit/functions.tcl
index a1f6acdffb9..a907badbea1 100644
--- a/tests/unit/functions.tcl
+++ b/tests/unit/functions.tcl
@@ -38,7 +38,7 @@ start_server {tags {"scripting"}} {
             r function load [get_function_code LUA {bad\0foramat} test {return 'hello1'}]
         } e
         set _ $e
-    } {*Library names can only contain letters and numbers*}
+    } {*Library names can only contain letters, numbers, or underscores(_)*}
 
     test {FUNCTION - Create library with unexisting engine} {
         catch {
@@ -597,7 +597,7 @@ start_server {tags {"scripting"}} {
             }
         } e
         set _ $e
-    } {*Function names can only contain letters and numbers and must be at least one character long*}
+    } {*Library names can only contain letters, numbers, or underscores(_) and must be at least one character long*}
 
     test {LIBRARIES - test registration with empty name} {
         catch {
@@ -606,7 +606,7 @@ start_server {tags {"scripting"}} {
             }
         } e
         set _ $e
-    } {*Function names can only contain letters and numbers and must be at least one character long*}
+    } {*Library names can only contain letters, numbers, or underscores(_) and must be at least one character long*}
 
     test {LIBRARIES - math.random from function load} {
         catch {
diff --git a/tests/unit/moduleapi/cmdintrospection.tcl b/tests/unit/moduleapi/cmdintrospection.tcl
index 8ac49ed9fff..4d67af1e116 100644
--- a/tests/unit/moduleapi/cmdintrospection.tcl
+++ b/tests/unit/moduleapi/cmdintrospection.tcl
@@ -40,4 +40,8 @@ start_server {tags {"modules"}} {
 
         assert_equal $redis_reply $module_reply
     }
+
+    test "Unload the module - cmdintrospection" {
+        assert_equal {OK} [r module unload cmdintrospection]
+    }
 }
diff --git a/tests/unit/moduleapi/infotest.tcl b/tests/unit/moduleapi/infotest.tcl
index 354487a191c..ccd8c4ecbe2 100644
--- a/tests/unit/moduleapi/infotest.tcl
+++ b/tests/unit/moduleapi/infotest.tcl
@@ -44,6 +44,14 @@ start_server {tags {"modules"}} {
         assert { [string match "*used_memory*" $info] }
     }
 
+    test {module info all infotest} {
+        set info [r info all infotest]
+        # info all infotest should contain both ALL and the module information
+        assert { [string match "*Spanish*" $info] }
+        assert { [string match "*infotest_*" $info] }
+        assert { [string match "*used_memory*" $info] }
+    }
+
     test {module info everything} {
         set info [r info everything]
         # info everything contains all default sections, but not ones for crash report
diff --git a/tests/unit/moduleapi/misc.tcl b/tests/unit/moduleapi/misc.tcl
index e9efdc2b338..04618ab89d4 100644
--- a/tests/unit/moduleapi/misc.tcl
+++ b/tests/unit/moduleapi/misc.tcl
@@ -173,6 +173,29 @@ start_server {tags {"modules"}} {
         r config set maxmemory 0
     } {OK} {needs:config-maxmemory}
 
+    test {rm_call OOM Eval} {
+        r config set maxmemory 1
+        r config set maxmemory-policy volatile-lru
+
+        # use the M flag without allow-oom shebang flag
+        assert_error {OOM *} {
+            r test.rm_call_flags M eval {#!lua
+                redis.call('set','x',1)
+                return 1
+            } 1 x
+        }
+
+        # add the M flag with allow-oom shebang flag
+        assert_equal {1} [
+            r test.rm_call_flags M eval {#!lua flags=allow-oom
+                redis.call('set','x',1)
+                return 1
+            } 1 x
+        ]
+
+        r config set maxmemory 0
+    } {OK} {needs:config-maxmemory}
+
     test {rm_call write flag} {
         # add the W flag
         assert_error {ERR Write command 'set' was called while write is not allowed.} {
diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl
index f1c05a4d4e6..cca24c1bd4d 100644
--- a/tests/unit/scripting.tcl
+++ b/tests/unit/scripting.tcl
@@ -59,6 +59,20 @@ start_server {tags {"scripting"}} {
         run_script {return 'hello'} 0
     } {hello}
 
+    test {EVAL - Return _G} {
+        run_script {return _G} 0
+    } {}
+
+    test {EVAL - Return table with a metatable that raise error} {
+        run_script {local a = {}; setmetatable(a,{__index=function() foo() end}) return a} 0
+    } {}
+
+    test {EVAL - Return table with a metatable that call redis} {
+        run_script {local a = {}; setmetatable(a,{__index=function() redis.call('set', 'x', '1') end}) return a} 0
+        # make sure x was not set
+        r get x
+    } {}
+
     test {EVAL - Lua integer -> Redis protocol type conversion} {
         run_script {return 100.5} 0
     } {100}
diff --git a/tests/unit/tracking.tcl b/tests/unit/tracking.tcl
index 6270ce47290..8d4fd8c894f 100644
--- a/tests/unit/tracking.tcl
+++ b/tests/unit/tracking.tcl
@@ -208,6 +208,19 @@ start_server {tags {"tracking network"}} {
         assert {$res eq {key1}}
     }
 
+    test {Invalid keys should not be tracked for scripts in NOLOOP mode} {
+        $rd_sg CLIENT TRACKING off
+        $rd_sg CLIENT TRACKING on NOLOOP
+        $rd_sg HELLO 3
+        $rd_sg SET key1 1
+        assert_equal "1" [$rd_sg GET key1]
+
+        # For write command in script, invalid key should not be tracked with NOLOOP flag
+        $rd_sg eval "return redis.call('set', 'key1', '2')" 1 key1
+        assert_equal "2" [$rd_sg GET key1]
+        $rd_sg CLIENT TRACKING off
+    }
+
     test {Tracking only occurs for scripts when a command calls a read-only command} {
         r CLIENT TRACKING off
         r CLIENT TRACKING on
@@ -548,6 +561,20 @@ start_server {tags {"tracking network"}} {
         assert_equal [lindex [$rd_redirection read] 2] {}
     }
 
+    test {flushdb tracking invalidation message is not interleaved with transaction response} {
+        clean_all
+        r HELLO 3
+        r CLIENT TRACKING on
+        r SET a{t} 1
+        r GET a{t}
+        r MULTI
+        r FLUSHDB
+        set res [r EXEC]
+        assert_equal $res {OK}
+        # Consume the invalidate message which is after command response
+        r read
+    } {invalidate {}}
+
     # Keys are defined to be evicted 100 at a time by default.
     # If after eviction the number of keys still surpasses the limit
     # defined in tracking-table-max-keys, we increases eviction 
diff --git a/tests/unit/type/list.tcl b/tests/unit/type/list.tcl
index 50870a0411c..3133923d6e5 100644
--- a/tests/unit/type/list.tcl
+++ b/tests/unit/type/list.tcl
@@ -66,7 +66,8 @@ start_server [list overrides [list save ""] ] {
         assert_equal [r lpop list4] [string repeat c 500]
         assert_equal [r lpop list4] [string repeat b 500]
         assert_equal [r lpop list4] [string repeat a 500]
-    } {} {needs:debug}
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 
     test {plain node check compression with ltrim} {
         r debug quicklist-packed-threshold 1b
@@ -75,8 +76,9 @@ start_server [list overrides [list save ""] ] {
         r rpush list5 [string repeat c 500]
         assert_equal [string repeat b 500] [r lindex list5 1]
         r LTRIM list5 1 -1
-        r llen list5
-    } {2} {needs:debug}
+        assert_equal [r llen list5] 2
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 
     test {plain node check compression using lset} {
         r debug quicklist-packed-threshold 1b
@@ -86,7 +88,8 @@ start_server [list overrides [list save ""] ] {
         r lpush list6 [string repeat c 500]
         r LSET list6 0 [string repeat d 500]
         assert_equal [string repeat d 500] [r lindex list6 0]
-    } {} {needs:debug}
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 
     # revert config for external mode tests.
     r config set list-compress-depth 0
@@ -115,7 +118,8 @@ start_server [list overrides [list save ""] ] {
         r lpush lst bb
         r debug reload
         assert_equal [r rpop lst] "xxxxxxxxxx"
-    } {} {needs:debug}
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 
     # basic command check for plain nodes - "LINDEX & LINSERT"
     test {Test LINDEX and LINSERT on plain nodes} {
@@ -129,7 +133,8 @@ start_server [list overrides [list save ""] ] {
         r linsert lst BEFORE "9" "7"
         r linsert lst BEFORE "9" "xxxxxxxxxxx"
         assert {[r lindex lst 3] eq "xxxxxxxxxxx"}
-    } {} {needs:debug}
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 
     # basic command check for plain nodes - "LTRIM"
     test {Test LTRIM on plain nodes} {
@@ -140,7 +145,8 @@ start_server [list overrides [list save ""] ] {
         r lpush lst1 9
         r LTRIM lst1 1 -1
         assert_equal [r llen lst1] 2
-    } {} {needs:debug}
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 
     # basic command check for plain nodes - "LREM"
     test {Test LREM on plain nodes} {
@@ -153,7 +159,8 @@ start_server [list overrides [list save ""] ] {
         r lpush lst 9
         r LREM lst -2 "one"
         assert_equal [r llen lst] 2
-    } {} {needs:debug}
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 
     # basic command check for plain nodes - "LPOS"
     test {Test LPOS on plain nodes} {
@@ -164,7 +171,8 @@ start_server [list overrides [list save ""] ] {
         r RPUSH lst "cc"
         r LSET lst 0 "xxxxxxxxxxx"
         assert_equal [r LPOS lst "xxxxxxxxxxx"] 0
-    } {} {needs:debug}
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 
     # basic command check for plain nodes - "LMOVE"
     test {Test LMOVE on plain nodes} {
@@ -183,7 +191,8 @@ start_server [list overrides [list save ""] ] {
         assert_equal [r lpop lst2{t}] "cc"
         assert_equal [r lpop lst{t}] "dd"
         assert_equal [r lpop lst{t}] "xxxxxxxxxxx"
-    } {} {needs:debug}
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 
     # testing LSET with combinations of node types
     # plain->packed , packed->plain, plain->plain, packed->packed
@@ -206,7 +215,8 @@ start_server [list overrides [list save ""] ] {
         r lset lst 0 "cc"
         set s1 [r lpop lst]
         assert_equal $s1 "cc"
-    } {} {needs:debug}
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 
     # checking LSET in case ziplist needs to be split
     test {Test LSET with packed is split in the middle} {
@@ -223,14 +233,15 @@ start_server [list overrides [list save ""] ] {
         assert_equal [r lpop lst] [string repeat e 10]
         assert_equal [r lpop lst] "dd"
         assert_equal [r lpop lst] "ee"
-    } {} {needs:debug}
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 
 
     # repeating "plain check LSET with combinations"
     # but now with single item in each ziplist
     test {Test LSET with packed consist only one item} {
         r flushdb
-        r config set list-max-ziplist-size 1
+        set original_config [config_get_set list-max-ziplist-size 1]
         r debug quicklist-packed-threshold 1b
         r RPUSH lst "aa"
         r RPUSH lst "bb"
@@ -249,7 +260,46 @@ start_server [list overrides [list save ""] ] {
         r lset lst 0 "cc"
         set s1 [r lpop lst]
         assert_equal $s1 "cc"
-    } {} {needs:debug}
+        r debug quicklist-packed-threshold 0
+        r config set list-max-ziplist-size $original_config
+    } {OK} {needs:debug}
+
+    test {Crash due to delete entry from a compress quicklist node} {
+        r flushdb
+        r debug quicklist-packed-threshold 100b
+        set original_config [config_get_set list-compress-depth 1]
+
+        set small_ele [string repeat x 32]
+        set large_ele [string repeat x 100]
+
+        # Push a large element
+        r RPUSH lst $large_ele
+
+        # Insert two elements and keep them in the same node
+        r RPUSH lst $small_ele
+        r RPUSH lst $small_ele
+
+        # When setting the position of -1 to a large element, we first insert
+        # a large element at the end and then delete its previous element.
+        r LSET lst -1 $large_ele
+        assert_equal "$large_ele $small_ele $large_ele" [r LRANGE lst 0 -1]
+
+        r debug quicklist-packed-threshold 0
+        r config set list-compress-depth $original_config
+    } {OK} {needs:debug}
+
+    test {Crash due to split quicklist node wrongly} {
+        r flushdb
+        r debug quicklist-packed-threshold 10b
+
+        r LPUSH lst "aa"
+        r LPUSH lst "bb"
+        r LSET lst -2 [string repeat x 10]
+        r RPOP lst
+        assert_equal [string repeat x 10] [r LRANGE lst 0 -1]
+
+        r debug quicklist-packed-threshold 0
+    } {OK} {needs:debug}
 }
 
 run_solo {list-large-memory} {
diff --git a/tests/unit/type/stream-cgroups.tcl b/tests/unit/type/stream-cgroups.tcl
index a6ed716fb84..1fcfe87fa35 100644
--- a/tests/unit/type/stream-cgroups.tcl
+++ b/tests/unit/type/stream-cgroups.tcl
@@ -718,6 +718,10 @@ start_server {
         assert_equal [r XPENDING x grp - + 10 Alice] {}
     }
 
+    test {XAUTOCLAIM with out of range count} {
+        assert_error {ERR COUNT*} {r XAUTOCLAIM x grp Bob 0 3-0 COUNT 8070450532247928833}
+    }
+
     test {XCLAIM with trimming} {
         r DEL x
         r config set stream-node-max-entries 2