Unverified Commit 5fe3e74a authored by YaacovHazan's avatar YaacovHazan Committed by GitHub
Browse files

Redis 8.0 M01 (#13538)

### New Features in binary distributions

- 7 new data structures: JSON, Time series, Bloom filter, Cuckoo filter,
Count-min sketch, Top-k, t-digest
- Redis scalable query engine (including vector search)

### Potentially breaking changes

- #12272 `GETRANGE` returns an empty bulk when the negative end index is
out of range
- #12395 Optimize `SCAN` command when matching data type

### Bug fixes

- #13510 Fix `RM_RdbLoad` to enable AOF after RDB loading is completed
- #13489 `ACL CAT` - return module commands
- #13476 Fix a race condition in the `cache_memory` of `functionsLibCtx`
- #13473 Fix incorrect lag due to trimming stream via `XTRIM` command
- #13338 Fix incorrect lag field in `XINFO` when tombstone is after the
`last_id` of the consume group
- #13470 On `HDEL` of last field - update the global hash field
expiration data structure
- #13465 Cluster: Pass extensions to node if extension processing is
handled by it
- #13443 Cluster: Ensure validity of myself when loading cluster config
- #13422 Cluster: Fix `CLUSTER SHARDS` command returns empty array

### Modules API

- #13509 New API calls: `RM_DefragAllocRaw`, `RM_DefragFreeRaw`, and
`RM_RegisterDefragCallbacks` - defrag API to allocate and free raw
memory

### Performance and resource utilization improvements

- #13503 Avoid overhead of comparison function pointer calls in listpack
`lpFind`
- #13505 Optimize `STRING` datatype write commands
- #13499 Optimize `SMEMBERS` command
- #13494 Optimize `GEO*` commands reply
- #13490 Optimize `HELLO` command
- #13488 Optimize client query buffer
- #12395 Optimize `SCAN` command when matching data type
- #13529 Optimize `LREM`, `LPOS`, `LINSERT`, and `LINDEX` commands
- #13516 Optimize `LRANGE` and other commands that perform several
writes to client buffers per call
- #13431 Avoid `used_memory` contention when updating from multiple
threads

### Other general improvements

- #13495 Reply `-LOADING` on replica while flushing the db

### CLI tools

- #13411 redis-cli: Fix wrong `dbnum` showed after the client
reconnected

### Notes

- No backward compatibility for replication or persistence.
- Additional distributions, upgrade paths, features, and improvements
will be introduced in upcoming pre-releases.
- With the GA release of 8.0 we will deprecate Redis Stack.
parents ea3e8b79 4955375e
Hello! This file is just a placeholder, since this is the "unstable" branch
of Redis, the place where all the development happens.
Redis Community Edition 8.0 release notes
=========================================
There is no release notes for this branch, it gets forked into another branch
every time there is a partial feature freeze in order to eventually create
a new stable release.
===========================================================
8.0-M01 (v7.9.224) Released Thu 12 Sep 2024 10:00:00 IST
===========================================================
Usually "unstable" is stable enough for you to use it in development environments
however you should never use it in production environments. It is possible
to download the latest stable release here:
This is the first Milestone of Redis Community Edition 8.0.
https://download.redis.io/redis-stable.tar.gz
Milestones are non-feature-complete pre-releases. Pre-releases are not suitable for production use.
Once we reach feature-completeness we will release RC1.
More information is available at https://redis.io
### Headlines:
Redis 8.0 introduces new data structures: JSON, time series, and 5 probabilistic data structures (previously available as separate Redis modules) and incorporates Redis scalable query engine (including vector search).
8.0-M01 is available as a Docker image and can be downloaded from [Docker Hub](https://hub.docker.com/_/redis). Additional distributions will be introduced in upcoming pre-releases.
### Supported upgrade paths (by replication or persistence) to 8.0-M01
- From previous Redis versions, without modules
The following upgrade paths (by replication or persistence) to 8.0-M01 are not yet tested and will be introduced in upcoming pre-releases:
- From previous Redis versions with modules (RediSearch, RedisJSON, RedisTimeSeries, RedisBloom)
- From Redis Stack 7.2 or 7.4
### New Features in binary distributions
- 7 new data structures: JSON, Time series, Bloom filter, Cuckoo filter, Count-min sketch, Top-k, t-digest
- Redis scalable query engine (including vector search)
### Potentially breaking changes
- #12272 `GETRANGE` returns an empty bulk when the negative end index is out of range
- #12395 Optimize `SCAN` command when matching data type
### Bug fixes
- #13510 Fix `RM_RdbLoad` to enable AOF after RDB loading is completed
- #13489 `ACL CAT` - return module commands
- #13476 Fix a race condition in the `cache_memory` of `functionsLibCtx`
- #13473 Fix incorrect lag due to trimming stream via `XTRIM` command
- #13338 Fix incorrect lag field in `XINFO` when tombstone is after the `last_id` of the consume group
- #13470 On `HDEL` of last field - update the global hash field expiration data structure
- #13465 Cluster: Pass extensions to node if extension processing is handled by it
- #13443 Cluster: Ensure validity of myself when loading cluster config
- #13422 Cluster: Fix `CLUSTER SHARDS` command returns empty array
### Modules API
- #13509 New API calls: `RM_DefragAllocRaw`, `RM_DefragFreeRaw`, and `RM_RegisterDefragCallbacks` - defrag API to allocate and free raw memory
### Performance and resource utilization improvements
- #13503 Avoid overhead of comparison function pointer calls in listpack `lpFind`
- #13505 Optimize `STRING` datatype write commands
- #13499 Optimize `SMEMBERS` command
- #13494 Optimize `GEO*` commands reply
- #13490 Optimize `HELLO` command
- #13488 Optimize client query buffer
- #12395 Optimize `SCAN` command when matching data type
- #13529 Optimize `LREM`, `LPOS`, `LINSERT`, and `LINDEX` commands
- #13516 Optimize `LRANGE` and other commands that perform several writes to client buffers per call
- #13431 Avoid `used_memory` contention when updating from multiple threads
### Other general improvements
- #13495 Reply `-LOADING` on replica while flushing the db
### CLI tools
- #13411 redis-cli: Fix wrong `dbnum` showed after the client reconnected
### Notes
- No backward compatibility for replication or persistence.
- Additional distributions, upgrade paths, features, and improvements will be introduced in upcoming pre-releases.
- With the GA release of 8.0 we will deprecate Redis Stack.
Happy hacking!
# Top level makefile, the real stuff is at src/Makefile
# Top level makefile, the real stuff is at ./src/Makefile and in ./modules/Makefile
SUBDIRS = src
ifeq ($(BUILD_WITH_MODULES), yes)
SUBDIRS += modules
endif
default: all
.DEFAULT:
cd src && $(MAKE) $@
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $@; done
install:
cd src && $(MAKE) $@
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $@; done
.PHONY: install
......@@ -16,7 +16,6 @@ Another good example is to think of Redis as a more complex version of memcached
If you want to know more, this is a list of selected starting points:
* Introduction to Redis data types. https://redis.io/topics/data-types-intro
* Try Redis directly inside your browser. https://try.redis.io
* The full list of Redis commands. https://redis.io/commands
* There is much more inside the official Redis documentation. https://redis.io/documentation
......
SUBDIRS = redisjson redistimeseries redisbloom redisearch
define submake
for dir in $(SUBDIRS); do $(MAKE) -C $$dir $(1); done
endef
all: prepare_source
$(call submake,$@)
get_source:
$(call submake,$@)
prepare_source: get_source handle-werrors setup_environment
clean:
$(call submake,$@)
distclean: clean_environment
$(call submake,$@)
pristine:
$(call submake,$@)
install:
$(call submake,$@)
setup_environment: install-rust handle-werrors
clean_environment: uninstall-rust
# Keep all of the Rust stuff in one place
install-rust:
ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
@RUST_VERSION=1.80.1; \
case "$$(uname -m)" in \
'x86_64') RUST_INSTALLER="rust-$${RUST_VERSION}-x86_64-unknown-linux-gnu"; RUST_SHA256="85e936d5d36970afb80756fa122edcc99bd72a88155f6bdd514f5d27e778e00a" ;; \
'aarch64') RUST_INSTALLER="rust-$${RUST_VERSION}-aarch64-unknown-linux-gnu"; RUST_SHA256="2e89bad7857711a1c11d017ea28fbfeec54076317763901194f8f5decbac1850" ;; \
*) echo >&2 "Unsupported architecture: '$$(uname -m)'"; exit 1 ;; \
esac; \
wget --quiet -O $${RUST_INSTALLER}.tar.xz https://static.rust-lang.org/dist/$${RUST_INSTALLER}.tar.xz; \
echo "$${RUST_SHA256} $${RUST_INSTALLER}.tar.xz" | sha256sum -c --quiet || { echo "Rust standalone installer checksum failed!"; exit 1; }; \
tar -xf $${RUST_INSTALLER}.tar.xz; \
(cd $${RUST_INSTALLER} && ./install.sh); \
rm -rf $${RUST_INSTALLER}
endif
uninstall-rust:
ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
@if [ -x "/usr/local/lib/rustlib/uninstall.sh" ]; then \
echo "Uninstalling Rust using uninstall.sh script"; \
rm -rf ~/.cargo; \
/usr/local/lib/rustlib/uninstall.sh; \
else \
echo "WARNING: Rust toolchain not found or uninstall script is missing."; \
fi
endif
handle-werrors: get_source
ifeq ($(DISABLE_WERRORS),yes)
@echo "Disabling -Werror for all modules"
@for dir in $(SUBDIRS); do \
echo "Processing $$dir"; \
find $$dir/src -type f \
\( -name "Makefile" \
-o -name "*.mk" \
-o -name "CMakeLists.txt" \) \
-exec sed -i 's/-Werror//g' {} +; \
done
endif
.PHONY: all clean distclean install $(SUBDIRS) setup_environment clean_environment install-rust uninstall-rust handle-werrors
PREFIX ?= /usr/local
INSTALL_DIR ?= $(DESTDIR)$(PREFIX)/lib/redis/modules
INSTALL ?= install
# This logic *partially* follows the current module build system. It is a bit awkward and
# should be changed if/when the modules' build process is refactored.
ARCH_MAP_x86_64 := x64
ARCH_MAP_i386 := x86
ARCH_MAP_i686 := x86
ARCH_MAP_aarch64 := arm64v8
ARCH_MAP_arm64 := arm64v8
OS := $(shell uname -s | tr '[:upper:]' '[:lower:]')
ARCH := $(ARCH_MAP_$(shell uname -m))
ifeq ($(ARCH),)
$(error Unrecognized CPU architecture $(shell uname -m))
endif
FULL_VARIANT := $(OS)-$(ARCH)-release
# Common rules for all modules, based on per-module configuration
all: $(TARGET_MODULE)
$(TARGET_MODULE): get_source
$(MAKE) -C $(SRC_DIR)
get_source: $(SRC_DIR)/.prepared
$(SRC_DIR)/.prepared:
mkdir -p $(SRC_DIR)
git clone --recursive --depth 1 --branch $(MODULE_VERSION) $(MODULE_REPO) $(SRC_DIR)
touch $@
clean:
-$(MAKE) -C $(SRC_DIR) clean
distclean:
-$(MAKE) -C $(SRC_DIR) distclean
pristine:
-rm -rf $(SRC_DIR)
install: $(TARGET_MODULE)
mkdir -p $(INSTALL_DIR)
$(INSTALL) -m 0755 -D $(TARGET_MODULE) $(INSTALL_DIR)
.PHONY: all clean distclean pristine install
SRC_DIR = src
MODULE_VERSION = v2.8.2
MODULE_REPO = https://github.com/redisbloom/redisbloom
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redisbloom.so
include ../common.mk
SRC_DIR = src
MODULE_VERSION = v7.99.0
MODULE_REPO = https://github.com/redisearch/redisearch
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/coord-oss/redisearch.so
include ../common.mk
SRC_DIR = src
MODULE_VERSION = v2.8.4
MODULE_REPO = https://github.com/redisjson/redisjson
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/rejson.so
include ../common.mk
$(SRC_DIR)/.cargo_fetched:
cd $(SRC_DIR) && cargo fetch
get_source: $(SRC_DIR)/.cargo_fetched
SRC_DIR = src
MODULE_VERSION = v1.12.2
MODULE_REPO = https://github.com/redistimeseries/redistimeseries
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redistimeseries.so
include ../common.mk
......@@ -124,7 +124,9 @@ static void cumulativeKeyCountAdd(kvstore *kvs, int didx, long delta) {
dict *d = kvstoreGetDict(kvs, didx);
size_t dsize = dictSize(d);
int non_empty_dicts_delta = dsize == 1? 1 : dsize == 0? -1 : 0;
/* Increment if dsize is 1 and delta is positive (first element inserted, dict becomes non-empty).
* Decrement if dsize is 0 (dict becomes empty). */
int non_empty_dicts_delta = (dsize == 1 && delta > 0) ? 1 : (dsize == 0) ? -1 : 0;
kvs->non_empty_dicts += non_empty_dicts_delta;
/* BIT does not need to be calculated when there's only one dict. */
......@@ -1026,6 +1028,31 @@ int kvstoreTest(int argc, char **argv, int flags) {
kvstoreRelease(kvs);
}
TEST("Verify non-empty dict count is correctly updated") {
kvstore *kvs = kvstoreCreate(&KvstoreDictTestType, 2, KVSTORE_ALLOCATE_DICTS_ON_DEMAND);
for (int idx = 0; idx < 4; idx++) {
for (i = 0; i < 16; i++) {
de = kvstoreDictAddRaw(kvs, idx, stringFromInt(i), NULL);
assert(de != NULL);
/* When the first element is inserted, the number of non-empty dictionaries is increased by 1. */
if (i == 0) assert(kvstoreNumNonEmptyDicts(kvs) == idx + 1);
}
}
/* Step by step, clear all dictionaries and ensure non-empty dict count is updated */
for (int idx = 0; idx < 4; idx++) {
kvs_di = kvstoreGetDictSafeIterator(kvs, idx);
while((de = kvstoreDictIteratorNext(kvs_di)) != NULL) {
key = dictGetKey(de);
assert(kvstoreDictDelete(kvs, idx, key) == DICT_OK);
/* When the dictionary is emptied, the number of non-empty dictionaries is reduced by 1. */
if (kvstoreDictSize(kvs, idx) == 0) assert(kvstoreNumNonEmptyDicts(kvs) == 3 - idx);
}
kvstoreReleaseDictIterator(kvs_di);
}
kvstoreRelease(kvs);
}
kvstoreRelease(kvs1);
kvstoreRelease(kvs2);
return 0;
......
......@@ -26,6 +26,7 @@ static void setProtocolError(const char *errstr, client *c);
static void pauseClientsByClient(mstime_t end, int isPauseClientAll);
int postponeClientRead(client *c);
char *getClientSockname(client *c);
static inline int clientTypeIsSlave(client *c);
int ProcessingEventsWhileBlocked = 0; /* See processEventsWhileBlocked(). */
__thread sds thread_reusable_qb = NULL;
__thread int thread_reusable_qb_used = 0; /* Avoid multiple clients using reusable query
......@@ -394,7 +395,7 @@ void _addReplyToBufferOrList(client *c, const char *s, size_t len) {
* replication link that caused a reply to be generated we'll simply disconnect it.
* Note this is the simplest way to check a command added a response. Replication links are used to write data but
* not for responses, so we should normally never get here on a replica client. */
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
sds cmdname = c->lastcmd ? c->lastcmd->fullname : NULL;
logInvalidUseAndFreeClientAsync(c, "Replica generated a reply to command '%s'",
cmdname ? cmdname : "<unknown>");
......@@ -736,7 +737,7 @@ void *addReplyDeferredLen(client *c) {
* replication link that caused a reply to be generated we'll simply disconnect it.
* Note this is the simplest way to check a command added a response. Replication links are used to write data but
* not for responses, so we should normally never get here on a replica client. */
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
sds cmdname = c->lastcmd ? c->lastcmd->fullname : NULL;
logInvalidUseAndFreeClientAsync(c, "Replica generated a reply to command '%s'",
cmdname ? cmdname : "<unknown>");
......@@ -1251,7 +1252,7 @@ void copyReplicaOutputBuffer(client *dst, client *src) {
/* Return true if the specified client has pending reply buffers to write to
* the socket. */
int clientHasPendingReplies(client *c) {
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
/* Replicas use global shared replication buffer instead of
* private output buffer. */
serverAssert(c->bufpos == 0 && listLength(c->reply) == 0);
......@@ -1654,7 +1655,7 @@ void freeClient(client *c) {
}
/* Log link disconnection with slave */
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (clientTypeIsSlave(c)) {
serverLog(LL_NOTICE,"Connection with replica %s lost.",
replicationGetSlaveName(c));
}
......@@ -1736,7 +1737,7 @@ void freeClient(client *c) {
/* We need to remember the time when we started to have zero
* attached slaves, as after some time we'll free the replication
* backlog. */
if (getClientType(c) == CLIENT_TYPE_SLAVE && listLength(server.slaves) == 0)
if (clientTypeIsSlave(c) && listLength(server.slaves) == 0)
server.repl_no_slaves_since = server.unixtime;
refreshGoodSlavesCount();
/* Fire the replica change modules event. */
......@@ -1949,7 +1950,7 @@ static int _writevToClient(client *c, ssize_t *nwritten) {
* to client. */
int _writeToClient(client *c, ssize_t *nwritten) {
*nwritten = 0;
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
serverAssert(c->bufpos == 0 && listLength(c->reply) == 0);
replBufBlock *o = listNodeValue(c->ref_repl_buf_node);
......@@ -2036,7 +2037,7 @@ int writeToClient(client *c, int handler_installed) {
!(c->flags & CLIENT_SLAVE)) break;
}
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
atomicIncr(server.stat_net_repl_output_bytes, totwritten);
} else {
atomicIncr(server.stat_net_output_bytes, totwritten);
......@@ -2240,7 +2241,7 @@ int processInlineBuffer(client *c) {
/* Newline from slaves can be used to refresh the last ACK time.
* This is useful for a slave to ping back while loading a big
* RDB file. */
if (querylen == 0 && getClientType(c) == CLIENT_TYPE_SLAVE)
if (querylen == 0 && clientTypeIsSlave(c))
c->repl_ack_time = server.unixtime;
/* Masters should never send us inline protocol to run actual
......@@ -3897,7 +3898,7 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
* the caller wishes. The main usage of this function currently is
* enforcing the client output length limits. */
size_t getClientOutputBufferMemoryUsage(client *c) {
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
size_t repl_buf_size = 0;
size_t repl_node_num = 0;
size_t repl_node_size = sizeof(listNode) + sizeof(replBufBlock);
......@@ -3961,6 +3962,12 @@ int getClientType(client *c) {
return CLIENT_TYPE_NORMAL;
}
static inline int clientTypeIsSlave(client *c) {
if (unlikely((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)))
return 1;
return 0;
}
int getClientTypeByName(char *name) {
if (!strcasecmp(name,"normal")) return CLIENT_TYPE_NORMAL;
else if (!strcasecmp(name,"slave")) return CLIENT_TYPE_SLAVE;
......@@ -4050,7 +4057,7 @@ int closeClientOnOutputBufferLimitReached(client *c, int async) {
serverAssert(c->reply_bytes < SIZE_MAX-(1024*64));
/* Note that c->reply_bytes is irrelevant for replica clients
* (they use the global repl buffers). */
if ((c->reply_bytes == 0 && getClientType(c) != CLIENT_TYPE_SLAVE) ||
if ((c->reply_bytes == 0 && !clientTypeIsSlave(c)) ||
c->flags & CLIENT_CLOSE_ASAP) return 0;
if (checkClientOutputBufferLimits(c)) {
sds client = catClientInfoString(sdsempty(),c);
......@@ -4480,7 +4487,7 @@ int handleClientsWithPendingWritesUsingThreads(void) {
* buffer, to guarantee data accessing thread safe, we must put all
* replicas client into io_threads_list[0] i.e. main thread handles
* sending the output buffer of all replicas. */
if (getClientType(c) == CLIENT_TYPE_SLAVE) {
if (unlikely(clientTypeIsSlave(c))) {
listAddNodeTail(io_threads_list[0],c);
continue;
}
......
......@@ -2748,7 +2748,7 @@ robj *listTypeGet(listTypeEntry *entry);
unsigned char *listTypeGetValue(listTypeEntry *entry, size_t *vlen, long long *lval);
void listTypeInsert(listTypeEntry *entry, robj *value, int where);
void listTypeReplace(listTypeEntry *entry, robj *value);
int listTypeEqual(listTypeEntry *entry, robj *o);
int listTypeEqual(listTypeEntry *entry, robj *o, size_t object_len);
void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
robj *listTypeDup(robj *o);
void listTypeDelRange(robj *o, long start, long stop);
......
......@@ -2428,7 +2428,11 @@ void genericHgetallCommand(client *c, int flags) {
/* We return a map if the user requested keys and values, like in the
* HGETALL case. Otherwise to use a flat array makes more sense. */
length = hashTypeLength(o, 1 /*subtractExpiredFields*/);
if ((length = hashTypeLength(o, 1 /*subtractExpiredFields*/)) == 0) {
addReply(c, emptyResp);
return;
}
if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) {
addReplyMapLen(c, length);
} else {
......@@ -2437,11 +2441,7 @@ void genericHgetallCommand(client *c, int flags) {
hi = hashTypeInitIterator(o);
/* Skip expired fields if the hash has an expire time set at global HFE DS. We could
* set it to constant 1, but then it will make another lookup for each field expiration */
int skipExpiredFields = (EB_EXPIRE_TIME_INVALID == hashTypeGetMinExpire(o, 0)) ? 0 : 1;
while (hashTypeNext(hi, skipExpiredFields) != C_ERR) {
while (hashTypeNext(hi, 1 /*skipExpiredFields*/) != C_ERR) {
if (flags & OBJ_HASH_KEY) {
addHashIteratorCursorToReply(c, hi, OBJ_HASH_KEY);
count++;
......
......@@ -383,12 +383,12 @@ int listTypeReplaceAtIndex(robj *o, int index, robj *value) {
}
/* Compare the given object with the entry at the current position. */
int listTypeEqual(listTypeEntry *entry, robj *o) {
int listTypeEqual(listTypeEntry *entry, robj *o, size_t object_len) {
serverAssertWithInfo(NULL,o,sdsEncodedObject(o));
if (entry->li->encoding == OBJ_ENCODING_QUICKLIST) {
return quicklistCompare(&entry->entry,o->ptr,sdslen(o->ptr));
return quicklistCompare(&entry->entry,o->ptr,object_len);
} else if (entry->li->encoding == OBJ_ENCODING_LISTPACK) {
return lpCompare(entry->lpe,o->ptr,sdslen(o->ptr));
return lpCompare(entry->lpe,o->ptr,object_len);
} else {
serverPanic("Unknown list encoding");
}
......@@ -538,8 +538,9 @@ void linsertCommand(client *c) {
/* Seek pivot from head to tail */
iter = listTypeInitIterator(subject,0,LIST_TAIL);
const size_t object_len = sdslen(c->argv[3]->ptr);
while (listTypeNext(iter,&entry)) {
if (listTypeEqual(&entry,c->argv[3])) {
if (listTypeEqual(&entry,c->argv[3],object_len)) {
listTypeInsert(&entry,c->argv[4],where);
inserted = 1;
break;
......@@ -999,8 +1000,9 @@ void lposCommand(client *c) {
listTypeEntry entry;
long llen = listTypeLength(o);
long index = 0, matches = 0, matchindex = -1, arraylen = 0;
const size_t ele_len = sdslen(ele->ptr);
while (listTypeNext(li,&entry) && (maxlen == 0 || index < maxlen)) {
if (listTypeEqual(&entry,ele)) {
if (listTypeEqual(&entry,ele,ele_len)) {
matches++;
matchindex = (direction == LIST_TAIL) ? index : llen - index - 1;
if (matches >= rank) {
......@@ -1052,8 +1054,9 @@ void lremCommand(client *c) {
}
listTypeEntry entry;
const size_t object_len = sdslen(c->argv[3]->ptr);
while (listTypeNext(li,&entry)) {
if (listTypeEqual(&entry,obj)) {
if (listTypeEqual(&entry,obj,object_len)) {
listTypeDelete(li, &entry);
server.dirty++;
removed++;
......
#define REDIS_VERSION "255.255.255"
#define REDIS_VERSION_NUM 0x00ffffff
#define REDIS_VERSION "7.9.224"
#define REDIS_VERSION_NUM 0x000709e0
......@@ -32,6 +32,13 @@ proc get_one_of_my_replica {id} {
}
set replica_port [lindex [lindex [lindex [R $id role] 2] 0] 1]
set replica_id_num [get_instance_id_by_port redis $replica_port]
# To avoid -LOADING reply, wait until replica syncs with master.
wait_for_condition 1000 50 {
[RI $replica_id_num master_link_status] eq {up}
} else {
fail "Replica did not sync in time."
}
return $replica_id_num
}
......@@ -105,8 +112,15 @@ proc test_slave_load_expired_keys {aof} {
# start the replica again (loading an RDB or AOF file)
restart_instance redis $replica_id
# Replica may start a full sync after restart, trying in a loop to avoid
# -LOADING reply in that case.
wait_for_condition 1000 50 {
[catch {set replica_dbsize_3 [R $replica_id dbsize]} e] == 0
} else {
fail "Replica is not up."
}
# make sure the keys are still there
set replica_dbsize_3 [R $replica_id dbsize]
assert {$replica_dbsize_3 > $replica_dbsize_0}
# restore settings
......
......@@ -2069,6 +2069,14 @@ start_server {tags {"scripting"}} {
} 1 x
r replicaof [srv -1 host] [srv -1 port]
# To avoid -LOADING reply, wait until replica syncs with master.
wait_for_condition 50 100 {
[s master_link_status] eq {up}
} else {
fail "Replica did not sync in time."
}
assert_error {EXECABORT Transaction discarded because of: READONLY *} {$rr exec}
assert_error {READONLY You can't write against a read only replica. script: *} {$rr2 exec}
$rr close
......
......@@ -560,10 +560,10 @@ start_server {tags {"external:skip needs:debug"}} {
# Test with small hash
r debug set-active-expire 0
r del myhash
r hset myhash1 f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6
r hpexpire myhash1 1 NX FIELDS 3 f2 f4 f6
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6
r hpexpire myhash 1 NX FIELDS 3 f2 f4 f6
after 10
assert_equal [lsort [r hgetall myhash1]] "f1 f3 f5 v1 v3 v5"
assert_equal [lsort [r hgetall myhash]] "f1 f3 f5 v1 v3 v5"
# Test with large hash
r del myhash
......@@ -573,6 +573,13 @@ start_server {tags {"external:skip needs:debug"}} {
}
after 10
assert_equal [lsort [r hgetall myhash]] [lsort "f1 f2 f3 v1 v2 v3"]
# hash that all fields are expired return empty result
r del myhash
r hset myhash f1 v1 f2 v2 f3 v3 f4 v4 f5 v5 f6 v6
r hpexpire myhash 1 FIELDS 6 f1 f2 f3 f4 f5 f6
after 10
assert_equal [r hgetall myhash] ""
r debug set-active-expire 1
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment