Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e0f533932 | ||
|
|
dfa5b12627 | ||
|
|
39ea429d03 | ||
|
|
8658e2aeb9 | ||
|
|
4c6ffd5125 | ||
|
|
08961e16db | ||
|
|
e93df78b8b | ||
|
|
f9530e77d1 | ||
|
|
9d11a4f862 | ||
|
|
5f317a632c | ||
|
|
38e9e8ed38 | ||
|
|
24080114cc | ||
|
|
b9405a7d9d | ||
|
|
a93a319ba2 | ||
|
|
c214e0f1eb | ||
|
|
c2289ea402 | ||
|
|
3138dbac1b | ||
|
|
3415d7ae63 | ||
|
|
6717df1388 | ||
|
|
949d4efa36 | ||
|
|
9b45a148f2 | ||
|
|
5d7b60e117 | ||
|
|
7a9584b15d | ||
|
|
98077651bf | ||
|
|
b2a8c675a7 | ||
|
|
a96216bb9b | ||
|
|
2619411a4c |
@@ -91,8 +91,8 @@ jobs:
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y gnupg2
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list
|
||||
echo "deb http://archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list
|
||||
echo "deb http://archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
|
||||
@@ -1073,8 +1073,8 @@ jobs:
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y gnupg2
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list
|
||||
echo "deb http://archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list
|
||||
echo "deb http://archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
@@ -1121,8 +1121,8 @@ jobs:
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y gnupg2
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list
|
||||
echo "deb http://archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list
|
||||
echo "deb http://archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
@@ -1175,8 +1175,8 @@ jobs:
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y gnupg2
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list
|
||||
echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list
|
||||
echo "deb http://archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list
|
||||
echo "deb http://archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
--tags -slow
|
||||
- name: Archive redis log
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-external-redis-log
|
||||
path: external-redis.log
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
--tags -slow
|
||||
- name: Archive redis log
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-external-cluster-log
|
||||
path: external-redis-cluster.log
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
--tags "-slow -needs:debug"
|
||||
- name: Archive redis log
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-external-redis-nodebug-log
|
||||
path: external-redis-nodebug.log
|
||||
|
||||
@@ -11,6 +11,52 @@ CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
SECURITY: There are security fixes in the release.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
================================================================================
|
||||
Redis 7.4.5 Released Sun 6 Jul 2025 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Update urgency: `SECURITY`: There are security fixes in the release.
|
||||
|
||||
### Security fixes
|
||||
|
||||
* (CVE-2025-32023) Fix out-of-bounds write in `HyperLogLog` commands
|
||||
* (CVE-2025-48367) Retry accepting other connections even if the accepted connection reports an error
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.4.4 Released Tue 27 May 2025 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Update urgency: `SECURITY`: There are security fixes in the release.
|
||||
|
||||
### Security fixes
|
||||
|
||||
* (CVE-2025-27151) redis-check-aof may lead to stack overflow and potential RCE
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- #13966, #13932 `CLUSTER SLOTS` - TLS port update not reflected in CLUSTER SLOTS
|
||||
- #13958 `XTRIM`, `XADD` - incorrect lag due to trimming stream
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis 7.4.3 Released Wed 23 Apr 2025 12:00:00 IST
|
||||
================================================================================
|
||||
|
||||
Update urgency: `SECURITY`: There are security fixes in the release.
|
||||
|
||||
### Security fixes
|
||||
|
||||
- (CVE-2025-21605) An unauthenticated client can cause an unlimited growth of output buffers
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- #13661 `FUNCTION FLUSH` - memory leak when using jemalloc
|
||||
- #13793 `WAITAOF` returns prematurely
|
||||
- #13853 `SLAVEOF` - crash when clients are blocked on lazy free
|
||||
- #13863 `RANDOMKEY` - infinite loop during client pause
|
||||
- #13877 ShardID inconsistency when both primary and replica support it
|
||||
|
||||
|
||||
================================================================================
|
||||
Redis Community Edition 7.4.2 Released Mon 6 Jan 2025 12:30:00 IDT
|
||||
|
||||
@@ -110,6 +110,9 @@ else
|
||||
ifeq ($(SANITIZER),undefined)
|
||||
MALLOC=libc
|
||||
CFLAGS+=-fsanitize=undefined -fno-sanitize-recover=all -fno-omit-frame-pointer
|
||||
ifeq (clang,$(CLANG))
|
||||
CFLAGS+=-fno-sanitize=function
|
||||
endif
|
||||
LDFLAGS+=-fsanitize=undefined
|
||||
else
|
||||
ifeq ($(SANITIZER),thread)
|
||||
|
||||
+24
@@ -786,3 +786,27 @@ int anetIsFifo(char *filepath) {
|
||||
if (stat(filepath, &sb) == -1) return 0;
|
||||
return S_ISFIFO(sb.st_mode);
|
||||
}
|
||||
|
||||
/* This function must be called after accept4() fails. It returns 1 if 'err'
|
||||
* indicates accepted connection faced an error, and it's okay to continue
|
||||
* accepting next connection by calling accept4() again. Other errors either
|
||||
* indicate programming errors, e.g. calling accept() on a closed fd or indicate
|
||||
* a resource limit has been reached, e.g. -EMFILE, open fd limit has been
|
||||
* reached. In the latter case, caller might wait until resources are available.
|
||||
* See accept4() documentation for details. */
|
||||
int anetAcceptFailureNeedsRetry(int err) {
|
||||
if (err == ECONNABORTED)
|
||||
return 1;
|
||||
|
||||
#if defined(__linux__)
|
||||
/* For details, see 'Error Handling' section on
|
||||
* https://man7.org/linux/man-pages/man2/accept.2.html */
|
||||
if (err == ENETDOWN || err == EPROTO || err == ENOPROTOOPT ||
|
||||
err == EHOSTDOWN || err == ENONET || err == EHOSTUNREACH ||
|
||||
err == EOPNOTSUPP || err == ENETUNREACH)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -52,5 +52,6 @@ int anetPipe(int fds[2], int read_flags, int write_flags);
|
||||
int anetSetSockMarkId(char *err, int fd, uint32_t id);
|
||||
int anetGetError(int fd);
|
||||
int anetIsFifo(char *filepath);
|
||||
int anetAcceptFailureNeedsRetry(int err);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1048,35 +1048,34 @@ void flushAppendOnlyFile(int force) {
|
||||
mstime_t latency;
|
||||
|
||||
if (sdslen(server.aof_buf) == 0) {
|
||||
/* Check if we need to do fsync even the aof buffer is empty,
|
||||
* because previously in AOF_FSYNC_EVERYSEC mode, fsync is
|
||||
* called only when aof buffer is not empty, so if users
|
||||
* stop write commands before fsync called in one second,
|
||||
* the data in page cache cannot be flushed in time. */
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.aof_last_incr_fsync_offset != server.aof_last_incr_size &&
|
||||
server.mstime - server.aof_last_fsync >= 1000 &&
|
||||
!(sync_in_progress = aofFsyncInProgress())) {
|
||||
goto try_fsync;
|
||||
|
||||
/* Check if we need to do fsync even the aof buffer is empty,
|
||||
* the reason is described in the previous AOF_FSYNC_EVERYSEC block,
|
||||
* and AOF_FSYNC_ALWAYS is also checked here to handle a case where
|
||||
* aof_fsync is changed from everysec to always. */
|
||||
} else if (server.aof_fsync == AOF_FSYNC_ALWAYS &&
|
||||
server.aof_last_incr_fsync_offset != server.aof_last_incr_size)
|
||||
{
|
||||
goto try_fsync;
|
||||
} else {
|
||||
if (server.aof_last_incr_fsync_offset == server.aof_last_incr_size) {
|
||||
/* All data is fsync'd already: Update fsynced_reploff_pending just in case.
|
||||
* This is needed to avoid a WAITAOF hang in case a module used RM_Call with the NO_AOF flag,
|
||||
* in which case master_repl_offset will increase but fsynced_reploff_pending won't be updated
|
||||
* (because there's no reason, from the AOF POV, to call fsync) and then WAITAOF may wait on
|
||||
* the higher offset (which contains data that was only propagated to replicas, and not to AOF) */
|
||||
if (!sync_in_progress && server.aof_fsync != AOF_FSYNC_NO)
|
||||
* This is needed to avoid a WAITAOF hang in case a module used RM_Call
|
||||
* with the NO_AOF flag, in which case master_repl_offset will increase but
|
||||
* fsynced_reploff_pending won't be updated (because there's no reason, from
|
||||
* the AOF POV, to call fsync) and then WAITAOF may wait on the higher offset
|
||||
* (which contains data that was only propagated to replicas, and not to AOF) */
|
||||
if (!aofFsyncInProgress())
|
||||
atomicSet(server.fsynced_reploff_pending, server.master_repl_offset);
|
||||
return;
|
||||
} else {
|
||||
/* Check if we need to do fsync even the aof buffer is empty,
|
||||
* because previously in AOF_FSYNC_EVERYSEC mode, fsync is
|
||||
* called only when aof buffer is not empty, so if users
|
||||
* stop write commands before fsync called in one second,
|
||||
* the data in page cache cannot be flushed in time. */
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.mstime - server.aof_last_fsync >= 1000 &&
|
||||
!(sync_in_progress = aofFsyncInProgress()))
|
||||
goto try_fsync;
|
||||
|
||||
/* Check if we need to do fsync even the aof buffer is empty,
|
||||
* the reason is described in the previous AOF_FSYNC_EVERYSEC block,
|
||||
* and AOF_FSYNC_ALWAYS is also checked here to handle a case where
|
||||
* aof_fsync is changed from everysec to always. */
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS)
|
||||
goto try_fsync;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
|
||||
|
||||
@@ -270,6 +270,7 @@ void disconnectAllBlockedClients(void) {
|
||||
|
||||
if (c->bstate.btype == BLOCKED_LAZYFREE) {
|
||||
addReply(c, shared.ok); /* No reason lazy-free to fail */
|
||||
updateStatsOnUnblock(c, 0, 0, 0);
|
||||
c->flags &= ~CLIENT_PENDING_COMMAND;
|
||||
unblockClient(c, 1);
|
||||
} else {
|
||||
|
||||
+37
-30
@@ -93,6 +93,7 @@ int auxTlsPortPresent(clusterNode *n);
|
||||
static void clusterBuildMessageHdr(clusterMsg *hdr, int type, size_t msglen);
|
||||
void freeClusterLink(clusterLink *link);
|
||||
int verifyClusterNodeId(const char *name, int length);
|
||||
static void updateShardId(clusterNode *node, const char *shard_id);
|
||||
|
||||
int getNodeDefaultClientPort(clusterNode *n) {
|
||||
return server.tls_cluster ? n->tls_port : n->tcp_port;
|
||||
@@ -203,12 +204,11 @@ int auxShardIdSetter(clusterNode *n, void *value, int length) {
|
||||
return C_ERR;
|
||||
}
|
||||
memcpy(n->shard_id, value, CLUSTER_NAMELEN);
|
||||
/* if n already has replicas, make sure they all agree
|
||||
* on the shard id */
|
||||
/* if n already has replicas, make sure they all use
|
||||
* the primary shard id */
|
||||
for (int i = 0; i < n->numslaves; i++) {
|
||||
if (memcmp(n->slaves[i]->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0) {
|
||||
return C_ERR;
|
||||
}
|
||||
if (memcmp(n->slaves[i]->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0)
|
||||
updateShardId(n->slaves[i], n->shard_id);
|
||||
}
|
||||
clusterAddNodeToShard(value, n);
|
||||
return C_OK;
|
||||
@@ -550,18 +550,12 @@ int clusterLoadConfig(char *filename) {
|
||||
clusterAddNode(master);
|
||||
}
|
||||
/* shard_id can be absent if we are loading a nodes.conf generated
|
||||
* by an older version of Redis; we should follow the primary's
|
||||
* shard_id in this case */
|
||||
if (auxFieldHandlers[af_shard_id].isPresent(n) == 0) {
|
||||
memcpy(n->shard_id, master->shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNodeToShard(master->shard_id, n);
|
||||
} else if (clusterGetNodesInMyShard(master) != NULL &&
|
||||
memcmp(master->shard_id, n->shard_id, CLUSTER_NAMELEN) != 0)
|
||||
{
|
||||
/* If the primary has been added to a shard, make sure this
|
||||
* node has the same persisted shard id as the primary. */
|
||||
goto fmterr;
|
||||
}
|
||||
* by an older version of Redis;
|
||||
* ignore replica's shard_id in the file, only use the primary's.
|
||||
* If replica precedes primary in file, it will be corrected
|
||||
* later by the auxShardIdSetter */
|
||||
memcpy(n->shard_id, master->shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNodeToShard(master->shard_id, n);
|
||||
n->slaveof = master;
|
||||
clusterNodeAddSlave(master,n);
|
||||
} else if (auxFieldHandlers[af_shard_id].isPresent(n) == 0) {
|
||||
@@ -908,22 +902,33 @@ static void updateAnnouncedHumanNodename(clusterNode *node, char *new) {
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
|
||||
}
|
||||
|
||||
static void assignShardIdToNode(clusterNode *node, const char *shard_id, int flag) {
|
||||
clusterRemoveNodeFromShard(node);
|
||||
memcpy(node->shard_id, shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNodeToShard(shard_id, node);
|
||||
clusterDoBeforeSleep(flag);
|
||||
}
|
||||
|
||||
static void updateShardId(clusterNode *node, const char *shard_id) {
|
||||
if (shard_id && memcmp(node->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
|
||||
clusterRemoveNodeFromShard(node);
|
||||
memcpy(node->shard_id, shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNodeToShard(shard_id, node);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG);
|
||||
}
|
||||
if (shard_id && myself != node && myself->slaveof == node) {
|
||||
if (memcmp(myself->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
|
||||
/* shard-id can diverge right after a rolling upgrade
|
||||
* from pre-7.2 releases */
|
||||
clusterRemoveNodeFromShard(myself);
|
||||
memcpy(myself->shard_id, shard_id, CLUSTER_NAMELEN);
|
||||
clusterAddNodeToShard(shard_id, myself);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
|
||||
/* We always make our best effort to keep the shard-id consistent
|
||||
* between the master and its replicas:
|
||||
*
|
||||
* 1. When updating the master's shard-id, we simultaneously update the
|
||||
* shard-id of all its replicas to ensure consistency.
|
||||
* 2. When updating replica's shard-id, if it differs from its master's shard-id,
|
||||
* we discard this replica's shard-id and continue using master's shard-id.
|
||||
* This applies even if the master does not support shard-id, in which
|
||||
* case we rely on the master's randomly generated shard-id. */
|
||||
if (node->slaveof == NULL) {
|
||||
assignShardIdToNode(node, shard_id, CLUSTER_TODO_SAVE_CONFIG);
|
||||
for (int i = 0; i < clusterNodeNumSlaves(node); i++) {
|
||||
clusterNode *slavenode = clusterNodeGetSlave(node, i);
|
||||
if (memcmp(slavenode->shard_id, shard_id, CLUSTER_NAMELEN) != 0)
|
||||
assignShardIdToNode(slavenode, shard_id, CLUSTER_TODO_SAVE_CONFIG|CLUSTER_TODO_FSYNC_CONFIG);
|
||||
}
|
||||
} else if (memcmp(node->slaveof->shard_id, shard_id, CLUSTER_NAMELEN) == 0) {
|
||||
assignShardIdToNode(node, shard_id, CLUSTER_TODO_SAVE_CONFIG);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1245,6 +1250,8 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (anetAcceptFailureNeedsRetry(errno))
|
||||
continue;
|
||||
if (errno != EWOULDBLOCK)
|
||||
serverLog(LL_VERBOSE,
|
||||
"Error accepting cluster node: %s", server.neterr);
|
||||
|
||||
@@ -2450,6 +2450,7 @@ static int updatePort(const char **err) {
|
||||
listener->bindaddr = server.bindaddr;
|
||||
listener->bindaddr_count = server.bindaddr_count;
|
||||
listener->port = server.port;
|
||||
clusterUpdateMyselfAnnouncedPorts();
|
||||
listener->ct = connectionByType(CONN_TYPE_SOCKET);
|
||||
if (changeListener(listener) == C_ERR) {
|
||||
*err = "Unable to listen on this port. Check server logs.";
|
||||
@@ -2671,6 +2672,7 @@ static int applyTLSPort(const char **err) {
|
||||
listener->bindaddr_count = server.bindaddr_count;
|
||||
listener->port = server.tls_port;
|
||||
listener->ct = connectionByType(CONN_TYPE_TLS);
|
||||
clusterUpdateMyselfAnnouncedPorts();
|
||||
if (changeListener(listener) == C_ERR) {
|
||||
*err = "Unable to listen on this port. Check server logs.";
|
||||
return 0;
|
||||
|
||||
@@ -348,7 +348,7 @@ robj *dbRandomKey(redisDb *db) {
|
||||
key = dictGetKey(de);
|
||||
keyobj = createStringObject(key,sdslen(key));
|
||||
if (dbFindExpires(db, key)) {
|
||||
if (allvolatile && server.masterhost && --maxtries == 0) {
|
||||
if (allvolatile && (server.masterhost || isPausedActions(PAUSE_ACTION_EXPIRE)) && --maxtries == 0) {
|
||||
/* If the DB is composed only of keys with an expire set,
|
||||
* it could happen that all the keys are already logically
|
||||
* expired in the slave, so the function cannot stop because
|
||||
@@ -702,7 +702,7 @@ void flushallSyncBgDone(uint64_t client_id) {
|
||||
client *c = lookupClientByID(client_id);
|
||||
|
||||
/* Verify that client still exists */
|
||||
if (!c) return;
|
||||
if (!(c && c->flags & CLIENT_BLOCKED)) return;
|
||||
|
||||
/* Update current_client (Called functions might rely on it) */
|
||||
client *old_client = server.current_client;
|
||||
|
||||
+11
-18
@@ -394,8 +394,7 @@ long scanLaterList(robj *ob, unsigned long *cursor, long long endtime) {
|
||||
quicklistNode *node;
|
||||
long iterations = 0;
|
||||
int bookmark_failed = 0;
|
||||
if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST)
|
||||
return 0;
|
||||
serverAssert(ob->type == OBJ_LIST && ob->encoding == OBJ_ENCODING_QUICKLIST);
|
||||
|
||||
if (*cursor == 0) {
|
||||
/* if cursor is 0, we start new iteration */
|
||||
@@ -444,8 +443,7 @@ void scanLaterZsetCallback(void *privdata, const dictEntry *_de) {
|
||||
}
|
||||
|
||||
void scanLaterZset(robj *ob, unsigned long *cursor) {
|
||||
if (ob->type != OBJ_ZSET || ob->encoding != OBJ_ENCODING_SKIPLIST)
|
||||
return;
|
||||
serverAssert(ob->type == OBJ_ZSET && ob->encoding == OBJ_ENCODING_SKIPLIST);
|
||||
zset *zs = (zset*)ob->ptr;
|
||||
dict *d = zs->dict;
|
||||
scanLaterZsetData data = {zs};
|
||||
@@ -461,8 +459,7 @@ void scanCallbackCountScanned(void *privdata, const dictEntry *de) {
|
||||
}
|
||||
|
||||
void scanLaterSet(robj *ob, unsigned long *cursor) {
|
||||
if (ob->type != OBJ_SET || ob->encoding != OBJ_ENCODING_HT)
|
||||
return;
|
||||
serverAssert(ob->type == OBJ_SET && ob->encoding == OBJ_ENCODING_HT);
|
||||
dict *d = ob->ptr;
|
||||
dictDefragFunctions defragfns = {
|
||||
.defragAlloc = activeDefragAlloc,
|
||||
@@ -472,8 +469,7 @@ void scanLaterSet(robj *ob, unsigned long *cursor) {
|
||||
}
|
||||
|
||||
void scanLaterHash(robj *ob, unsigned long *cursor) {
|
||||
if (ob->type != OBJ_HASH || ob->encoding != OBJ_ENCODING_HT)
|
||||
return;
|
||||
serverAssert(ob->type == OBJ_HASH && ob->encoding == OBJ_ENCODING_HT);
|
||||
dict *d = ob->ptr;
|
||||
dictDefragFunctions defragfns = {
|
||||
.defragAlloc = activeDefragAlloc,
|
||||
@@ -568,10 +564,7 @@ int scanLaterStreamListpacks(robj *ob, unsigned long *cursor, long long endtime)
|
||||
static unsigned char last[sizeof(streamID)];
|
||||
raxIterator ri;
|
||||
long iterations = 0;
|
||||
if (ob->type != OBJ_STREAM || ob->encoding != OBJ_ENCODING_STREAM) {
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
serverAssert(ob->type == OBJ_STREAM && ob->encoding == OBJ_ENCODING_STREAM);
|
||||
|
||||
stream *s = ob->ptr;
|
||||
raxStart(&ri,s->rax);
|
||||
@@ -919,22 +912,22 @@ void defragOtherGlobals(void) {
|
||||
int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime, int dbid) {
|
||||
if (de) {
|
||||
robj *ob = dictGetVal(de);
|
||||
if (ob->type == OBJ_LIST) {
|
||||
if (ob->type == OBJ_LIST && ob->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
return scanLaterList(ob, cursor, endtime);
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
} else if (ob->type == OBJ_SET && ob->encoding == OBJ_ENCODING_HT) {
|
||||
scanLaterSet(ob, cursor);
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
} else if (ob->type == OBJ_ZSET && ob->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
scanLaterZset(ob, cursor);
|
||||
} else if (ob->type == OBJ_HASH) {
|
||||
} else if (ob->type == OBJ_HASH && ob->encoding == OBJ_ENCODING_HT) {
|
||||
scanLaterHash(ob, cursor);
|
||||
} else if (ob->type == OBJ_STREAM) {
|
||||
} else if (ob->type == OBJ_STREAM && ob->encoding == OBJ_ENCODING_STREAM) {
|
||||
return scanLaterStreamListpacks(ob, cursor, endtime);
|
||||
} else if (ob->type == OBJ_MODULE) {
|
||||
robj keyobj;
|
||||
initStaticStringObject(keyobj, dictGetKey(de));
|
||||
return moduleLateDefrag(&keyobj, ob, cursor, endtime, dbid);
|
||||
} else {
|
||||
*cursor = 0; /* object type may have changed since we schedule it for later */
|
||||
*cursor = 0; /* object type/encoding may have changed since we schedule it for later */
|
||||
}
|
||||
} else {
|
||||
*cursor = 0; /* object may have been deleted already */
|
||||
|
||||
+15
@@ -260,12 +260,16 @@ int _dictResize(dict *d, unsigned long size, int* malloc_failed)
|
||||
d->ht_table[1] = new_ht_table;
|
||||
d->rehashidx = 0;
|
||||
if (d->type->rehashingStarted) d->type->rehashingStarted(d);
|
||||
if (d->type->bucketChanged)
|
||||
d->type->bucketChanged(d, DICTHT_SIZE(d->ht_size_exp[1]));
|
||||
|
||||
/* Is this the first initialization or is the first hash table empty? If so
|
||||
* it's not really a rehashing, we can just set the first hash table so that
|
||||
* it can accept keys. */
|
||||
if (d->ht_table[0] == NULL || d->ht_used[0] == 0) {
|
||||
if (d->type->rehashingCompleted) d->type->rehashingCompleted(d);
|
||||
if (d->type->bucketChanged)
|
||||
d->type->bucketChanged(d, -(long long)DICTHT_SIZE(d->ht_size_exp[0]));
|
||||
if (d->ht_table[0]) zfree(d->ht_table[0]);
|
||||
d->ht_size_exp[0] = new_ht_size_exp;
|
||||
d->ht_used[0] = new_ht_used;
|
||||
@@ -363,6 +367,8 @@ static int dictCheckRehashingCompleted(dict *d) {
|
||||
if (d->ht_used[0] != 0) return 0;
|
||||
|
||||
if (d->type->rehashingCompleted) d->type->rehashingCompleted(d);
|
||||
if (d->type->bucketChanged)
|
||||
d->type->bucketChanged(d, -(long long)DICTHT_SIZE(d->ht_size_exp[0]));
|
||||
zfree(d->ht_table[0]);
|
||||
/* Copy the new ht onto the old one */
|
||||
d->ht_table[0] = d->ht_table[1];
|
||||
@@ -725,6 +731,10 @@ void dictRelease(dict *d)
|
||||
if (dictIsRehashing(d) && d->type->rehashingCompleted)
|
||||
d->type->rehashingCompleted(d);
|
||||
|
||||
/* Subtract the size of all buckets. */
|
||||
if (d->type->bucketChanged)
|
||||
d->type->bucketChanged(d, -(long long)dictBuckets(d));
|
||||
|
||||
if (d->type->onDictRelease)
|
||||
d->type->onDictRelease(d);
|
||||
|
||||
@@ -1619,6 +1629,11 @@ void dictEmpty(dict *d, void(callback)(dict*)) {
|
||||
* destroying the dict fake completion. */
|
||||
if (dictIsRehashing(d) && d->type->rehashingCompleted)
|
||||
d->type->rehashingCompleted(d);
|
||||
|
||||
/* Subtract the size of all buckets. */
|
||||
if (d->type->bucketChanged)
|
||||
d->type->bucketChanged(d, -(long long)dictBuckets(d));
|
||||
|
||||
_dictClear(d,0,callback);
|
||||
_dictClear(d,1,callback);
|
||||
d->rehashidx = -1;
|
||||
|
||||
@@ -43,6 +43,9 @@ typedef struct dictType {
|
||||
/* Invoked at the end of dict initialization/rehashing of all the entries from old to new ht. Both ht still exists
|
||||
* and are cleaned up after this callback. */
|
||||
void (*rehashingCompleted)(dict *d);
|
||||
/* Invoked when the size of the dictionary changes.
|
||||
* The `delta` parameter can be positive (size increase) or negative (size decrease). */
|
||||
void (*bucketChanged)(dict *d, long long delta);
|
||||
/* Allow a dict to carry extra caller-defined metadata. The
|
||||
* extra memory is initialized to 0 when a dict is allocated. */
|
||||
size_t (*dictMetadataBytes)(dict *d);
|
||||
|
||||
+6
-2
@@ -259,13 +259,17 @@ void scriptingInit(int setup) {
|
||||
void freeLuaScriptsSync(dict *lua_scripts, list *lua_scripts_lru_list, lua_State *lua) {
|
||||
dictRelease(lua_scripts);
|
||||
listRelease(lua_scripts_lru_list);
|
||||
lua_gc(lua, LUA_GCCOLLECT, 0);
|
||||
lua_close(lua);
|
||||
|
||||
#if defined(USE_JEMALLOC)
|
||||
/* When lua is closed, destroy the previously used private tcache. */
|
||||
void *ud = (global_State*)G(lua)->ud;
|
||||
unsigned int lua_tcache = (unsigned int)(uintptr_t)ud;
|
||||
#endif
|
||||
|
||||
lua_gc(lua, LUA_GCCOLLECT, 0);
|
||||
lua_close(lua);
|
||||
|
||||
#if defined(USE_JEMALLOC)
|
||||
je_mallctl("tcache.destroy", NULL, NULL, (void *)&lua_tcache, sizeof(unsigned int));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
#include <lua.h>
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
#if defined(USE_JEMALLOC)
|
||||
#include <lstate.h>
|
||||
#endif
|
||||
|
||||
#define LUA_ENGINE_NAME "LUA"
|
||||
#define REGISTRY_ENGINE_CTX_NAME "__ENGINE_CTX__"
|
||||
@@ -189,9 +192,19 @@ static void luaEngineFreeFunction(void *engine_ctx, void *compiled_function) {
|
||||
|
||||
static void luaEngineFreeCtx(void *engine_ctx) {
|
||||
luaEngineCtx *lua_engine_ctx = engine_ctx;
|
||||
#if defined(USE_JEMALLOC)
|
||||
/* When lua is closed, destroy the previously used private tcache. */
|
||||
void *ud = (global_State*)G(lua_engine_ctx->lua)->ud;
|
||||
unsigned int lua_tcache = (unsigned int)(uintptr_t)ud;
|
||||
#endif
|
||||
|
||||
lua_gc(lua_engine_ctx->lua, LUA_GCCOLLECT, 0);
|
||||
lua_close(lua_engine_ctx->lua);
|
||||
zfree(lua_engine_ctx);
|
||||
|
||||
#if defined(USE_JEMALLOC)
|
||||
je_mallctl("tcache.destroy", NULL, NULL, (void *)&lua_tcache, sizeof(unsigned int));
|
||||
#endif
|
||||
}
|
||||
|
||||
static void luaRegisterFunctionArgsInitialize(registerFunctionArgs *register_f_args,
|
||||
|
||||
+42
-5
@@ -566,6 +566,7 @@ int hllSparseToDense(robj *o) {
|
||||
struct hllhdr *hdr, *oldhdr = (struct hllhdr*)sparse;
|
||||
int idx = 0, runlen, regval;
|
||||
uint8_t *p = (uint8_t*)sparse, *end = p+sdslen(sparse);
|
||||
int valid = 1;
|
||||
|
||||
/* If the representation is already the right one return ASAP. */
|
||||
hdr = (struct hllhdr*) sparse;
|
||||
@@ -585,16 +586,27 @@ int hllSparseToDense(robj *o) {
|
||||
while(p < end) {
|
||||
if (HLL_SPARSE_IS_ZERO(p)) {
|
||||
runlen = HLL_SPARSE_ZERO_LEN(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
idx += runlen;
|
||||
p++;
|
||||
} else if (HLL_SPARSE_IS_XZERO(p)) {
|
||||
runlen = HLL_SPARSE_XZERO_LEN(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
idx += runlen;
|
||||
p += 2;
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) break; /* Overflow. */
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
while(runlen--) {
|
||||
HLL_DENSE_SET_REGISTER(hdr->registers,idx,regval);
|
||||
idx++;
|
||||
@@ -605,7 +617,7 @@ int hllSparseToDense(robj *o) {
|
||||
|
||||
/* If the sparse representation was valid, we expect to find idx
|
||||
* set to HLL_REGISTERS. */
|
||||
if (idx != HLL_REGISTERS) {
|
||||
if (!valid || idx != HLL_REGISTERS) {
|
||||
sdsfree(dense);
|
||||
return C_ERR;
|
||||
}
|
||||
@@ -902,27 +914,40 @@ int hllSparseAdd(robj *o, unsigned char *ele, size_t elesize) {
|
||||
void hllSparseRegHisto(uint8_t *sparse, int sparselen, int *invalid, int* reghisto) {
|
||||
int idx = 0, runlen, regval;
|
||||
uint8_t *end = sparse+sparselen, *p = sparse;
|
||||
int valid = 1;
|
||||
|
||||
while(p < end) {
|
||||
if (HLL_SPARSE_IS_ZERO(p)) {
|
||||
runlen = HLL_SPARSE_ZERO_LEN(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
idx += runlen;
|
||||
reghisto[0] += runlen;
|
||||
p++;
|
||||
} else if (HLL_SPARSE_IS_XZERO(p)) {
|
||||
runlen = HLL_SPARSE_XZERO_LEN(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
idx += runlen;
|
||||
reghisto[0] += runlen;
|
||||
p += 2;
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + idx) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
idx += runlen;
|
||||
reghisto[regval] += runlen;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
if (idx != HLL_REGISTERS && invalid) *invalid = 1;
|
||||
if ((!valid || idx != HLL_REGISTERS) && invalid) *invalid = 1;
|
||||
}
|
||||
|
||||
/* ========================= HyperLogLog Count ==============================
|
||||
@@ -1070,22 +1095,34 @@ int hllMerge(uint8_t *max, robj *hll) {
|
||||
} else {
|
||||
uint8_t *p = hll->ptr, *end = p + sdslen(hll->ptr);
|
||||
long runlen, regval;
|
||||
int valid = 1;
|
||||
|
||||
p += HLL_HDR_SIZE;
|
||||
i = 0;
|
||||
while(p < end) {
|
||||
if (HLL_SPARSE_IS_ZERO(p)) {
|
||||
runlen = HLL_SPARSE_ZERO_LEN(p);
|
||||
if ((runlen + i) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
i += runlen;
|
||||
p++;
|
||||
} else if (HLL_SPARSE_IS_XZERO(p)) {
|
||||
runlen = HLL_SPARSE_XZERO_LEN(p);
|
||||
if ((runlen + i) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
i += runlen;
|
||||
p += 2;
|
||||
} else {
|
||||
runlen = HLL_SPARSE_VAL_LEN(p);
|
||||
regval = HLL_SPARSE_VAL_VALUE(p);
|
||||
if ((runlen + i) > HLL_REGISTERS) break; /* Overflow. */
|
||||
if ((runlen + i) > HLL_REGISTERS) { /* Overflow. */
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
while(runlen--) {
|
||||
if (regval > max[i]) max[i] = regval;
|
||||
i++;
|
||||
@@ -1093,7 +1130,7 @@ int hllMerge(uint8_t *max, robj *hll) {
|
||||
p++;
|
||||
}
|
||||
}
|
||||
if (i != HLL_REGISTERS) return C_ERR;
|
||||
if (!valid || i != HLL_REGISTERS) return C_ERR;
|
||||
}
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
+10
-11
@@ -40,7 +40,6 @@ struct _kvstore {
|
||||
unsigned long long key_count; /* Total number of keys in this kvstore. */
|
||||
unsigned long long bucket_count; /* Total number of buckets in this kvstore across dictionaries. */
|
||||
unsigned long long *dict_size_index; /* Binary indexed tree (BIT) that describes cumulative key frequencies up until given dict-index. */
|
||||
size_t overhead_hashtable_lut; /* The overhead of all dictionaries. */
|
||||
size_t overhead_hashtable_rehashing; /* The overhead of dictionaries rehashing. */
|
||||
};
|
||||
|
||||
@@ -190,8 +189,6 @@ static void kvstoreDictRehashingStarted(dict *d) {
|
||||
|
||||
unsigned long long from, to;
|
||||
dictRehashingInfo(d, &from, &to);
|
||||
kvs->bucket_count += to; /* Started rehashing (Add the new ht size) */
|
||||
kvs->overhead_hashtable_lut += to;
|
||||
kvs->overhead_hashtable_rehashing += from;
|
||||
}
|
||||
|
||||
@@ -209,11 +206,17 @@ static void kvstoreDictRehashingCompleted(dict *d) {
|
||||
|
||||
unsigned long long from, to;
|
||||
dictRehashingInfo(d, &from, &to);
|
||||
kvs->bucket_count -= from; /* Finished rehashing (Remove the old ht size) */
|
||||
kvs->overhead_hashtable_lut -= from;
|
||||
kvs->overhead_hashtable_rehashing -= from;
|
||||
}
|
||||
|
||||
/* Updates the bucket count for the given dictionary in a DB. It adds the new ht size
|
||||
* of the dictionary or removes the old ht size of the dictionary from the total
|
||||
* sum of buckets for a DB. */
|
||||
static void kvstoreDictBucketChanged(dict *d, long long delta) {
|
||||
kvstore *kvs = d->type->userdata;
|
||||
kvs->bucket_count += delta;
|
||||
}
|
||||
|
||||
/* Returns the size of the DB dict metadata in bytes. */
|
||||
static size_t kvstoreDictMetadataSize(dict *d) {
|
||||
UNUSED(d);
|
||||
@@ -246,6 +249,7 @@ kvstore *kvstoreCreate(dictType *type, int num_dicts_bits, int flags) {
|
||||
kvs->dtype.dictMetadataBytes = kvstoreDictMetadataSize;
|
||||
kvs->dtype.rehashingStarted = kvstoreDictRehashingStarted;
|
||||
kvs->dtype.rehashingCompleted = kvstoreDictRehashingCompleted;
|
||||
kvs->dtype.bucketChanged = kvstoreDictBucketChanged;
|
||||
|
||||
kvs->num_dicts_bits = num_dicts_bits;
|
||||
kvs->num_dicts = 1 << kvs->num_dicts_bits;
|
||||
@@ -261,7 +265,6 @@ kvstore *kvstoreCreate(dictType *type, int num_dicts_bits, int flags) {
|
||||
kvs->resize_cursor = 0;
|
||||
kvs->dict_size_index = kvs->num_dicts > 1? zcalloc(sizeof(unsigned long long) * (kvs->num_dicts + 1)) : NULL;
|
||||
kvs->bucket_count = 0;
|
||||
kvs->overhead_hashtable_lut = 0;
|
||||
kvs->overhead_hashtable_rehashing = 0;
|
||||
|
||||
return kvs;
|
||||
@@ -287,7 +290,6 @@ void kvstoreEmpty(kvstore *kvs, void(callback)(dict*)) {
|
||||
kvs->bucket_count = 0;
|
||||
if (kvs->dict_size_index)
|
||||
memset(kvs->dict_size_index, 0, sizeof(unsigned long long) * (kvs->num_dicts + 1));
|
||||
kvs->overhead_hashtable_lut = 0;
|
||||
kvs->overhead_hashtable_rehashing = 0;
|
||||
}
|
||||
|
||||
@@ -339,9 +341,6 @@ size_t kvstoreMemUsage(kvstore *kvs) {
|
||||
/* Values are dict* shared with kvs->dicts */
|
||||
mem += listLength(kvs->rehashing) * sizeof(listNode);
|
||||
|
||||
if (kvs->dict_size_index)
|
||||
mem += sizeof(unsigned long long) * (kvs->num_dicts + 1);
|
||||
|
||||
return mem;
|
||||
}
|
||||
|
||||
@@ -661,7 +660,7 @@ uint64_t kvstoreIncrementallyRehash(kvstore *kvs, uint64_t threshold_us) {
|
||||
}
|
||||
|
||||
size_t kvstoreOverheadHashtableLut(kvstore *kvs) {
|
||||
return kvs->overhead_hashtable_lut * sizeof(dictEntry *);
|
||||
return kvs->bucket_count * sizeof(dictEntry *);
|
||||
}
|
||||
|
||||
size_t kvstoreOverheadHashtableRehashing(kvstore *kvs) {
|
||||
|
||||
+8
-5
@@ -12364,7 +12364,7 @@ int moduleLoad(const char *path, void **module_argv, int module_argc, int is_loa
|
||||
}
|
||||
|
||||
if (post_load_err) {
|
||||
moduleUnload(ctx.module->name, NULL);
|
||||
serverAssert(moduleUnload(ctx.module->name, NULL, 1) == C_OK);
|
||||
moduleFreeContext(&ctx);
|
||||
return C_ERR;
|
||||
}
|
||||
@@ -12380,14 +12380,17 @@ int moduleLoad(const char *path, void **module_argv, int module_argc, int is_loa
|
||||
|
||||
/* Unload the module registered with the specified name. On success
|
||||
* C_OK is returned, otherwise C_ERR is returned and errmsg is set
|
||||
* with an appropriate message. */
|
||||
int moduleUnload(sds name, const char **errmsg) {
|
||||
* with an appropriate message.
|
||||
* Only forcefully unload this module, passing forced_unload != 0,
|
||||
* if it is certain that it has not yet been in use (e.g., immediate
|
||||
* unload on failed load). */
|
||||
int moduleUnload(sds name, const char **errmsg, int forced_unload) {
|
||||
struct RedisModule *module = dictFetchValue(modules,name);
|
||||
|
||||
if (module == NULL) {
|
||||
*errmsg = "no such module with that name";
|
||||
return C_ERR;
|
||||
} else if (listLength(module->types)) {
|
||||
} else if (listLength(module->types) && !forced_unload) {
|
||||
*errmsg = "the module exports one or more module-side data "
|
||||
"types, can't unload";
|
||||
return C_ERR;
|
||||
@@ -13170,7 +13173,7 @@ NULL
|
||||
|
||||
} else if (!strcasecmp(subcmd,"unload") && c->argc == 3) {
|
||||
const char *errmsg = NULL;
|
||||
if (moduleUnload(c->argv[2]->ptr, &errmsg) == C_OK)
|
||||
if (moduleUnload(c->argv[2]->ptr, &errmsg, 0) == C_OK)
|
||||
addReply(c,shared.ok);
|
||||
else {
|
||||
if (errmsg == NULL) errmsg = "operation not possible.";
|
||||
|
||||
@@ -3926,6 +3926,11 @@ int checkClientOutputBufferLimits(client *c) {
|
||||
int soft = 0, hard = 0, class;
|
||||
unsigned long used_mem = getClientOutputBufferMemoryUsage(c);
|
||||
|
||||
/* For unauthenticated clients the output buffer is limited to prevent
|
||||
* them from abusing it by not reading the replies */
|
||||
if (used_mem > 1024 && authRequired(c))
|
||||
return 1;
|
||||
|
||||
class = getClientType(c);
|
||||
/* For the purpose of output buffer limiting, masters are handled
|
||||
* like normal clients. */
|
||||
|
||||
@@ -556,6 +556,12 @@ int redis_check_aof_main(int argc, char **argv) {
|
||||
goto invalid_args;
|
||||
}
|
||||
|
||||
/* Check if filepath is longer than PATH_MAX */
|
||||
if (strlen(filepath) > PATH_MAX) {
|
||||
printf("Error: filepath is too long (exceeds PATH_MAX)\n");
|
||||
goto invalid_args;
|
||||
}
|
||||
|
||||
/* In the glibc implementation dirname may modify their argument. */
|
||||
memcpy(temp_filepath, filepath, strlen(filepath) + 1);
|
||||
dirpath = dirname(temp_filepath);
|
||||
|
||||
+2
-2
@@ -64,7 +64,7 @@ lua_State *createLuaState(void) {
|
||||
size_t sz = sizeof(unsigned int);
|
||||
int err = je_mallctl("tcache.create", (void *)&tcache, &sz, NULL, 0);
|
||||
if (err) {
|
||||
serverLog(LL_WARNING, "Failed creating the lua jemalloc tcache.");
|
||||
serverLog(LL_WARNING, "Failed creating the lua jemalloc tcache (err=%d).", err);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ void luaEnvInit(void) {
|
||||
size_t sz = sizeof(unsigned int);
|
||||
int err = je_mallctl("arenas.create", (void *)&arena, &sz, NULL, 0);
|
||||
if (err) {
|
||||
serverLog(LL_WARNING, "Failed creating the lua jemalloc arena.");
|
||||
serverLog(LL_WARNING, "Failed creating the lua jemalloc arena (err=%d).", err);
|
||||
exit(1);
|
||||
}
|
||||
server.lua_arena = arena;
|
||||
|
||||
+1
-1
@@ -2510,7 +2510,7 @@ void moduleInitModulesSystem(void);
|
||||
void moduleInitModulesSystemLast(void);
|
||||
void modulesCron(void);
|
||||
int moduleLoad(const char *path, void **argv, int argc, int is_loadex);
|
||||
int moduleUnload(sds name, const char **errmsg);
|
||||
int moduleUnload(sds name, const char **errmsg, int forced_unload);
|
||||
void moduleLoadFromQueue(void);
|
||||
int moduleGetCommandKeysViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
|
||||
int moduleGetCommandChannelsViaAPI(struct redisCommand *cmd, robj **argv, int argc, getKeysResult *result);
|
||||
|
||||
@@ -298,6 +298,8 @@ static void connSocketAcceptHandler(aeEventLoop *el, int fd, void *privdata, int
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (anetAcceptFailureNeedsRetry(errno))
|
||||
continue;
|
||||
if (errno != EWOULDBLOCK)
|
||||
serverLog(LL_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
|
||||
+10
-8
@@ -1694,7 +1694,10 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
/* Update the group last_id if needed. */
|
||||
if (group && streamCompareID(&id,&group->last_id) > 0) {
|
||||
if (group->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s,&group->last_id,NULL)) {
|
||||
if (group->entries_read != SCG_INVALID_ENTRIES_READ &&
|
||||
streamCompareID(&group->last_id, &s->first_id) >= 0 &&
|
||||
!streamRangeHasTombstones(s,&group->last_id,NULL))
|
||||
{
|
||||
/* A valid counter and no tombstones between the group's last-delivered-id
|
||||
* and the stream's last-generated-id mean we can increment the read counter
|
||||
* to keep tracking the group's progress. */
|
||||
@@ -2299,14 +2302,13 @@ void xreadCommand(client *c) {
|
||||
"just return an empty result set.");
|
||||
goto cleanup;
|
||||
}
|
||||
if (o) {
|
||||
if (o && ((stream *)o->ptr)->length) {
|
||||
stream *s = o->ptr;
|
||||
ids[id_idx] = s->last_id;
|
||||
if (streamDecrID(&ids[id_idx]) != C_OK) {
|
||||
/* shouldn't happen */
|
||||
addReplyError(c,"the stream last element ID is 0-0");
|
||||
goto cleanup;
|
||||
}
|
||||
/* We need to get the last valid ID.
|
||||
* It is impossible to use s->last_id because
|
||||
* entry with s->last_id may have been removed. */
|
||||
streamLastValidID(s, &ids[id_idx]);
|
||||
streamDecrID(&ids[id_idx]);
|
||||
} else {
|
||||
ids[id_idx].ms = 0;
|
||||
ids[id_idx].seq = 0;
|
||||
|
||||
+7
-1
@@ -3277,7 +3277,7 @@ void genericZrangebyscoreCommand(zrange_result_handler *handler,
|
||||
handler->beginResultEmission(handler, -1);
|
||||
|
||||
/* For invalid offset, return directly. */
|
||||
if (offset > 0 && offset >= (long)zsetLength(zobj)) {
|
||||
if (offset < 0 || (offset > 0 && offset >= (long)zsetLength(zobj))) {
|
||||
handler->finalizeResultEmission(handler, 0);
|
||||
return;
|
||||
}
|
||||
@@ -3551,6 +3551,12 @@ void genericZrangebylexCommand(zrange_result_handler *handler,
|
||||
|
||||
handler->beginResultEmission(handler, -1);
|
||||
|
||||
/* For invalid offset, return directly. */
|
||||
if (offset < 0 || (offset > 0 && offset >= (long)zsetLength(zobj))) {
|
||||
handler->finalizeResultEmission(handler, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (zobj->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
unsigned char *zl = zobj->ptr;
|
||||
unsigned char *eptr, *sptr;
|
||||
|
||||
@@ -755,6 +755,8 @@ static void tlsAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask)
|
||||
while(max--) {
|
||||
cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (anetAcceptFailureNeedsRetry(errno))
|
||||
continue;
|
||||
if (errno != EWOULDBLOCK)
|
||||
serverLog(LL_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
|
||||
@@ -101,6 +101,8 @@ static void connUnixAcceptHandler(aeEventLoop *el, int fd, void *privdata, int m
|
||||
while(max--) {
|
||||
cfd = anetUnixAccept(server.neterr, fd);
|
||||
if (cfd == ANET_ERR) {
|
||||
if (anetAcceptFailureNeedsRetry(errno))
|
||||
continue;
|
||||
if (errno != EWOULDBLOCK)
|
||||
serverLog(LL_WARNING,
|
||||
"Accepting client connection: %s", server.neterr);
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
#define REDIS_VERSION "7.4.2"
|
||||
#define REDIS_VERSION_NUM 0x00070402
|
||||
#define REDIS_VERSION "7.4.5"
|
||||
#define REDIS_VERSION_NUM 0x00070405
|
||||
|
||||
@@ -312,3 +312,12 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
||||
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
int RedisModule_OnUnload(RedisModuleCtx *ctx) {
|
||||
REDISMODULE_NOT_USED(ctx);
|
||||
if (datatype) {
|
||||
RedisModule_Free(datatype);
|
||||
datatype = NULL;
|
||||
}
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,24 @@ start_server {tags {"auth external:skip"} overrides {requirepass foobar}} {
|
||||
assert_match {*unauthenticated bulk length*} $e
|
||||
$rr close
|
||||
}
|
||||
|
||||
test {For unauthenticated clients output buffer is limited} {
|
||||
set rr [redis [srv "host"] [srv "port"] 1 $::tls]
|
||||
$rr SET x 5
|
||||
catch {[$rr read]} e
|
||||
assert_match {*NOAUTH Authentication required*} $e
|
||||
|
||||
# Fill the output buffer in a loop without reading it and make
|
||||
# sure the client disconnected.
|
||||
# Considering the socket eat some of the replies, we are testing
|
||||
# that such client can't consume more than few MB's.
|
||||
catch {
|
||||
for {set j 0} {$j < 1000000} {incr j} {
|
||||
$rr SET x 5
|
||||
}
|
||||
} e
|
||||
assert_match {I/O error reading reply} $e
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"auth_binary_password external:skip"}} {
|
||||
|
||||
@@ -47,4 +47,29 @@ start_cluster 2 2 {tags {external:skip cluster}} {
|
||||
R 0 config set cluster-announce-bus-port 0
|
||||
assert_match "*@$base_bus_port *" [R 0 CLUSTER NODES]
|
||||
}
|
||||
|
||||
test "CONFIG SET port updates cluster-announced port" {
|
||||
set count [expr [llength $::servers] + 1]
|
||||
# Get the original port and change to new_port
|
||||
if {$::tls} {
|
||||
set orig_port [lindex [R 0 config get tls-port] 1]
|
||||
} else {
|
||||
set orig_port [lindex [R 0 config get port] 1]
|
||||
}
|
||||
assert {$orig_port != ""}
|
||||
set new_port [find_available_port $orig_port $count]
|
||||
|
||||
if {$::tls} {
|
||||
R 0 config set tls-port $new_port
|
||||
} else {
|
||||
R 0 config set port $new_port
|
||||
}
|
||||
|
||||
# Verify that the new port appears in the output of cluster slots
|
||||
wait_for_condition 50 100 {
|
||||
[string match "*$new_port*" [R 0 cluster slots]]
|
||||
} else {
|
||||
fail "Cluster announced port was not updated in cluster slots"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,61 @@ start_server {tags {"hll"}} {
|
||||
set e
|
||||
} {*WRONGTYPE*}
|
||||
|
||||
test {Corrupted sparse HyperLogLogs doesn't cause overflow and out-of-bounds with XZERO opcode} {
|
||||
r del hll
|
||||
|
||||
# Create a sparse-encoded HyperLogLog header
|
||||
set header "HYLL"
|
||||
set payload [binary format c12 {1 0 0 0 0 0 0 0 0 0 0 0}]
|
||||
set pl [binary format a4a12 $header $payload]
|
||||
|
||||
# Create an XZERO opcode with the maximum run length of 16384(2^14)
|
||||
set runlen [expr 16384 - 1]
|
||||
set chunk [binary format cc [expr {0b01000000 | ($runlen >> 8)}] [expr {$runlen & 0xff}]]
|
||||
# Fill the HLL with more than 131072(2^17) XZERO opcodes to make the total
|
||||
# run length exceed 4GB, will cause an integer overflow.
|
||||
set repeat [expr 131072 + 1000]
|
||||
for {set i 0} {$i < $repeat} {incr i} {
|
||||
append pl $chunk
|
||||
}
|
||||
|
||||
# Create a VAL opcode with a value that will cause out-of-bounds.
|
||||
append pl [binary format c 0b11111111]
|
||||
r set hll $pl
|
||||
|
||||
# This should not overflow and out-of-bounds.
|
||||
assert_error {*INVALIDOBJ*} {r pfcount hll hll}
|
||||
assert_error {*INVALIDOBJ*} {r pfdebug getreg hll}
|
||||
r ping
|
||||
}
|
||||
|
||||
test {Corrupted sparse HyperLogLogs doesn't cause overflow and out-of-bounds with ZERO opcode} {
|
||||
r del hll
|
||||
|
||||
# Create a sparse-encoded HyperLogLog header
|
||||
set header "HYLL"
|
||||
set payload [binary format c12 {1 0 0 0 0 0 0 0 0 0 0 0}]
|
||||
set pl [binary format a4a12 $header $payload]
|
||||
|
||||
# # Create an ZERO opcode with the maximum run length of 64(2^6)
|
||||
set chunk [binary format c [expr {0b00000000 | 0x3f}]]
|
||||
# Fill the HLL with more than 33554432(2^17) ZERO opcodes to make the total
|
||||
# run length exceed 4GB, will cause an integer overflow.
|
||||
set repeat [expr 33554432 + 1000]
|
||||
for {set i 0} {$i < $repeat} {incr i} {
|
||||
append pl $chunk
|
||||
}
|
||||
|
||||
# Create a VAL opcode with a value that will cause out-of-bounds.
|
||||
append pl [binary format c 0b11111111]
|
||||
r set hll $pl
|
||||
|
||||
# This should not overflow and out-of-bounds.
|
||||
assert_error {*INVALIDOBJ*} {r pfcount hll hll}
|
||||
assert_error {*INVALIDOBJ*} {r pfdebug getreg hll}
|
||||
r ping
|
||||
}
|
||||
|
||||
test {Corrupted dense HyperLogLogs are detected: Wrong length} {
|
||||
r del hll
|
||||
r pfadd hll a b c
|
||||
|
||||
+39
-2
@@ -1,3 +1,16 @@
|
||||
#
|
||||
# Copyright (c) 2009-Present, Redis Ltd.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Copyright (c) 2024-present, Valkey contributors.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Licensed under your choice of the Redis Source Available License 2.0
|
||||
# (RSALv2) or the Server Side Public License v1 (SSPLv1).
|
||||
#
|
||||
# Portions of this file are available under BSD3 terms; see REDISCONTRIBUTIONS for more information.
|
||||
#
|
||||
|
||||
proc cmdstat {cmd} {
|
||||
return [cmdrstat $cmd r]
|
||||
}
|
||||
@@ -386,10 +399,10 @@ start_server {tags {"info" "external:skip"}} {
|
||||
r config set client-output-buffer-limit "normal 10 0 0"
|
||||
r set key [string repeat a 100000] ;# to trigger output buffer limit check this needs to be big
|
||||
catch {r get key}
|
||||
r config set client-output-buffer-limit $org_outbuf_limit
|
||||
set info [r info stats]
|
||||
assert_equal [getInfoProperty $info client_output_buffer_limit_disconnections] {1}
|
||||
r config set client-output-buffer-limit $org_outbuf_limit
|
||||
} {OK} {logreqres:skip} ;# same as obuf-limits.tcl, skip logreqres
|
||||
} {} {logreqres:skip} ;# same as obuf-limits.tcl, skip logreqres
|
||||
|
||||
test {clients: pubsub clients} {
|
||||
set info [r info clients]
|
||||
@@ -522,3 +535,27 @@ start_server {tags {"info" "external:skip"}} {
|
||||
assert_equal [dict get $mem_stats db.dict.rehashing.count] {1}
|
||||
}
|
||||
}
|
||||
|
||||
start_cluster 1 0 {tags {external:skip cluster}} {
|
||||
test "Verify that LUT overhead is properly updated when dicts are emptied or reused (issue #13973)" {
|
||||
R 0 set k v ;# Make dbs overhead displayed
|
||||
set info_mem [r memory stats]
|
||||
set overhead_main [dict get $info_mem db.0 overhead.hashtable.main]
|
||||
set overhead_expires [dict get $info_mem db.0 overhead.hashtable.expires]
|
||||
assert_range $overhead_main 1 5000
|
||||
assert_range $overhead_expires 1 1000
|
||||
|
||||
# In cluster mode, we use KVSTORE_FREE_EMPTY_DICTS to ensure that dicts
|
||||
# are freed when they are emptied. This test verifies that after a dict
|
||||
# is cleared, the lut overhead is properly updated, preventing it from
|
||||
# growing indefinitely.
|
||||
for {set j 1} {$j <= 500} {incr j} {
|
||||
R 0 set k v
|
||||
R 0 del k
|
||||
}
|
||||
R 0 set k v ;# Make dbs overhead displayed
|
||||
set info_mem [r memory stats]
|
||||
assert_equal [dict get $info_mem db.0 overhead.hashtable.main] $overhead_main
|
||||
assert_equal [dict get $info_mem db.0 overhead.hashtable.expires] $overhead_expires
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,4 +174,18 @@ start_server {tags {"lazyfree"}} {
|
||||
assert_equal [s lazyfreed_objects] 2
|
||||
$rd close
|
||||
}
|
||||
|
||||
test "Unblocks client blocked on lazyfree via REPLICAOF command" {
|
||||
set rd [redis_deferring_client]
|
||||
|
||||
populate 50000 ;# Just to make flushdb async slower
|
||||
$rd flushdb
|
||||
wait_for_blocked_client
|
||||
# Test that slaveof command unblocks clients without assertion failure
|
||||
r slaveof 127.0.0.1 0
|
||||
assert_equal [$rd read] {OK}
|
||||
$rd close
|
||||
r ping
|
||||
r slaveof no one
|
||||
} {OK} {external:skip}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
set testmodule [file normalize tests/modules/datatype.so]
|
||||
|
||||
start_server {tags {"modules"}} {
|
||||
test {DataType: test loadex with invalid config} {
|
||||
catch { r module loadex $testmodule CONFIG invalid_config 1 } e
|
||||
assert_match {*ERR Error loading the extension*} $e
|
||||
}
|
||||
|
||||
r module load $testmodule
|
||||
|
||||
test {DataType: Test module is sane, GET/SET work.} {
|
||||
|
||||
@@ -359,6 +359,26 @@ start_server {tags {"pause network"}} {
|
||||
} {bar2}
|
||||
}
|
||||
|
||||
test "Test the randomkey command will not cause the server to get into an infinite loop during the client pause write" {
|
||||
r flushall
|
||||
|
||||
r multi
|
||||
r set key value px 3
|
||||
r client pause 10000 write
|
||||
r exec
|
||||
|
||||
after 5
|
||||
|
||||
wait_for_condition 50 100 {
|
||||
[r randomkey] == "key"
|
||||
} else {
|
||||
fail "execute randomkey failed, caused by the infinite loop"
|
||||
}
|
||||
|
||||
r client unpause
|
||||
assert_equal [r randomkey] {}
|
||||
}
|
||||
|
||||
# Make sure we unpause at the end
|
||||
r client unpause
|
||||
}
|
||||
|
||||
@@ -1884,6 +1884,35 @@ start_server {tags {"scripting needs:debug"}} {
|
||||
}
|
||||
|
||||
start_server {tags {"scripting"}} {
|
||||
test "Test script flush will not leak memory - script:$is_eval" {
|
||||
r flushall
|
||||
r script flush
|
||||
r function flush
|
||||
|
||||
# This is a best-effort test to check we don't leak some resources on
|
||||
# script flush and function flush commands. For lua vm, we create a
|
||||
# jemalloc thread cache. On each script flush command, thread cache is
|
||||
# destroyed and we create a new one. In this test, running script flush
|
||||
# many times to verify there is no increase in the memory usage while
|
||||
# re-creating some of the resources for lua vm.
|
||||
set used_memory [s used_memory]
|
||||
set allocator_allocated [s allocator_allocated]
|
||||
|
||||
r multi
|
||||
for {set j 1} {$j <= 500} {incr j} {
|
||||
if {$is_eval} {
|
||||
r SCRIPT FLUSH
|
||||
} else {
|
||||
r FUNCTION FLUSH
|
||||
}
|
||||
}
|
||||
r exec
|
||||
|
||||
# Verify used memory is not (much) higher.
|
||||
assert_lessthan [s used_memory] [expr $used_memory*1.5]
|
||||
assert_lessthan [s allocator_allocated] [expr $allocator_allocated*1.5]
|
||||
}
|
||||
|
||||
test "Verify Lua performs GC correctly after script loading" {
|
||||
set dummy_script "--[string repeat x 10]\nreturn "
|
||||
set n 50000
|
||||
|
||||
@@ -1300,6 +1300,19 @@ start_server {
|
||||
assert_equal [dict get $group entries-read] 1
|
||||
assert_equal [dict get $group lag] 1
|
||||
|
||||
# When all the entries are read, the lag is always 0.
|
||||
r XREADGROUP GROUP mygroup alice STREAMS x >
|
||||
set reply [r XINFO STREAM x FULL]
|
||||
set group [lindex [dict get $reply groups] 0]
|
||||
assert_equal [dict get $group entries-read] 5
|
||||
assert_equal [dict get $group lag] 0
|
||||
|
||||
r XADD x 6-0 data f
|
||||
set reply [r XINFO STREAM x FULL]
|
||||
set group [lindex [dict get $reply groups] 0]
|
||||
assert_equal [dict get $group entries-read] 5
|
||||
assert_equal [dict get $group lag] 1
|
||||
|
||||
# When all the entries were deleted, the lag is always 0.
|
||||
r XTRIM x MAXLEN 0
|
||||
set reply [r XINFO STREAM x FULL]
|
||||
|
||||
@@ -435,6 +435,17 @@ start_server {
|
||||
|
||||
# verify nil is still received when reading last entry
|
||||
assert_equal [r XREAD STREAMS lestream +] {}
|
||||
|
||||
# case when stream created empty
|
||||
|
||||
# make sure the stream is not initialized
|
||||
r DEL lestream
|
||||
|
||||
# create empty stream with XGROUP CREATE
|
||||
r XGROUP CREATE lestream legroup $ MKSTREAM
|
||||
|
||||
# verify nil is received when reading last entry
|
||||
assert_equal [r XREAD STREAMS lestream +] {}
|
||||
}
|
||||
|
||||
test {XREAD last element blocking from empty stream} {
|
||||
@@ -510,6 +521,22 @@ start_server {
|
||||
assert_equal $res {{lestream {{3-0 {k3 v3}}}}}
|
||||
}
|
||||
|
||||
test "XREAD: read last element after XDEL (issue #13628)" {
|
||||
# Should return actual last element after XDEL of current last element
|
||||
|
||||
# Add 2 entries to a stream and delete last one
|
||||
r DEL stream
|
||||
r XADD stream 1-0 f 1
|
||||
r XADD stream 2-0 f 2
|
||||
r XDEL stream 2-0
|
||||
|
||||
# Read last entry
|
||||
set res [r XREAD STREAMS stream +]
|
||||
|
||||
# Verify the last entry was read
|
||||
assert_equal $res {{stream {{1-0 {f 1}}}}}
|
||||
}
|
||||
|
||||
test "XREAD: XADD + DEL should not awake client" {
|
||||
set rd [redis_deferring_client]
|
||||
r del s1
|
||||
|
||||
@@ -583,20 +583,24 @@ start_server {tags {"zset"}} {
|
||||
assert_equal {d e f} [r zrangebyscore zset 0 10 LIMIT 2 3]
|
||||
assert_equal {d e f} [r zrangebyscore zset 0 10 LIMIT 2 10]
|
||||
assert_equal {} [r zrangebyscore zset 0 10 LIMIT 20 10]
|
||||
assert_equal {} [r zrangebyscore zset 0 10 LIMIT -1 2]
|
||||
assert_equal {f e} [r zrevrangebyscore zset 10 0 LIMIT 0 2]
|
||||
assert_equal {d c b} [r zrevrangebyscore zset 10 0 LIMIT 2 3]
|
||||
assert_equal {d c b} [r zrevrangebyscore zset 10 0 LIMIT 2 10]
|
||||
assert_equal {} [r zrevrangebyscore zset 10 0 LIMIT 20 10]
|
||||
assert_equal {} [r zrevrangebyscore zset 10 0 LIMIT -1 2]
|
||||
# zrangebyscore uses different logic when offset > ZSKIPLIST_MAX_SEARCH
|
||||
create_long_zset zset 30
|
||||
assert_equal {i12 i13 i14} [r zrangebyscore zset 0 20 LIMIT 12 3]
|
||||
assert_equal {i14 i15} [r zrangebyscore zset 0 20 LIMIT 14 2]
|
||||
assert_equal {i19 i20 i21} [r zrangebyscore zset 0 30 LIMIT 19 3]
|
||||
assert_equal {i29} [r zrangebyscore zset 10 30 LIMIT 19 2]
|
||||
assert_equal {i29} [r zrangebyscore zset 10 30 LIMIT 19 2]
|
||||
assert_equal {} [r zrangebyscore zset 0 20 LIMIT -1 3]
|
||||
assert_equal {i17 i16 i15} [r zrevrangebyscore zset 30 10 LIMIT 12 3]
|
||||
assert_equal {i6 i5} [r zrevrangebyscore zset 20 0 LIMIT 14 2]
|
||||
assert_equal {i2 i1 i0} [r zrevrangebyscore zset 20 0 LIMIT 18 5]
|
||||
assert_equal {i0} [r zrevrangebyscore zset 20 0 LIMIT 20 5]
|
||||
assert_equal {} [r zrevrangebyscore zset 30 10 LIMIT -1 3]
|
||||
}
|
||||
|
||||
test "ZRANGEBYSCORE with LIMIT and WITHSCORES - $encoding" {
|
||||
@@ -680,9 +684,11 @@ start_server {tags {"zset"}} {
|
||||
assert_equal {bar} [r zrangebylex zset \[bar \[down LIMIT 0 1]
|
||||
assert_equal {cool} [r zrangebylex zset \[bar \[down LIMIT 1 1]
|
||||
assert_equal {bar cool down} [r zrangebylex zset \[bar \[down LIMIT 0 100]
|
||||
assert_equal {} [r zrangebylex zset - \[cool LIMIT -1 2]
|
||||
assert_equal {omega hill great foo elephant} [r zrevrangebylex zset + \[d LIMIT 0 5]
|
||||
assert_equal {omega hill great foo} [r zrevrangebylex zset + \[d LIMIT 0 4]
|
||||
assert_equal {great foo elephant} [r zrevrangebylex zset + \[d LIMIT 2 3]
|
||||
assert_equal {} [r zrevrangebylex zset + \[d LIMIT -1 5]
|
||||
# zrangebylex uses different logic when offset > ZSKIPLIST_MAX_SEARCH
|
||||
create_long_lex_zset
|
||||
assert_equal {max null} [r zrangebylex zset - \[tree LIMIT 12 2]
|
||||
@@ -692,12 +698,14 @@ start_server {tags {"zset"}} {
|
||||
assert_equal {max} [r zrangebylex zset \[max \[null LIMIT 0 1]
|
||||
assert_equal {null} [r zrangebylex zset \[max \[null LIMIT 1 1]
|
||||
assert_equal {max null omega point} [r zrangebylex zset \[max \[point LIMIT 0 100]
|
||||
assert_equal {} [r zrangebylex zset - \[tree LIMIT -1 2]
|
||||
assert_equal {tree sea result query point} [r zrevrangebylex zset + \[o LIMIT 0 5]
|
||||
assert_equal {tree sea result query} [r zrevrangebylex zset + \[o LIMIT 0 4]
|
||||
assert_equal {omega null max lip} [r zrevrangebylex zset + \[l LIMIT 5 4]
|
||||
assert_equal {elephant down} [r zrevrangebylex zset + \[a LIMIT 15 2]
|
||||
assert_equal {bar alpha} [r zrevrangebylex zset + - LIMIT 18 6]
|
||||
assert_equal {hill great foo} [r zrevrangebylex zset + \[c LIMIT 12 3]
|
||||
assert_equal {} [r zrevrangebylex zset + \[o LIMIT -1 5]
|
||||
}
|
||||
|
||||
test "ZRANGEBYLEX with invalid lex range specifiers - $encoding" {
|
||||
|
||||
Reference in New Issue
Block a user