mirror of
https://github.com/redis/redis.git
synced 2026-05-28 04:02:46 -04:00
Merge branch 'unstable' into readme-build-from-source
This commit is contained in:
commit
50886cc46f
36 changed files with 434 additions and 284 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
|
@ -2,6 +2,10 @@ name: CI
|
|||
|
||||
on: [push, pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
test-ubuntu-latest:
|
||||
|
|
|
|||
4
.github/workflows/codecov.yml
vendored
4
.github/workflows/codecov.yml
vendored
|
|
@ -4,6 +4,10 @@ name: "Codecov"
|
|||
# where each PR needs to be compared against the coverage of the head commit
|
||||
on: [push, pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
|
|
|||
4
.github/workflows/codeql-analysis.yml
vendored
4
.github/workflows/codeql-analysis.yml
vendored
|
|
@ -6,6 +6,10 @@ on:
|
|||
# run weekly new vulnerability was added to the database
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
|
|
|
|||
5
.github/workflows/coverity.yml
vendored
5
.github/workflows/coverity.yml
vendored
|
|
@ -6,6 +6,11 @@ on:
|
|||
- cron: '0 0 * * *'
|
||||
# Support manual execution
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
coverity:
|
||||
if: github.repository == 'redis/redis'
|
||||
|
|
|
|||
12
.github/workflows/daily.yml
vendored
12
.github/workflows/daily.yml
vendored
|
|
@ -1105,7 +1105,7 @@ jobs:
|
|||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: test
|
||||
uses: cross-platform-actions/action@v0.30.0
|
||||
uses: cross-platform-actions/action@v1.0.0
|
||||
with:
|
||||
operating_system: freebsd
|
||||
environment_variables: MAKE
|
||||
|
|
@ -1221,10 +1221,14 @@ jobs:
|
|||
- name: cluster tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'cluster')
|
||||
run: ./runtest-cluster --log-req-res --dont-clean --force-resp3 ${{github.event.inputs.cluster_test_args}}
|
||||
- name: Install Python dependencies
|
||||
uses: py-actions/py-dependency-install@30aa0023464ed4b5b116bd9fbdab87acf01a484e # v4.1.0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
path: "./utils/req-res-validator/requirements.txt"
|
||||
python-version: "3.x"
|
||||
cache: "pip"
|
||||
cache-dependency-path: "./utils/req-res-validator/requirements.txt"
|
||||
- name: Install Python dependencies
|
||||
run: python -m pip install -r ./utils/req-res-validator/requirements.txt
|
||||
- name: validator
|
||||
run: ./utils/req-res-log-validator.py --verbose --fail-missing-reply-schemas ${{ (!contains(github.event.inputs.skiptests, 'redis') && !contains(github.event.inputs.skiptests, 'module') && !contains(github.event.inputs.sentinel, 'redis') && !contains(github.event.inputs.skiptests, 'cluster')) && github.event.inputs.test_args == '' && github.event.inputs.cluster_test_args == '' && '--fail-commands-not-all-hit' || '' }}
|
||||
|
||||
|
|
|
|||
4
.github/workflows/external.yml
vendored
4
.github/workflows/external.yml
vendored
|
|
@ -6,6 +6,10 @@ on:
|
|||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test-external-standalone:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
4
.github/workflows/reply-schemas-linter.yml
vendored
4
.github/workflows/reply-schemas-linter.yml
vendored
|
|
@ -8,6 +8,10 @@ on:
|
|||
paths:
|
||||
- 'src/commands/*.json'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
reply-schemas-linter:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
4
.github/workflows/spell-check.yml
vendored
4
.github/workflows/spell-check.yml
vendored
|
|
@ -9,6 +9,10 @@ on:
|
|||
push:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Spellcheck
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -266,7 +266,7 @@ Tested with the following Docker image:
|
|||
|
||||
```sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
|
|
@ -320,7 +320,7 @@ Tested with the following Docker image:
|
|||
|
||||
```sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
|
|
@ -389,7 +389,7 @@ Tested with the following Docker image:
|
|||
|
||||
```sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
|
|
@ -446,7 +446,7 @@ Tested with the following Docker images:
|
|||
|
||||
```sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
|
|
@ -565,7 +565,7 @@ Tested with the following Docker images:
|
|||
```sh
|
||||
source /etc/profile.d/gcc-toolset-13.sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
|
|
@ -682,7 +682,7 @@ Tested with the following Docker images:
|
|||
```sh
|
||||
source /etc/profile.d/gcc-toolset-13.sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
|
|
@ -915,7 +915,7 @@ The following instructions apply to both Intel and Apple Silicon (ARM) Macs.
|
|||
export BUILD_TLS=yes
|
||||
export DISABLE_WERRORS=yes
|
||||
export LTO=0
|
||||
PATH="$HOMEBREW_PREFIX/opt/llvm@18/bin:$HOMEBREW_PREFIX/opt/make/libexec/gnubin:$HOMEBREW_PREFIX/opt/gnu-sed/libexec/gnubin:$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"
|
||||
PATH="$HOMEBREW_PREFIX/opt/libtool/libexec/gnubin:$HOMEBREW_PREFIX/opt/llvm@18/bin:$HOMEBREW_PREFIX/opt/make/libexec/gnubin:$HOMEBREW_PREFIX/opt/gnu-sed/libexec/gnubin:$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"
|
||||
export LDFLAGS="-L$HOMEBREW_PREFIX/opt/llvm@18/lib"
|
||||
export CPPFLAGS="-I$HOMEBREW_PREFIX/opt/llvm@18/include"
|
||||
mkdir -p build_dir/etc
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ all: prepare_source
|
|||
get_source:
|
||||
$(call submake,$@)
|
||||
|
||||
prepare_source: get_source handle-werrors setup_environment
|
||||
prepare_source: get_source setup_environment
|
||||
|
||||
clean:
|
||||
$(call submake,$@)
|
||||
|
|
@ -25,7 +25,7 @@ pristine:
|
|||
install:
|
||||
$(call submake,$@)
|
||||
|
||||
setup_environment: install-rust handle-werrors
|
||||
setup_environment: install-rust
|
||||
|
||||
clean_environment: uninstall-rust
|
||||
|
||||
|
|
@ -74,17 +74,4 @@ ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
|
|||
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
|
||||
.PHONY: all clean distclean install $(SUBDIRS) setup_environment clean_environment install-rust uninstall-rust
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
SRC_DIR = src
|
||||
MODULE_VERSION = v8.7.91
|
||||
MODULE_VERSION = v8.8.0
|
||||
MODULE_REPO = https://github.com/redisbloom/redisbloom
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redisbloom.so
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
SRC_DIR = src
|
||||
MODULE_VERSION = v8.7.91
|
||||
MODULE_VERSION = v8.8.0
|
||||
MODULE_REPO = https://github.com/redisearch/redisearch
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so
|
||||
|
||||
|
|
@ -7,10 +7,14 @@ TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so
|
|||
LTO ?= 1
|
||||
export LTO
|
||||
|
||||
# Use the committed C headers for Rust modules, rather than regenerating them
|
||||
# from Rust source. Override with REDISEARCH_GENERATE_HEADERS=1.
|
||||
REDISEARCH_GENERATE_HEADERS ?= 0
|
||||
export REDISEARCH_GENERATE_HEADERS
|
||||
|
||||
# Set INLINE_LSE_ATOMICS=1 for perf improvement on common ARM CPUs (i.e. Graviton2/3/4); no effect on x86 or macOS.
|
||||
# Default 0 keeps the binary runnable on pre-Armv8.1-a cores (Cortex-A72, Graviton1, RPi4) that would otherwise SIGILL at module load.
|
||||
INLINE_LSE_ATOMICS ?= 0
|
||||
export INLINE_LSE_ATOMICS
|
||||
|
||||
include ../common.mk
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
SRC_DIR = src
|
||||
MODULE_VERSION = v8.7.91
|
||||
MODULE_VERSION = v8.8.0
|
||||
MODULE_REPO = https://github.com/redisjson/redisjson
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/rejson.so
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
SRC_DIR = src
|
||||
MODULE_VERSION = v8.7.91
|
||||
MODULE_VERSION = v8.8.0
|
||||
MODULE_REPO = https://github.com/redistimeseries/redistimeseries
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redistimeseries.so
|
||||
|
||||
|
|
|
|||
5
runtest
5
runtest
|
|
@ -1,4 +1,9 @@
|
|||
#!/bin/sh
|
||||
# Raise open files limit (macOS default 256 is too low for tests).
|
||||
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
|
||||
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
|
||||
ulimit -n 1024 2>/dev/null || true
|
||||
fi
|
||||
TCL_VERSIONS="8.5 8.6 8.7 9.0"
|
||||
TCLSH=""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
#!/bin/sh
|
||||
# Raise open files limit (macOS default 256 is too low for tests).
|
||||
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
|
||||
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
|
||||
ulimit -n 1024 2>/dev/null || true
|
||||
fi
|
||||
TCL_VERSIONS="8.5 8.6 8.7 9.0"
|
||||
TCLSH=""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
#!/bin/sh
|
||||
# Raise open files limit (macOS default 256 is too low for tests).
|
||||
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
|
||||
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
|
||||
ulimit -n 1024 2>/dev/null || true
|
||||
fi
|
||||
TCL_VERSIONS="8.5 8.6 8.7 9.0"
|
||||
TCLSH=""
|
||||
[ -z "$MAKE" ] && MAKE=make
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
#!/bin/sh
|
||||
# Raise open files limit (macOS default 256 is too low for tests).
|
||||
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
|
||||
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
|
||||
ulimit -n 1024 2>/dev/null || true
|
||||
fi
|
||||
TCL_VERSIONS="8.5 8.6 8.7 9.0"
|
||||
TCLSH=""
|
||||
|
||||
|
|
|
|||
|
|
@ -2107,17 +2107,26 @@ int clusterCanAccessKeysInSlot(int slot) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Return the slot ranges that belong to the current node or its master. */
|
||||
/* Return the slot ranges that belong to the current node or its master.
|
||||
* In non-cluster mode, returns the full slot range (0-16383). */
|
||||
slotRangeArray *clusterGetLocalSlotRanges(void) {
|
||||
slotRangeArray *slots = NULL;
|
||||
|
||||
if (!server.cluster_enabled) {
|
||||
slots = slotRangeArrayCreate(1);
|
||||
slotRangeArray *slots = slotRangeArrayCreate(1);
|
||||
slotRangeArraySet(slots, 0, 0, CLUSTER_SLOTS - 1);
|
||||
return slots;
|
||||
}
|
||||
|
||||
clusterNode *master = clusterNodeGetMaster(getMyClusterNode());
|
||||
return clusterGetNodeSlotRanges(getMyClusterNode());
|
||||
}
|
||||
|
||||
/* Returns the slot ranges owned by the given node.
|
||||
* If the node is a replica, the master's slot ranges are returned.
|
||||
* Returns an empty array if the node has no slots. */
|
||||
slotRangeArray *clusterGetNodeSlotRanges(clusterNode *node) {
|
||||
slotRangeArray *slots = NULL;
|
||||
|
||||
serverAssert(server.cluster_enabled && node != NULL);
|
||||
clusterNode *master = clusterNodeGetMaster(node);
|
||||
if (master) {
|
||||
for (int i = 0; i < CLUSTER_SLOTS; i++) {
|
||||
if (clusterNodeCoversSlot(master, i))
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ int getSlotOrReply(client *c, robj *o);
|
|||
int clusterIsMySlot(int slot);
|
||||
int clusterCanAccessKeysInSlot(int slot);
|
||||
struct slotRangeArray *clusterGetLocalSlotRanges(void);
|
||||
struct slotRangeArray *clusterGetNodeSlotRanges(clusterNode *node);
|
||||
|
||||
/* functions with shared implementations */
|
||||
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot,
|
||||
|
|
|
|||
|
|
@ -11859,13 +11859,6 @@ struct COMMAND_ARG INCREX_increment_Subargs[] = {
|
|||
{MAKE_ARG("integer",ARG_TYPE_INTEGER,-1,"BYINT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* INCREX overflow_block argument table */
|
||||
struct COMMAND_ARG INCREX_overflow_block_Subargs[] = {
|
||||
{MAKE_ARG("fail",ARG_TYPE_PURE_TOKEN,-1,"FAIL",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("sat",ARG_TYPE_PURE_TOKEN,-1,"SAT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("reject",ARG_TYPE_PURE_TOKEN,-1,"REJECT",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
};
|
||||
|
||||
/* INCREX expiration argument table */
|
||||
struct COMMAND_ARG INCREX_expiration_Subargs[] = {
|
||||
{MAKE_ARG("seconds",ARG_TYPE_INTEGER,-1,"EX",NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
|
|
@ -11879,7 +11872,7 @@ struct COMMAND_ARG INCREX_expiration_Subargs[] = {
|
|||
struct COMMAND_ARG INCREX_Args[] = {
|
||||
{MAKE_ARG("key",ARG_TYPE_KEY,0,NULL,NULL,NULL,CMD_ARG_NONE,0,NULL)},
|
||||
{MAKE_ARG("increment",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,2,NULL),.subargs=INCREX_increment_Subargs},
|
||||
{MAKE_ARG("overflow-block",ARG_TYPE_ONEOF,-1,"OVERFLOW","Out-of-bounds policy; defaults to FAIL. Missing LBOUND/UBOUND default to the type limits (LLONG_MIN/LLONG_MAX for BYINT, -LDBL_MAX/LDBL_MAX for BYFLOAT).",NULL,CMD_ARG_OPTIONAL,3,NULL),.subargs=INCREX_overflow_block_Subargs},
|
||||
{MAKE_ARG("saturate",ARG_TYPE_PURE_TOKEN,-1,"SATURATE","Saturate the result to LBOUND/UBOUND (or the type limits when no explicit bound is given) when out of bounds. Without this option, out-of-bounds operations are rejected and reply [current_value, 0].",NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
{MAKE_ARG("lowerbound",ARG_TYPE_STRING,-1,"LBOUND","Integer when used with BYINT, floating-point when used with BYFLOAT.",NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
{MAKE_ARG("upperbound",ARG_TYPE_STRING,-1,"UBOUND","Integer when used with BYINT, floating-point when used with BYFLOAT.",NULL,CMD_ARG_OPTIONAL,0,NULL)},
|
||||
{MAKE_ARG("expiration",ARG_TYPE_ONEOF,-1,NULL,NULL,NULL,CMD_ARG_OPTIONAL,5,NULL),.subargs=INCREX_expiration_Subargs},
|
||||
|
|
|
|||
|
|
@ -74,28 +74,11 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"name": "overflow-block",
|
||||
"token": "OVERFLOW",
|
||||
"type": "oneof",
|
||||
"name": "saturate",
|
||||
"token": "SATURATE",
|
||||
"type": "pure-token",
|
||||
"optional": true,
|
||||
"summary": "Out-of-bounds policy; defaults to FAIL. Missing LBOUND/UBOUND default to the type limits (LLONG_MIN/LLONG_MAX for BYINT, -LDBL_MAX/LDBL_MAX for BYFLOAT).",
|
||||
"arguments": [
|
||||
{
|
||||
"name": "fail",
|
||||
"type": "pure-token",
|
||||
"token": "FAIL"
|
||||
},
|
||||
{
|
||||
"name": "sat",
|
||||
"type": "pure-token",
|
||||
"token": "SAT"
|
||||
},
|
||||
{
|
||||
"name": "reject",
|
||||
"type": "pure-token",
|
||||
"token": "REJECT"
|
||||
}
|
||||
]
|
||||
"summary": "Saturate the result to LBOUND/UBOUND (or the type limits when no explicit bound is given) when out of bounds. Without this option, out-of-bounds operations are rejected and reply [current_value, 0]."
|
||||
},
|
||||
{
|
||||
"name": "lowerbound",
|
||||
|
|
|
|||
|
|
@ -780,6 +780,11 @@ static int ff_eq(double a, double b) {
|
|||
return a == b;
|
||||
}
|
||||
|
||||
static int is_parse_failed(const char *s, size_t len, const char *eptr, int err, double d) {
|
||||
return ((size_t)(eptr - s) != len) || err == EINVAL ||
|
||||
(err == ERANGE && (d == HUGE_VAL || d == -HUGE_VAL || fpclassify(d) == FP_ZERO));
|
||||
}
|
||||
|
||||
static void run_ff_tests(ff_testcase *cases, int n, int expect_failed) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
const char *s = cases[i].input;
|
||||
|
|
@ -788,8 +793,7 @@ static void run_ff_tests(ff_testcase *cases, int n, int expect_failed) {
|
|||
|
||||
errno = 0;
|
||||
double d = fast_float_strtod(s, len, &eptr);
|
||||
int failed = ((size_t)(eptr - s) != len) || errno == EINVAL ||
|
||||
(errno == ERANGE && (d == HUGE_VAL || d == -HUGE_VAL || fpclassify(d) == FP_ZERO));
|
||||
int failed = is_parse_failed(s, len, eptr, errno, d);
|
||||
int ok = (expect_failed == failed) && ff_eq(d, cases[i].expected);
|
||||
char descr[128];
|
||||
if (ok)
|
||||
|
|
@ -802,6 +806,28 @@ static void run_ff_tests(ff_testcase *cases, int n, int expect_failed) {
|
|||
}
|
||||
}
|
||||
|
||||
static void run_ff_libc_compat_tests(const char **cases, int n) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
const char *s = cases[i];
|
||||
size_t len = strlen(s);
|
||||
char *eptr, *libc_eptr;
|
||||
|
||||
errno = 0;
|
||||
double d = fast_float_strtod(s, len, &eptr);
|
||||
int err = errno;
|
||||
|
||||
errno = 0;
|
||||
double libc_d = strtod(s, &libc_eptr);
|
||||
int libc_err = errno;
|
||||
|
||||
int failed = is_parse_failed(s, len, eptr, err, d);
|
||||
int libc_failed = is_parse_failed(s, len, libc_eptr, libc_err, libc_d);
|
||||
char descr[128];
|
||||
snprintf(descr, sizeof(descr), "ff matches libc strtod: \"%s\"", s);
|
||||
test_cond(descr, failed == libc_failed && (eptr - s) == (libc_eptr - s) && ff_eq(d, libc_d));
|
||||
}
|
||||
}
|
||||
|
||||
int fastFloatTest(int argc, char **argv, int flags) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
|
|
@ -1015,8 +1041,6 @@ int fastFloatTest(int argc, char **argv, int flags) {
|
|||
{"na", 0},
|
||||
{"nan(", NAN}, /* unclosed paren */
|
||||
{"nan(abc", NAN}, /* missing closing paren */
|
||||
{"nan(ab!c)", NAN}, /* invalid char in paren */
|
||||
{"nan(ab c)", NAN}, /* space in paren */
|
||||
{"nanx", NAN}, /* trailing garbage */
|
||||
};
|
||||
run_ff_tests(nan_invalid, COUNTOF(nan_invalid), 1);
|
||||
|
|
@ -1043,6 +1067,14 @@ int fastFloatTest(int argc, char **argv, int flags) {
|
|||
eptr == big && ff_eq(d, 0.0));
|
||||
}
|
||||
|
||||
/* The accepted character set for nan(n-char-sequence) is libc-dependent.
|
||||
* Preserve strtod-compatible behavior instead of asserting a fixed result. */
|
||||
const char *nan_libc_compat[] = {
|
||||
"nan(ab!c)",
|
||||
"nan(ab c)",
|
||||
};
|
||||
run_ff_libc_compat_tests(nan_libc_compat, COUNTOF(nan_libc_compat));
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
#include "server.h"
|
||||
|
||||
/* IO threads. */
|
||||
static IOThread IOThreads[IO_THREADS_MAX_NUM];
|
||||
IOThread IOThreads[IO_THREADS_MAX_NUM];
|
||||
|
||||
/* For main thread */
|
||||
static list *mainThreadPendingClientsToIOThreads[IO_THREADS_MAX_NUM]; /* Clients to IO threads */
|
||||
|
|
|
|||
23
src/module.c
23
src/module.c
|
|
@ -9808,6 +9808,28 @@ int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *m
|
|||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Returns the slot ranges owned by the cluster node identified by `nodeid`.
|
||||
*
|
||||
* An optional `ctx` can be provided to enable auto-memory management.
|
||||
* An empty array is returned if cluster mode is disabled (no cluster nodes
|
||||
* exist) or if no node matches `nodeid`.
|
||||
* If the node is a replica, the slot ranges of its master are returned.
|
||||
*
|
||||
* The returned array must be freed with RM_ClusterFreeSlotRanges(). */
|
||||
RedisModuleSlotRangeArray *RM_GetClusterNodeSlotRanges(RedisModuleCtx *ctx, const char *nodeid) {
|
||||
slotRangeArray *slots;
|
||||
|
||||
if (!server.cluster_enabled) {
|
||||
slots = slotRangeArrayCreate(0);
|
||||
} else {
|
||||
clusterNode *node = clusterLookupNode(nodeid, CLUSTER_NAMELEN);
|
||||
slots = node ? clusterGetNodeSlotRanges(node) : slotRangeArrayCreate(0);
|
||||
}
|
||||
|
||||
if (ctx) autoMemoryAdd(ctx, REDISMODULE_AM_SLOTRANGEARRAY, slots);
|
||||
return (RedisModuleSlotRangeArray *)slots;
|
||||
}
|
||||
|
||||
/* Set Redis Cluster flags in order to change the normal behavior of
|
||||
* Redis Cluster, especially with the goal of disabling certain functions.
|
||||
* This is useful for modules that use the Cluster API in order to create
|
||||
|
|
@ -15576,6 +15598,7 @@ void moduleRegisterCoreAPI(void) {
|
|||
REGISTER_API(RegisterClusterMessageReceiver);
|
||||
REGISTER_API(SendClusterMessage);
|
||||
REGISTER_API(GetClusterNodeInfo);
|
||||
REGISTER_API(GetClusterNodeSlotRanges);
|
||||
REGISTER_API(GetClusterNodesList);
|
||||
REGISTER_API(FreeClusterNodesList);
|
||||
REGISTER_API(CreateTimer);
|
||||
|
|
|
|||
|
|
@ -2777,7 +2777,7 @@ static inline int _writeToClientSlave(client *c, ssize_t *nwritten) {
|
|||
int writeToClient(client *c, int handler_installed) {
|
||||
if (!(c->io_flags & CLIENT_IO_WRITE_ENABLED)) return C_OK;
|
||||
/* Update the number of writes of io threads on server */
|
||||
atomicIncr(server.stat_io_writes_processed[c->running_tid], 1);
|
||||
atomicIncr(IOThreads[c->running_tid].io_writes_processed, 1);
|
||||
|
||||
ssize_t nwritten = 0, totwritten = 0;
|
||||
const int is_slave = clientTypeIsSlave(c);
|
||||
|
|
@ -3833,7 +3833,7 @@ void readQueryFromClient(connection *conn) {
|
|||
c->stat_total_read_events++;
|
||||
|
||||
/* Update the number of reads of io threads on server */
|
||||
atomicIncr(server.stat_io_reads_processed[c->running_tid], 1);
|
||||
atomicIncr(IOThreads[c->running_tid].io_reads_processed, 1);
|
||||
|
||||
readlen = PROTO_IOBUF_LEN;
|
||||
/* If this is a multi bulk request, and we are processing a bulk reply
|
||||
|
|
|
|||
|
|
@ -1390,6 +1390,7 @@ REDISMODULE_API int (*RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx
|
|||
REDISMODULE_API void (*RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, const char *target_id, uint8_t type, const char *msg, uint32_t len) REDISMODULE_ATTR;
|
||||
REDISMODULE_API int (*RedisModule_GetClusterNodeInfo)(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleSlotRangeArray *(*RedisModule_GetClusterNodeSlotRanges)(RedisModuleCtx *ctx, const char *nodeid) REDISMODULE_ATTR;
|
||||
REDISMODULE_API char ** (*RedisModule_GetClusterNodesList)(RedisModuleCtx *ctx, size_t *numnodes) REDISMODULE_ATTR;
|
||||
REDISMODULE_API void (*RedisModule_FreeClusterNodesList)(char **ids) REDISMODULE_ATTR;
|
||||
REDISMODULE_API RedisModuleTimerID (*RedisModule_CreateTimer)(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data) REDISMODULE_ATTR;
|
||||
|
|
@ -1795,6 +1796,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
|
|||
REDISMODULE_GET_API(RegisterClusterMessageReceiver);
|
||||
REDISMODULE_GET_API(SendClusterMessage);
|
||||
REDISMODULE_GET_API(GetClusterNodeInfo);
|
||||
REDISMODULE_GET_API(GetClusterNodeSlotRanges);
|
||||
REDISMODULE_GET_API(GetClusterNodesList);
|
||||
REDISMODULE_GET_API(FreeClusterNodesList);
|
||||
REDISMODULE_GET_API(CreateTimer);
|
||||
|
|
|
|||
|
|
@ -4010,7 +4010,7 @@ static void rdbChannelReplDataBufClear(void) {
|
|||
static int replDataBufReadIntoLastBlock(connection *conn, replDataBuf *buf,
|
||||
void (*error_handler)(connection *conn))
|
||||
{
|
||||
atomicIncr(server.stat_io_reads_processed[IOTHREAD_MAIN_THREAD_ID], 1);
|
||||
atomicIncr(IOThreads[IOTHREAD_MAIN_THREAD_ID].io_reads_processed, 1);
|
||||
|
||||
replDataBufBlock *block = listNodeValue(listLast(buf->blocks));
|
||||
serverAssert(block && block->size > block->used);
|
||||
|
|
|
|||
12
src/server.c
12
src/server.c
|
|
@ -2895,8 +2895,8 @@ void resetServerStats(void) {
|
|||
server.stat_sync_partial_ok = 0;
|
||||
server.stat_sync_partial_err = 0;
|
||||
for (j = 0; j < IO_THREADS_MAX_NUM; j++) {
|
||||
atomicSet(server.stat_io_reads_processed[j], 0);
|
||||
atomicSet(server.stat_io_writes_processed[j], 0);
|
||||
atomicSet(IOThreads[j].io_reads_processed, 0);
|
||||
atomicSet(IOThreads[j].io_writes_processed, 0);
|
||||
}
|
||||
atomicSet(server.stat_client_qbuf_limit_disconnections, 0);
|
||||
server.stat_client_outbuf_limit_disconnections = 0;
|
||||
|
|
@ -6623,8 +6623,8 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
|
|||
info = sdscatprintf(info, "# Threads\r\n");
|
||||
long long reads, writes;
|
||||
for (j = 0; j < server.io_threads_num; j++) {
|
||||
atomicGet(server.stat_io_reads_processed[j], reads);
|
||||
atomicGet(server.stat_io_writes_processed[j], writes);
|
||||
atomicGet(IOThreads[j].io_reads_processed, reads);
|
||||
atomicGet(IOThreads[j].io_writes_processed, writes);
|
||||
info = sdscatprintf(info, "io_thread_%d:clients=%d,reads=%lld,writes=%lld\r\n",
|
||||
j, server.io_threads_clients_num[j], reads, writes);
|
||||
stat_total_reads_processed += reads;
|
||||
|
|
@ -6661,10 +6661,10 @@ sds genRedisInfoString(dict *section_dict, int all_sections, int everything) {
|
|||
if (!stat_io_ops_processed_calculated) {
|
||||
long long reads, writes;
|
||||
for (j = 0; j < server.io_threads_num; j++) {
|
||||
atomicGet(server.stat_io_reads_processed[j], reads);
|
||||
atomicGet(IOThreads[j].io_reads_processed, reads);
|
||||
stat_total_reads_processed += reads;
|
||||
if (j != 0) stat_io_reads_processed += reads; /* Skip the main thread */
|
||||
atomicGet(server.stat_io_writes_processed[j], writes);
|
||||
atomicGet(IOThreads[j].io_writes_processed, writes);
|
||||
stat_total_writes_processed += writes;
|
||||
if (j != 0) stat_io_writes_processed += writes; /* Skip the main thread */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1677,8 +1677,12 @@ typedef struct __attribute__((aligned(CACHE_LINE_SIZE))) {
|
|||
pthread_mutex_t pending_clients_mutex; /* Mutex for pending write list */
|
||||
list *pending_clients_to_main_thread; /* Clients that are waiting to be executed by the main thread. */
|
||||
list *clients; /* IO thread managed clients. */
|
||||
redisAtomic long long io_reads_processed; /* Number of read events processed */
|
||||
redisAtomic long long io_writes_processed; /* Number of write events processed */
|
||||
} IOThread;
|
||||
|
||||
extern IOThread IOThreads[IO_THREADS_MAX_NUM];
|
||||
|
||||
/* Context for streaming replDataBuf to database */
|
||||
typedef struct replDataBufToDbCtx {
|
||||
void *privdata; /* Private data of context */
|
||||
|
|
@ -2157,8 +2161,6 @@ struct redisServer {
|
|||
long long stat_unexpected_error_replies; /* Number of unexpected (aof-loading, replica to master, etc.) error replies */
|
||||
long long stat_total_error_replies; /* Total number of issued error replies ( command + rejected errors ) */
|
||||
long long stat_dump_payload_sanitizations; /* Number deep dump payloads integrity validations. */
|
||||
redisAtomic long long stat_io_reads_processed[IO_THREADS_MAX_NUM]; /* Number of read events processed by IO / Main threads */
|
||||
redisAtomic long long stat_io_writes_processed[IO_THREADS_MAX_NUM]; /* Number of write events processed by IO / Main threads */
|
||||
redisAtomic long long stat_client_qbuf_limit_disconnections; /* Total number of clients reached query buf length limit */
|
||||
long long stat_client_outbuf_limit_disconnections; /* Total number of clients reached output buf length limit */
|
||||
long long stat_cluster_incompatible_ops; /* Number of operations that are incompatible with cluster mode */
|
||||
|
|
|
|||
|
|
@ -1003,15 +1003,13 @@ void incrbyfloatCommand(client *c) {
|
|||
#define OBJ_INCREX_BYINT (1<<1) /* Set if integer increment is given */
|
||||
#define OBJ_INCREX_LBOUND (1<<2) /* Set if lower bound of increx result is given */
|
||||
#define OBJ_INCREX_UBOUND (1<<3) /* Set if upper bound of increx result is given */
|
||||
#define OBJ_INCREX_OVERFLOW_FAIL (1<<4) /* Return an error when the result is out of bounds (default) */
|
||||
#define OBJ_INCREX_OVERFLOW_SAT (1<<5) /* Saturate the result to LBOUND/UBOUND/type limits instead of failing */
|
||||
#define OBJ_INCREX_OVERFLOW_REJECT (1<<6) /* Leave the key unchanged and reply [current_value, 0] when the result is out of bounds */
|
||||
#define OBJ_INCREX_ENX (1<<7) /* Set expiration only when the key has no expiry */
|
||||
#define OBJ_INCREX_PERSIST (1<<8) /* Set if we need to remove the ttl */
|
||||
#define OBJ_INCREX_EX (1<<9) /* Set if time in seconds is given */
|
||||
#define OBJ_INCREX_PX (1<<10) /* Set if time in ms is given */
|
||||
#define OBJ_INCREX_EXAT (1<<11) /* Set if timestamp in second is given */
|
||||
#define OBJ_INCREX_PXAT (1<<12) /* Set if timestamp in ms is given */
|
||||
#define OBJ_INCREX_SATURATE (1<<4) /* Saturate the result to LBOUND/UBOUND/type limits when out of bounds. */
|
||||
#define OBJ_INCREX_ENX (1<<5) /* Set expiration only when the key has no expiry */
|
||||
#define OBJ_INCREX_PERSIST (1<<6) /* Set if we need to remove the ttl */
|
||||
#define OBJ_INCREX_EX (1<<7) /* Set if time in seconds is given */
|
||||
#define OBJ_INCREX_PX (1<<8) /* Set if time in ms is given */
|
||||
#define OBJ_INCREX_EXAT (1<<9) /* Set if timestamp in second is given */
|
||||
#define OBJ_INCREX_PXAT (1<<10) /* Set if timestamp in ms is given */
|
||||
|
||||
/* INCREX argument structure */
|
||||
typedef struct {
|
||||
|
|
@ -1076,20 +1074,8 @@ static int parseIncrExArgumentsOrReply(client *c, int start_pos, incrExArgs *arg
|
|||
args->flags |= OBJ_INCREX_UBOUND;
|
||||
upper_bound = next;
|
||||
j++;
|
||||
} else if (!strcasecmp(opt, "OVERFLOW") && next &&
|
||||
!(args->flags & (OBJ_INCREX_OVERFLOW_FAIL|OBJ_INCREX_OVERFLOW_SAT|OBJ_INCREX_OVERFLOW_REJECT)))
|
||||
{
|
||||
if (!strcasecmp(next->ptr, "FAIL")) {
|
||||
args->flags |= OBJ_INCREX_OVERFLOW_FAIL;
|
||||
} else if (!strcasecmp(next->ptr, "SAT")) {
|
||||
args->flags |= OBJ_INCREX_OVERFLOW_SAT;
|
||||
} else if (!strcasecmp(next->ptr, "REJECT")) {
|
||||
args->flags |= OBJ_INCREX_OVERFLOW_REJECT;
|
||||
} else {
|
||||
addReplyError(c, "OVERFLOW policy must be FAIL, SAT or REJECT");
|
||||
return C_ERR;
|
||||
}
|
||||
j++;
|
||||
} else if (!strcasecmp(opt, "SATURATE") && !(args->flags & OBJ_INCREX_SATURATE)) {
|
||||
args->flags |= OBJ_INCREX_SATURATE;
|
||||
} else if (!strcasecmp(opt, "ENX") && !(args->flags & (OBJ_INCREX_ENX|OBJ_INCREX_PERSIST))) {
|
||||
args->flags |= OBJ_INCREX_ENX;
|
||||
} else if (!strcasecmp(opt, "PERSIST") && !(args->flags & (expire_flags|OBJ_INCREX_ENX))) {
|
||||
|
|
@ -1167,7 +1153,7 @@ static int parseIncrExArgumentsOrReply(client *c, int start_pos, incrExArgs *arg
|
|||
|
||||
/*
|
||||
* INCREX <key> [BYFLOAT increment | BYINT increment] [LBOUND lowerbound]
|
||||
* [UBOUND upperbound] [OVERFLOW <FAIL | SAT | REJECT>]
|
||||
* [UBOUND upperbound] [SATURATE]
|
||||
* [EX seconds | PX milliseconds | EXAT seconds-timestamp | PXAT milliseconds-timestamp | PERSIST] [ENX]
|
||||
*
|
||||
* Increments the numeric value of a key and optionally updates its expiration time.
|
||||
|
|
@ -1181,15 +1167,13 @@ static int parseIncrExArgumentsOrReply(client *c, int start_pos, incrExArgs *arg
|
|||
* Range options:
|
||||
* LBOUND and UBOUND optionally restrict the result to a range. The behavior
|
||||
* when the result would land outside that range (or, with no explicit bound,
|
||||
* would overflow the type limits) is controlled by OVERFLOW:
|
||||
* - OVERFLOW FAIL (default): the operation is rejected with an error,
|
||||
* matching the semantics of INCRBY/INCRBYFLOAT.
|
||||
* - OVERFLOW SAT: the result is silently capped at UBOUND / floored at LBOUND
|
||||
* (or saturated to the type limits when no explicit bound is
|
||||
* given) instead of producing an error.
|
||||
* - OVERFLOW REJECT: the operation is silently skipped (the key value and TTL
|
||||
* are left unchanged) and the reply is the current value with
|
||||
* an applied increment of 0, instead of producing an error.
|
||||
* would overflow the type limits) is controlled by SATURATE:
|
||||
* - Default: the operation is rejected (the key value and TTL are left
|
||||
* unchanged) and the reply is the current value with an applied
|
||||
* increment of 0.
|
||||
* - SATURATE: the result is capped at UBOUND / floored at LBOUND (or
|
||||
* saturated to the type limits when no explicit bound is given)
|
||||
* instead of being rejected.
|
||||
*
|
||||
* Expiration options:
|
||||
* At most one of the following may be specified:
|
||||
|
|
@ -1203,7 +1187,6 @@ static int parseIncrExArgumentsOrReply(client *c, int start_pos, incrExArgs *arg
|
|||
* ENX restricts expiration updates to keys that currently have no TTL.
|
||||
*
|
||||
* Reply:
|
||||
* - (Simple Error) if any parameter is invalid, or if BYFLOAT produces NaN or Infinity.
|
||||
* - (Array) of two Bulk Strings on success:
|
||||
* 1. The new value of the key after the increment.
|
||||
* 2. The actual increment applied.
|
||||
|
|
@ -1225,9 +1208,9 @@ void increxCommand(client *c) {
|
|||
if (checkType(c, o, OBJ_STRING)) return;
|
||||
|
||||
int byfloat = args.flags & OBJ_INCREX_BYFLOAT;
|
||||
/* FAIL is the default when no OVERFLOW policy is specified. */
|
||||
int fail_mode = !(args.flags & (OBJ_INCREX_OVERFLOW_SAT | OBJ_INCREX_OVERFLOW_REJECT));
|
||||
int reject_mode = args.flags & OBJ_INCREX_OVERFLOW_REJECT;
|
||||
/* By default the operation is rejected on out-of-bounds:
|
||||
* leave the key unchanged and reply [current_value, 0]. */
|
||||
int sat_mode = args.flags & OBJ_INCREX_SATURATE;
|
||||
if (byfloat) {
|
||||
long double lb = args.lb_ld, ub = args.ub_ld;
|
||||
if (getLongDoubleFromObjectOrReply(c, o, &value_ld, NULL) != C_OK)
|
||||
|
|
@ -1244,24 +1227,17 @@ void increxCommand(client *c) {
|
|||
value_ld += args.incr_ld;
|
||||
int overflow = isinf(value_ld);
|
||||
if (overflow || value_ld > ub || value_ld < lb) {
|
||||
/* FAIL: return an error. */
|
||||
if (fail_mode) {
|
||||
addReplyError(c, overflow ? "increment would produce Infinity" :
|
||||
"value is out of bounds");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Result is infinite or out of [LBOUND, UBOUND]:
|
||||
* FAIL: error; SAT: clamp to +/-LDBL_MAX or the breached bound;
|
||||
* REJECT: leave key untouched, reply [current_value, 0]. */
|
||||
if (reject_mode) {
|
||||
* default: reject (leave key untouched, reply [current_value, 0]);
|
||||
* SATURATE: clamp to +/-LDBL_MAX or the breached bound. */
|
||||
if (!sat_mode) {
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyHumanLongDouble(c, oldvalue_ld);
|
||||
addReplyHumanLongDouble(c, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* SAT: clamp the result. */
|
||||
/* SATURATE: clamp the result. */
|
||||
if (overflow)
|
||||
value_ld = (args.incr_ld >= 0) ? ub : lb;
|
||||
else
|
||||
|
|
@ -1271,7 +1247,7 @@ void increxCommand(client *c) {
|
|||
long double delta = value_ld - oldvalue_ld;
|
||||
if (isinf(delta)) {
|
||||
/* The applied delta cannot be represented as a valid long double. This can
|
||||
* only happen under OVERFLOW SAT when the saturated result and the
|
||||
* only happen under SATURATE when the saturated result and the
|
||||
* prior value sit at opposite ends of the type range. */
|
||||
addReplyError(c, "applied increment would be Infinity");
|
||||
return;
|
||||
|
|
@ -1288,24 +1264,17 @@ void increxCommand(client *c) {
|
|||
oldvalue_ll = value_ll;
|
||||
int overflow = add_overflow_ll(oldvalue_ll, args.incr_ll, &value_ll);
|
||||
if (overflow || value_ll > ub || value_ll < lb) {
|
||||
/* FAIL: return an error. */
|
||||
if (fail_mode) {
|
||||
addReplyError(c, overflow ? "increment or decrement would overflow" :
|
||||
"value is out of bounds");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Result overflows long long or is out of [LBOUND, UBOUND]:
|
||||
* FAIL: error; SAT: clamp to LLONG_MAX/LLONG_MIN or the breached bound;
|
||||
* REJECT: leave key untouched, reply [current_value, 0]. */
|
||||
if (reject_mode) {
|
||||
* default: reject (leave key untouched, reply [current_value, 0]);
|
||||
* SATURATE: clamp to LLONG_MAX/LLONG_MIN or the breached bound. */
|
||||
if (!sat_mode) {
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyLongLong(c, oldvalue_ll);
|
||||
addReplyLongLong(c, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* SAT: clamp the result. */
|
||||
/* SATURATE: clamp the result. */
|
||||
if (overflow)
|
||||
value_ll = (args.incr_ll >= 0) ? ub : lb;
|
||||
else
|
||||
|
|
@ -1315,7 +1284,7 @@ void increxCommand(client *c) {
|
|||
long long delta = 0;
|
||||
if (sub_overflow_ll(value_ll, oldvalue_ll, &delta)) {
|
||||
/* The applied delta cannot be represented as a long long. This can
|
||||
* only happen under OVERFLOW SAT when the saturated result and the
|
||||
* only happen under SATURATE when the saturated result and the
|
||||
* prior value sit at opposite ends of the type range. */
|
||||
addReplyError(c, "applied increment would overflow");
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ start_server {tags {"cli"}} {
|
|||
|
||||
# We may have a short read, try to read some more.
|
||||
set empty_reads 0
|
||||
while {$empty_reads < 5} {
|
||||
while {$empty_reads < 100} {
|
||||
set buf [read $fd]
|
||||
if {[string length $buf] == 0} {
|
||||
after 10
|
||||
|
|
|
|||
|
|
@ -886,27 +886,44 @@ proc compute_cpu_usage {start end} {
|
|||
return [ list $pucpu $pscpu ]
|
||||
}
|
||||
|
||||
|
||||
if {!$::valgrind} {
|
||||
# test diskless rdb pipe with multiple replicas, which may drop half way
|
||||
start_server {tags {"repl external:skip tsan:skip"} overrides {save ""}} {
|
||||
set master [srv 0 client]
|
||||
$master config set repl-diskless-sync yes
|
||||
$master config set repl-diskless-sync-delay 5
|
||||
$master config set repl-diskless-sync-max-replicas 2
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
set master_pid [srv 0 pid]
|
||||
# put enough data in the db that the rdb file will be bigger than the socket buffers
|
||||
# and since we'll have key-load-delay of 100, 20000 keys will take at least 2 seconds
|
||||
# we also need the replica to process requests during transfer (which it does only once in 2mb)
|
||||
$master debug populate 20000 test 10000
|
||||
$master config set rdbcompression no
|
||||
$master config set repl-rdb-channel no
|
||||
# If running on Linux, we also measure utime/stime to detect possible I/O handling issues
|
||||
set os [catch {exec uname}]
|
||||
set measure_time [expr {$os == "Linux"} ? 1 : 0]
|
||||
foreach all_drop {no slow fast all timeout} {
|
||||
foreach all_drop {no slow fast all timeout} {
|
||||
start_server {tags {"repl external:skip tsan:skip"} overrides {save ""}} {
|
||||
set master [srv 0 client]
|
||||
$master config set repl-diskless-sync yes
|
||||
$master config set repl-diskless-sync-delay 5
|
||||
$master config set repl-diskless-sync-max-replicas 2
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
set master_pid [srv 0 pid]
|
||||
if {$all_drop == "timeout"} {
|
||||
# Use a larger RDB (~100 MB) so it cannot fit into the kernel TCP
|
||||
# send buffer (autotuning can absorb tens of MB on some hosts). We
|
||||
# need the primary to hit the blocked writer path
|
||||
# (repl_last_partial_write != 0) while the slow replica is paused,
|
||||
# so the cron triggers the "(full sync)" timeout path instead of
|
||||
# the replica being moved to ONLINE prematurely and timing out via
|
||||
# the "(streaming sync)" path.
|
||||
$master debug populate 10000 test 10000
|
||||
} else {
|
||||
# Put enough data in the db that the RDB is comfortably larger than the
|
||||
# pipe and socket buffers so the primary can hit the blocked writer path,
|
||||
# but keep it small enough that slow TLS CI runners don't spend minutes
|
||||
# draining an oversized transfer (~40 MB uncompressed).
|
||||
$master debug populate 4000 test 10000
|
||||
}
|
||||
$master config set rdbcompression no
|
||||
$master config set repl-rdb-channel no
|
||||
# If running on Linux, we also measure utime/stime to detect possible I/O handling issues
|
||||
set os [catch {exec uname}]
|
||||
set measure_time [expr {$os == "Linux"} ? 1 : 0]
|
||||
|
||||
test "diskless $all_drop replicas drop during rdb pipe" {
|
||||
# Reset config that the timeout subcase may change, so a failing
|
||||
# subcase does not leave the next one with an aggressive timeout.
|
||||
$master config set repl-timeout 60
|
||||
$master config set rdb-key-save-delay 0
|
||||
set replicas {}
|
||||
set replicas_alive {}
|
||||
# start one replica that will read the rdb fast, and one that will be slow
|
||||
|
|
@ -923,7 +940,24 @@ start_server {tags {"repl external:skip tsan:skip"} overrides {save ""}} {
|
|||
set loglines [count_log_lines -2]
|
||||
[lindex $replicas 0] config set repl-diskless-load swapdb
|
||||
[lindex $replicas 1] config set repl-diskless-load swapdb
|
||||
[lindex $replicas 0] config set key-load-delay 100 ;# 20k keys and 100 microseconds sleep means at least 2 seconds
|
||||
if {$all_drop == "all"} {
|
||||
# Keep the RDB child generating data long enough for
|
||||
# both replicas to be killed before the pipe reaches
|
||||
# EOF, so this subcase still covers the last-replica
|
||||
# drop path instead of racing with normal completion.
|
||||
$master config set rdb-key-save-delay 1000
|
||||
}
|
||||
# For non-timeout subcases, use key-load-delay to keep
|
||||
# replica 0 as a steady slow reader for the entire RDB
|
||||
# transfer. This keeps the expected diskless pipe code
|
||||
# paths covered without accepting alternate log outcomes.
|
||||
if {$all_drop != "timeout"} {
|
||||
# 4k keys with 500 microseconds each keeps replica 0
|
||||
# slow for about 2 seconds, which is long enough to
|
||||
# fill the pipe without turning the transfer into a
|
||||
# multi-minute TLS run.
|
||||
[lindex $replicas 0] config set key-load-delay 500
|
||||
}
|
||||
[lindex $replicas 0] replicaof $master_host $master_port
|
||||
[lindex $replicas 1] replicaof $master_host $master_port
|
||||
|
||||
|
|
@ -937,9 +971,16 @@ start_server {tags {"repl external:skip tsan:skip"} overrides {save ""}} {
|
|||
set start_time [clock seconds]
|
||||
}
|
||||
|
||||
# wait a while so that the pipe socket writer will be
|
||||
# blocked on write (since replica 0 is slow to read from the socket)
|
||||
after 500
|
||||
if {$all_drop != "timeout"} {
|
||||
# key-load-delay is already throttling the slow
|
||||
# replica; just wait for the pipe to fill.
|
||||
after 500
|
||||
} else {
|
||||
# For the timeout subcase, stop the slow reader so it
|
||||
# reaches repl-timeout during full sync.
|
||||
pause_process [srv -1 pid]
|
||||
after 500
|
||||
}
|
||||
|
||||
# add some command to be present in the command stream after the rdb.
|
||||
$master incr $all_drop
|
||||
|
|
@ -954,14 +995,17 @@ start_server {tags {"repl external:skip tsan:skip"} overrides {save ""}} {
|
|||
set replicas_alive [lreplace $replicas_alive 0 0]
|
||||
}
|
||||
if {$all_drop == "timeout"} {
|
||||
# Let one replica hit repl-timeout while the slow reader
|
||||
# is paused, then restore a generous timeout so the
|
||||
# remaining replica can finish the streamed RDB.
|
||||
$master config set repl-timeout 2
|
||||
# we want the slow replica to hang on a key for very long so it'll reach repl-timeout
|
||||
pause_process [srv -1 pid]
|
||||
after 2000
|
||||
wait_for_log_messages -2 {"*Disconnecting timedout replica (full sync)*"} $loglines 200 100
|
||||
$master config set repl-timeout 60
|
||||
}
|
||||
|
||||
# wait for rdb child to exit
|
||||
wait_for_condition 500 100 {
|
||||
# Use a single generous budget for all subcases; successful
|
||||
# runs still exit early once the child is done.
|
||||
wait_for_condition 5000 100 {
|
||||
[s -2 rdb_bgsave_in_progress] == 0
|
||||
} else {
|
||||
fail "rdb child didn't terminate"
|
||||
|
|
@ -978,7 +1022,6 @@ start_server {tags {"repl external:skip tsan:skip"} overrides {save ""}} {
|
|||
wait_for_log_messages -2 {"*Diskless rdb transfer, done reading from pipe, 1 replicas still up*"} $loglines 1 1
|
||||
}
|
||||
if {$all_drop == "timeout"} {
|
||||
wait_for_log_messages -2 {"*Disconnecting timedout replica (full sync)*"} $loglines 1 1
|
||||
wait_for_log_messages -2 {"*Diskless rdb transfer, done reading from pipe, 1 replicas still up*"} $loglines 1 1
|
||||
# master disconnected the slow replica, remove from array
|
||||
set replicas_alive [lreplace $replicas_alive 0 0]
|
||||
|
|
@ -1002,18 +1045,23 @@ start_server {tags {"repl external:skip tsan:skip"} overrides {save ""}} {
|
|||
assert {$master_utime < 70}
|
||||
assert {$master_stime < 70}
|
||||
}
|
||||
if {!$::no_latency && ($all_drop == "none" || $all_drop == "fast")} {
|
||||
if {!$::no_latency && ($all_drop == "no" || $all_drop == "fast")} {
|
||||
assert {$master_utime < 15}
|
||||
assert {$master_stime < 15}
|
||||
}
|
||||
}
|
||||
|
||||
# In the "no" case both replicas stay alive through the
|
||||
# full streamed RDB, so on slow TLS runners the final
|
||||
# ONLINE transition can lag behind child exit.
|
||||
set replica_online_wait_tries [expr {$all_drop == "no" ? 600 : 150}]
|
||||
|
||||
# verify the data integrity
|
||||
foreach replica $replicas_alive {
|
||||
# Wait that replicas acknowledge they are online so
|
||||
# we are sure that DBSIZE and DEBUG DIGEST will not
|
||||
# fail because of timing issues.
|
||||
wait_for_condition 150 100 {
|
||||
wait_for_condition $replica_online_wait_tries 100 {
|
||||
[lindex [$replica role] 3] eq {connected}
|
||||
} else {
|
||||
fail "replicas still not connected after some time"
|
||||
|
|
@ -1038,6 +1086,7 @@ start_server {tags {"repl external:skip tsan:skip"} overrides {save ""}} {
|
|||
}
|
||||
}
|
||||
}
|
||||
} ;# end of valgrind
|
||||
|
||||
test "diskless replication child being killed is collected" {
|
||||
# when diskless master is waiting for the replica to become writable
|
||||
|
|
|
|||
|
|
@ -90,6 +90,36 @@ int testClusterGetLocalSlotRanges(RedisModuleCtx *ctx, RedisModuleString **argv,
|
|||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Test command for RedisModule_GetClusterNodeSlotRanges */
|
||||
int testGetClusterNodeSlotRanges(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
if (argc != 2) {
|
||||
return RedisModule_WrongArity(ctx);
|
||||
}
|
||||
|
||||
const char *nodeid = RedisModule_StringPtrLen(argv[1], NULL);
|
||||
|
||||
static int use_auto_memory = 0;
|
||||
use_auto_memory = !use_auto_memory;
|
||||
|
||||
RedisModuleSlotRangeArray *slots;
|
||||
if (use_auto_memory) {
|
||||
RedisModule_AutoMemory(ctx);
|
||||
slots = RedisModule_GetClusterNodeSlotRanges(ctx, nodeid);
|
||||
} else {
|
||||
slots = RedisModule_GetClusterNodeSlotRanges(NULL, nodeid);
|
||||
}
|
||||
|
||||
RedisModule_ReplyWithArray(ctx, slots->num_ranges);
|
||||
for (int i = 0; i < slots->num_ranges; i++) {
|
||||
RedisModule_ReplyWithArray(ctx, 2);
|
||||
RedisModule_ReplyWithLongLong(ctx, slots->ranges[i].start);
|
||||
RedisModule_ReplyWithLongLong(ctx, slots->ranges[i].end);
|
||||
}
|
||||
if (!use_auto_memory)
|
||||
RedisModule_ClusterFreeSlotRanges(NULL, slots);
|
||||
return REDISMODULE_OK;
|
||||
}
|
||||
|
||||
/* Helper function to check if a slot range array contains a given slot. */
|
||||
int slotRangeArrayContains(RedisModuleSlotRangeArray *sra, unsigned int slot) {
|
||||
for (int i = 0; i < sra->num_ranges; i++)
|
||||
|
|
@ -562,6 +592,9 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
|
|||
if (RedisModule_CreateCommand(ctx, "asm.cluster_get_local_slot_ranges", testClusterGetLocalSlotRanges, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "asm.get_cluster_node_slot_ranges", testGetClusterNodeSlotRanges, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
if (RedisModule_CreateCommand(ctx, "asm.get_last_deleted_key", getLastDeletedKey, "", 0, 0, 0) == REDISMODULE_ERR)
|
||||
return REDISMODULE_ERR;
|
||||
|
||||
|
|
|
|||
|
|
@ -2943,6 +2943,32 @@ start_cluster 3 6 [list tags {external:skip cluster modules} config_lines [list
|
|||
assert_equal [R 1 asm.cluster_get_local_slot_ranges] {}
|
||||
assert_equal [R 4 asm.cluster_get_local_slot_ranges] {}
|
||||
}
|
||||
|
||||
test "Test RM_GetClusterNodeSlotRanges for local node" {
|
||||
set local_id [R 0 cluster myid]
|
||||
set ranges [R 0 asm.get_cluster_node_slot_ranges $local_id]
|
||||
set local_ranges [R 0 asm.cluster_get_local_slot_ranges]
|
||||
assert_equal $ranges $local_ranges
|
||||
}
|
||||
|
||||
test "Test RM_GetClusterNodeSlotRanges for remote node" {
|
||||
set node2_id [R 2 cluster myid]
|
||||
set ranges [R 0 asm.get_cluster_node_slot_ranges $node2_id]
|
||||
set remote_ranges [R 2 asm.cluster_get_local_slot_ranges]
|
||||
assert_equal $ranges $remote_ranges
|
||||
}
|
||||
|
||||
test "Test RM_GetClusterNodeSlotRanges for non-existent node" {
|
||||
set ranges [R 0 asm.get_cluster_node_slot_ranges "0000000000000000000000000000000000000000"]
|
||||
assert_equal $ranges {}
|
||||
}
|
||||
|
||||
test "Test RM_GetClusterNodeSlotRanges for replica returns master slots" {
|
||||
set replica3_id [R 3 cluster myid]
|
||||
set ranges [R 0 asm.get_cluster_node_slot_ranges $replica3_id]
|
||||
set master_ranges [R 0 asm.cluster_get_local_slot_ranges]
|
||||
assert_equal $ranges $master_ranges
|
||||
}
|
||||
}
|
||||
|
||||
set testmodule [file normalize tests/modules/atomicslotmigration.so]
|
||||
|
|
@ -3056,4 +3082,13 @@ start_server {tags "cluster external:skip"} {
|
|||
assert_equal [r asm.cluster_get_local_slot_ranges] {{0 16383}}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags "cluster external:skip"} {
|
||||
test "Test RM_GetClusterNodeSlotRanges without cluster" {
|
||||
r module load $testmodule
|
||||
set local_id "nonexistent-node-id"
|
||||
set ranges [r asm.get_cluster_node_slot_ranges $local_id]
|
||||
assert_equal $ranges {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ start_server {tags {"increx"}} {
|
|||
|
||||
test {INCREX - BYINT saturates to UBOUND} {
|
||||
r set mykey 50
|
||||
assert_equal [r increx mykey BYINT 100 UBOUND 80 OVERFLOW SAT] {80 30}
|
||||
assert_equal [r increx mykey BYINT 100 UBOUND 80 SATURATE] {80 30}
|
||||
assert_equal [r get mykey] 80
|
||||
}
|
||||
|
||||
test {INCREX - BYINT saturates to LBOUND} {
|
||||
r set mykey 10
|
||||
assert_equal [r increx mykey BYINT -100 LBOUND 0 OVERFLOW SAT] {0 -10}
|
||||
assert_equal [r increx mykey BYINT -100 LBOUND 0 SATURATE] {0 -10}
|
||||
assert_equal [r get mykey] 0
|
||||
}
|
||||
|
||||
|
|
@ -41,40 +41,40 @@ start_server {tags {"increx"}} {
|
|||
assert_equal [r increx mykey BYINT 1 LBOUND 0 UBOUND 10] {6 1}
|
||||
}
|
||||
|
||||
test {INCREX - BYINT positive overflow with OVERFLOW SAT saturates to LLONG_MAX} {
|
||||
test {INCREX - BYINT positive overflow with SATURATE saturates to LLONG_MAX} {
|
||||
# LLONG_MAX = 9223372036854775807
|
||||
r set mykey 9223372036854775800
|
||||
assert_equal [r increx mykey BYINT 9223372036854775800 OVERFLOW SAT] {9223372036854775807 7}
|
||||
assert_equal [r increx mykey BYINT 9223372036854775800 SATURATE] {9223372036854775807 7}
|
||||
assert_equal [r get mykey] 9223372036854775807
|
||||
}
|
||||
|
||||
test {INCREX - BYINT positive overflow with OVERFLOW SAT and UBOUND saturates to UBOUND} {
|
||||
test {INCREX - BYINT positive overflow with SATURATE and UBOUND saturates to UBOUND} {
|
||||
# LLONG_MAX = 9223372036854775807
|
||||
r set mykey 9223372036854775800
|
||||
assert_equal [r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807 OVERFLOW SAT] {9223372036854775807 7}
|
||||
assert_equal [r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807 SATURATE] {9223372036854775807 7}
|
||||
assert_equal [r get mykey] 9223372036854775807
|
||||
}
|
||||
|
||||
test {INCREX - BYINT negative overflow with OVERFLOW SAT saturates to LLONG_MIN} {
|
||||
test {INCREX - BYINT negative overflow with SATURATE saturates to LLONG_MIN} {
|
||||
# LLONG_MIN = -9223372036854775808
|
||||
r set mykey -9223372036854775800
|
||||
assert_equal [r increx mykey BYINT -9223372036854775800 OVERFLOW SAT] {-9223372036854775808 -8}
|
||||
assert_equal [r increx mykey BYINT -9223372036854775800 SATURATE] {-9223372036854775808 -8}
|
||||
assert_equal [r get mykey] -9223372036854775808
|
||||
}
|
||||
|
||||
test {INCREX - BYINT negative overflow with OVERFLOW SAT and LBOUND saturates to LBOUND} {
|
||||
test {INCREX - BYINT negative overflow with SATURATE and LBOUND saturates to LBOUND} {
|
||||
# LLONG_MIN = -9223372036854775808
|
||||
r set mykey -9223372036854775800
|
||||
assert_equal [r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808 OVERFLOW SAT] {-9223372036854775808 -8}
|
||||
assert_equal [r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808 SATURATE] {-9223372036854775808 -8}
|
||||
assert_equal [r get mykey] -9223372036854775808
|
||||
}
|
||||
|
||||
test {INCREX - BYINT SAT rejects when applied delta would overflow long long} {
|
||||
test {INCREX - BYINT SATURATE rejects when applied delta would overflow long long} {
|
||||
# The saturated result lands at LLONG_MIN while the prior value is positive,
|
||||
# so the reported delta would not fit in a long long.
|
||||
r set mykey 9223372036854775800
|
||||
assert_error "*applied increment would overflow*" {
|
||||
r increx mykey BYINT 1 OVERFLOW SAT UBOUND -9223372036854775808
|
||||
r increx mykey BYINT 1 SATURATE UBOUND -9223372036854775808
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,9 +102,9 @@ start_server {tags {"increx"}} {
|
|||
|
||||
test {INCREX - BYFLOAT saturates to UBOUND/LBOUND} {
|
||||
r set mykey 10
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 100 UBOUND 42.5 OVERFLOW SAT] {roundFloat $v}] {42.5 32.5}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 100 UBOUND 42.5 SATURATE] {roundFloat $v}] {42.5 32.5}
|
||||
r set mykey 0
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT -100 LBOUND -5.5 OVERFLOW SAT] {roundFloat $v}] {-5.5 -5.5}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT -100 LBOUND -5.5 SATURATE] {roundFloat $v}] {-5.5 -5.5}
|
||||
}
|
||||
|
||||
# On some platforms strtold("+inf") with valgrind returns a non-inf result
|
||||
|
|
@ -127,34 +127,35 @@ start_server {tags {"increx"}} {
|
|||
|
||||
# ---------------------------------------------------------------------
|
||||
# Non-existent key whose default 0 is already outside [LBOUND, UBOUND]
|
||||
# and the increment cannot bring it back into range -> refuse to create.
|
||||
# and the increment cannot bring it back into range -> default policy
|
||||
# leaves the key absent and replies [0, 0].
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
test {INCREX - BYINT/BYFLOAT on non-existent key refuses to create when result stays below LBOUND} {
|
||||
r del mykey
|
||||
assert_error "*value is out of bounds*" {r increx mykey BYINT 5 LBOUND 10}
|
||||
assert_equal [r increx mykey BYINT 5 LBOUND 10] {0 0}
|
||||
assert_equal [r exists mykey] 0
|
||||
|
||||
assert_error "*value is out of bounds*" {r increx mykey BYFLOAT -0.5 UBOUND -1.5}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT -0.5 UBOUND -1.5] {roundFloat $v}] {0 0}
|
||||
assert_equal [r exists mykey] 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Existing key whose value is already outside [LBOUND, UBOUND] is treated
|
||||
# the same as an in-range value pushed outside by the increment: OVERFLOW
|
||||
# FAIL errors out and OVERFLOW SAT saturates the result.
|
||||
# the same as an in-range value pushed outside by the increment: the
|
||||
# default policy leaves the key alone and SATURATE saturates.
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
test {INCREX - BYFLOAT existing value already outside bounds} {
|
||||
# Above UBOUND, same-side increment: FAIL errors, SAT saturates to UBOUND.
|
||||
# Above UBOUND, same-side increment: default leaves value unchanged, SATURATE saturates to UBOUND.
|
||||
r set mykey 50.5
|
||||
assert_error "*out of bounds*" {r increx mykey BYFLOAT 5.5 UBOUND 30}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 5.5 UBOUND 30] {roundFloat $v}] {50.5 0}
|
||||
assert_equal [roundFloat [r get mykey]] 50.5
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 5.5 UBOUND 30 OVERFLOW SAT] {roundFloat $v}] {30 -20.5}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 5.5 UBOUND 30 SATURATE] {roundFloat $v}] {30 -20.5}
|
||||
|
||||
# Below LBOUND, same-side decrement: SAT saturates to LBOUND.
|
||||
# Below LBOUND, same-side decrement: SATURATE saturates to LBOUND.
|
||||
r set mykey -50.5
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT -5.5 LBOUND -30 OVERFLOW SAT] {roundFloat $v}] {-30 20.5}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT -5.5 LBOUND -30 SATURATE] {roundFloat $v}] {-30 20.5}
|
||||
|
||||
# Increment that brings the out-of-range value back inside is applied normally.
|
||||
r set mykey 50
|
||||
|
|
@ -162,15 +163,15 @@ start_server {tags {"increx"}} {
|
|||
}
|
||||
|
||||
test {INCREX - BYINT existing value already outside bounds} {
|
||||
# Above UBOUND, same-side increment: FAIL errors, SAT saturates to UBOUND.
|
||||
# Above UBOUND, same-side increment: default leaves value unchanged, SATURATE saturates to UBOUND.
|
||||
r set mykey 50
|
||||
assert_error "*out of bounds*" {r increx mykey BYINT 5 UBOUND 30}
|
||||
assert_equal [r increx mykey BYINT 5 UBOUND 30] {50 0}
|
||||
assert_equal [r get mykey] 50
|
||||
assert_equal [r increx mykey BYINT 5 UBOUND 30 OVERFLOW SAT] {30 -20}
|
||||
assert_equal [r increx mykey BYINT 5 UBOUND 30 SATURATE] {30 -20}
|
||||
|
||||
# Below LBOUND, same-side decrement: SAT saturates to LBOUND.
|
||||
# Below LBOUND, same-side decrement: SATURATE saturates to LBOUND.
|
||||
r set mykey -50
|
||||
assert_equal [r increx mykey BYINT -5 LBOUND -30 OVERFLOW SAT] {-30 20}
|
||||
assert_equal [r increx mykey BYINT -5 LBOUND -30 SATURATE] {-30 20}
|
||||
|
||||
# Increment that brings the out-of-range value back inside is applied normally.
|
||||
r set mykey 50
|
||||
|
|
@ -178,37 +179,34 @@ start_server {tags {"increx"}} {
|
|||
}
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Out-of-range behavior: OVERFLOW FAIL (the default) errors out (like
|
||||
# INCRBY); OVERFLOW SAT saturates the result silently.
|
||||
# Out-of-range behavior: by default the operation is rejected
|
||||
# (reply is [current_value, 0]); SATURATE saturates the result.
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
test {INCREX - BYINT OVERFLOW FAIL rejects increment exceeding UBOUND; OVERFLOW SAT saturates it} {
|
||||
test {INCREX - BYINT default rejects increment exceeding UBOUND; SATURATE saturates it} {
|
||||
r set mykey 10
|
||||
assert_error "*out of bounds*" {r increx mykey BYINT 10 UBOUND 15}
|
||||
# Value is unchanged after the error
|
||||
assert_equal [r increx mykey BYINT 10 UBOUND 15] {10 0}
|
||||
# Value is unchanged
|
||||
assert_equal [r get mykey] 10
|
||||
# OVERFLOW FAIL is the explicit form of the default
|
||||
assert_error "*out of bounds*" {r increx mykey BYINT 10 UBOUND 15 OVERFLOW FAIL}
|
||||
assert_equal [r get mykey] 10
|
||||
# OVERFLOW SAT saturates the result at UBOUND
|
||||
assert_equal [r increx mykey BYINT 10 UBOUND 15 OVERFLOW SAT] {15 5}
|
||||
# SATURATE saturates the result at UBOUND
|
||||
assert_equal [r increx mykey BYINT 10 UBOUND 15 SATURATE] {15 5}
|
||||
assert_equal [r get mykey] 15
|
||||
}
|
||||
|
||||
test {INCREX - BYINT OVERFLOW FAIL rejects decrement falling below LBOUND; OVERFLOW SAT floors it} {
|
||||
test {INCREX - BYINT default rejects decrement falling below LBOUND; SATURATE floors it} {
|
||||
r set mykey 10
|
||||
assert_error "*out of bounds*" {r increx mykey BYINT -10 LBOUND 5}
|
||||
assert_equal [r increx mykey BYINT -10 LBOUND 5] {10 0}
|
||||
assert_equal [r get mykey] 10
|
||||
# OVERFLOW SAT floors the result at LBOUND
|
||||
assert_equal [r increx mykey BYINT -10 LBOUND 5 OVERFLOW SAT] {5 -5}
|
||||
# SATURATE floors the result at LBOUND
|
||||
assert_equal [r increx mykey BYINT -10 LBOUND 5 SATURATE] {5 -5}
|
||||
assert_equal [r get mykey] 5
|
||||
}
|
||||
|
||||
test {INCREX - BYINT within bounds is unaffected by OVERFLOW policy} {
|
||||
test {INCREX - BYINT within bounds is unaffected by SATURATE} {
|
||||
r set mykey 10
|
||||
assert_equal [r increx mykey BYINT 3 UBOUND 20] {13 3}
|
||||
assert_equal [r increx mykey BYINT -3 LBOUND 0 OVERFLOW SAT] {10 -3}
|
||||
assert_equal [r increx mykey BYINT 1 UBOUND 20 OVERFLOW FAIL] {11 1}
|
||||
assert_equal [r increx mykey BYINT -3 LBOUND 0 SATURATE] {10 -3}
|
||||
assert_equal [r increx mykey BYINT 1 UBOUND 20] {11 1}
|
||||
}
|
||||
|
||||
test {INCREX - BYINT with both LBOUND and UBOUND} {
|
||||
|
|
@ -216,13 +214,13 @@ start_server {tags {"increx"}} {
|
|||
# Within range -> allowed
|
||||
assert_equal [r increx mykey BYINT 2 LBOUND 0 UBOUND 10] {7 2}
|
||||
# Exceeds UBOUND -> rejected, value unchanged
|
||||
assert_error "*out of bounds*" {r increx mykey BYINT 10 LBOUND 0 UBOUND 10}
|
||||
assert_equal [r increx mykey BYINT 10 LBOUND 0 UBOUND 10] {7 0}
|
||||
# Falls below LBOUND -> rejected, value unchanged
|
||||
assert_error "*out of bounds*" {r increx mykey BYINT -20 LBOUND 0 UBOUND 10}
|
||||
assert_equal [r increx mykey BYINT -20 LBOUND 0 UBOUND 10] {7 0}
|
||||
assert_equal [r get mykey] 7
|
||||
# OVERFLOW SAT saturates at the bounds
|
||||
assert_equal [r increx mykey BYINT 10 LBOUND 0 UBOUND 10 OVERFLOW SAT] {10 3}
|
||||
assert_equal [r increx mykey BYINT -20 LBOUND 0 UBOUND 10 OVERFLOW SAT] {0 -10}
|
||||
# SATURATE saturates at the bounds
|
||||
assert_equal [r increx mykey BYINT 10 LBOUND 0 UBOUND 10 SATURATE] {10 3}
|
||||
assert_equal [r increx mykey BYINT -20 LBOUND 0 UBOUND 10 SATURATE] {0 -10}
|
||||
}
|
||||
|
||||
test {INCREX - BYINT at exact bound value is accepted} {
|
||||
|
|
@ -233,26 +231,26 @@ start_server {tags {"increx"}} {
|
|||
assert_equal [r increx mykey BYINT -10 LBOUND 0] {0 -10}
|
||||
}
|
||||
|
||||
test {INCREX - BYFLOAT OVERFLOW FAIL rejects increment exceeding UBOUND; OVERFLOW SAT saturates it} {
|
||||
test {INCREX - BYFLOAT default rejects increment exceeding UBOUND; SATURATE saturates it} {
|
||||
r set mykey 10.0
|
||||
assert_error "ERR value is out of bounds*" {r increx mykey BYFLOAT 10.0 UBOUND 15.5}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 10.0 UBOUND 15.5] {roundFloat $v}] {10 0}
|
||||
assert_equal [roundFloat [r get mykey]] 10
|
||||
# OVERFLOW SAT saturates the result at UBOUND
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 10.0 UBOUND 15.5 OVERFLOW SAT] {roundFloat $v}] {15.5 5.5}
|
||||
# SATURATE saturates the result at UBOUND
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 10.0 UBOUND 15.5 SATURATE] {roundFloat $v}] {15.5 5.5}
|
||||
}
|
||||
|
||||
test {INCREX - BYFLOAT OVERFLOW FAIL rejects decrement falling below LBOUND; OVERFLOW SAT floors it} {
|
||||
test {INCREX - BYFLOAT default rejects decrement falling below LBOUND; SATURATE floors it} {
|
||||
r set mykey 10.0
|
||||
assert_error "ERR value is out of bounds*" {r increx mykey BYFLOAT -10.0 LBOUND 5.5}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT -10.0 LBOUND 5.5] {roundFloat $v}] {10 0}
|
||||
assert_equal [roundFloat [r get mykey]] 10
|
||||
# OVERFLOW SAT floors the result at LBOUND
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT -10.0 LBOUND 5.5 OVERFLOW SAT] {roundFloat $v}] {5.5 -4.5}
|
||||
# SATURATE floors the result at LBOUND
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT -10.0 LBOUND 5.5 SATURATE] {roundFloat $v}] {5.5 -4.5}
|
||||
}
|
||||
|
||||
test {INCREX - BYFLOAT within bounds is unaffected by OVERFLOW policy} {
|
||||
test {INCREX - BYFLOAT within bounds is unaffected by SATURATE policy} {
|
||||
r set mykey 1.5
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 0.25 UBOUND 10.0] {roundFloat $v}] {1.75 0.25}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 0.25 UBOUND 10.0 OVERFLOW SAT] {roundFloat $v}] {2 0.25}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 0.25 UBOUND 10.0 SATURATE] {roundFloat $v}] {2 0.25}
|
||||
}
|
||||
|
||||
test {INCREX - BYFLOAT with both LBOUND and UBOUND} {
|
||||
|
|
@ -260,9 +258,9 @@ start_server {tags {"increx"}} {
|
|||
# Within range -> allowed
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 1.5 LBOUND 0 UBOUND 10] {roundFloat $v}] {6.5 1.5}
|
||||
# Exceeds UBOUND -> rejected
|
||||
assert_error "ERR value is out of bounds*" {r increx mykey BYFLOAT 10 LBOUND 0 UBOUND 10}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 10 LBOUND 0 UBOUND 10] {roundFloat $v}] {6.5 0}
|
||||
# Falls below LBOUND -> rejected
|
||||
assert_error "ERR value is out of bounds*" {r increx mykey BYFLOAT -20 LBOUND 0 UBOUND 10}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT -20 LBOUND 0 UBOUND 10] {roundFloat $v}] {6.5 0}
|
||||
assert_equal [lmap v [r get mykey] {roundFloat $v}] {6.5}
|
||||
}
|
||||
|
||||
|
|
@ -272,22 +270,22 @@ start_server {tags {"increx"}} {
|
|||
assert_equal [lmap v [r increx mykey BYFLOAT -10.0 LBOUND 0] {roundFloat $v}] {0 -10}
|
||||
}
|
||||
|
||||
test {INCREX - BYINT positive overflow: default errors, OVERFLOW SAT saturates} {
|
||||
test {INCREX - BYINT positive overflow: default rejects, SATURATE saturates} {
|
||||
# LLONG_MAX = 9223372036854775807
|
||||
r set mykey 9223372036854775800
|
||||
assert_error "*increment or decrement would overflow*" {r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807}
|
||||
assert_equal [r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807] {9223372036854775800 0}
|
||||
assert_equal [r get mykey] 9223372036854775800
|
||||
# OVERFLOW SAT: overflow saturates to LLONG_MAX, then saturates to UBOUND
|
||||
assert_equal [r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807 OVERFLOW SAT] {9223372036854775807 7}
|
||||
# SATURATE: overflow saturates to LLONG_MAX, then saturates to UBOUND
|
||||
assert_equal [r increx mykey BYINT 9223372036854775800 UBOUND 9223372036854775807 SATURATE] {9223372036854775807 7}
|
||||
}
|
||||
|
||||
test {INCREX - BYINT negative overflow: default errors, OVERFLOW SAT saturates} {
|
||||
test {INCREX - BYINT negative overflow: default rejects, SATURATE saturates} {
|
||||
# LLONG_MIN = -9223372036854775808
|
||||
r set mykey -9223372036854775800
|
||||
assert_error "*increment or decrement would overflow*" {r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808}
|
||||
assert_equal [r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808] {-9223372036854775800 0}
|
||||
assert_equal [r get mykey] -9223372036854775800
|
||||
# OVERFLOW SAT: overflow saturates to LLONG_MIN, then saturates to LBOUND
|
||||
assert_equal [r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808 OVERFLOW SAT] {-9223372036854775808 -8}
|
||||
# SATURATE: overflow saturates to LLONG_MIN, then saturates to LBOUND
|
||||
assert_equal [r increx mykey BYINT -9223372036854775800 LBOUND -9223372036854775808 SATURATE] {-9223372036854775808 -8}
|
||||
}
|
||||
|
||||
test {INCREX - BYINT on new key (created from zero) with bound} {
|
||||
|
|
@ -296,7 +294,7 @@ start_server {tags {"increx"}} {
|
|||
assert_equal [r increx mykey BYINT 5 UBOUND 10] {5 5}
|
||||
r del mykey
|
||||
# Increment from 0 exceeds UBOUND -> rejected, key not created
|
||||
assert_error "*out of bounds*" {r increx mykey BYINT 15 UBOUND 10}
|
||||
assert_equal [r increx mykey BYINT 15 UBOUND 10] {0 0}
|
||||
assert_equal [r exists mykey] 0
|
||||
}
|
||||
|
||||
|
|
@ -306,28 +304,28 @@ start_server {tags {"increx"}} {
|
|||
assert_equal [lmap v [r increx mykey BYFLOAT 5.5 UBOUND 10] {roundFloat $v}] {5.5 5.5}
|
||||
r del mykey
|
||||
# Increment from 0 exceeds UBOUND -> rejected, key not created
|
||||
assert_error "ERR value is out of bounds*" {r increx mykey BYFLOAT 15.5 UBOUND 10}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 15.5 UBOUND 10] {roundFloat $v}] {0 0}
|
||||
assert_equal [r exists mykey] 0
|
||||
}
|
||||
|
||||
test {INCREX - default with no bound behaves like INCRBY/INCRBYFLOAT} {
|
||||
test {INCREX - default with no bound saturates to type limits with SATURATE, rejects otherwise} {
|
||||
# In-range increments behave like INCRBY/INCRBYFLOAT.
|
||||
r set mykey 10
|
||||
assert_equal [r increx mykey BYINT 1] {11 1}
|
||||
assert_equal [lmap v [r increx mykey BYFLOAT 1.0] {roundFloat $v}] {12 1}
|
||||
assert_equal [r increx mykey] {13 1}
|
||||
|
||||
# BYINT overflow without an explicit bound -> error (like INCRBY).
|
||||
# BYINT overflow without an explicit bound -> default rejects (reply [current, 0]).
|
||||
r set mykey 9223372036854775800
|
||||
assert_error "*increment or decrement would overflow*" {r increx mykey BYINT 9223372036854775800}
|
||||
assert_equal [r increx mykey BYINT 9223372036854775800] {9223372036854775800 0}
|
||||
assert_equal [r get mykey] 9223372036854775800
|
||||
}
|
||||
|
||||
test {INCREX - error aborts before side effects: neither value nor TTL is modified} {
|
||||
test {INCREX - reject aborts before side effects: neither value nor TTL is modified} {
|
||||
r del mykey
|
||||
r set mykey 10
|
||||
# An out-of-range result aborts the command before any side effect.
|
||||
assert_error "*out of bounds*" {r increx mykey BYINT 100 UBOUND 15 EX 100}
|
||||
assert_equal [r increx mykey BYINT 100 UBOUND 15 EX 100] {10 0}
|
||||
assert_equal [r get mykey] 10
|
||||
assert_equal [r ttl mykey] -1
|
||||
|
||||
|
|
@ -339,32 +337,11 @@ start_server {tags {"increx"}} {
|
|||
|
||||
r del mykey
|
||||
r set mykey 10
|
||||
# OVERFLOW SAT also updates the TTL when saturation kicks in.
|
||||
assert_equal [r increx mykey BYINT 100 UBOUND 15 OVERFLOW SAT EX 200] {15 5}
|
||||
# SATURATE also updates the TTL when saturation kicks in.
|
||||
assert_equal [r increx mykey BYINT 100 UBOUND 15 SATURATE EX 200] {15 5}
|
||||
assert_morethan [r ttl mykey] 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# OVERFLOW REJECT: leave the key (and TTL) unchanged and reply
|
||||
# [current_value, 0] when the result would be out of bounds, instead of
|
||||
# producing an error.
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
test {INCREX - BYINT REJECT on overflow leaves value unchanged, in-range applies normally} {
|
||||
# llong overflow path
|
||||
r set mykey 9223372036854775800
|
||||
assert_equal [r increx mykey BYINT 9223372036854775800 OVERFLOW REJECT] {9223372036854775800 0}
|
||||
assert_equal [r get mykey] 9223372036854775800
|
||||
# UBOUND / LBOUND paths
|
||||
r set mykey 10
|
||||
assert_equal [r increx mykey BYINT 100 UBOUND 15 OVERFLOW REJECT] {10 0}
|
||||
assert_equal [r increx mykey BYINT -100 LBOUND 5 OVERFLOW REJECT] {10 0}
|
||||
assert_equal [r get mykey] 10
|
||||
# In-range increment is applied normally
|
||||
assert_equal [r increx mykey BYINT 3 UBOUND 20 OVERFLOW REJECT] {13 3}
|
||||
assert_equal [r get mykey] 13
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Argument parsing / syntax validation
|
||||
# ---------------------------------------------------------------------
|
||||
|
|
@ -401,10 +378,8 @@ start_server {tags {"increx"}} {
|
|||
assert_error "*syntax error*" {r increx mykey BYFLOAT 1.0 BYFLOAT 2.0}
|
||||
assert_error "*syntax error*" {r increx mykey LBOUND 0 LBOUND 1}
|
||||
assert_error "*syntax error*" {r increx mykey UBOUND 9 UBOUND 8}
|
||||
assert_error "*syntax error*" {r increx mykey OVERFLOW FAIL OVERFLOW SAT LBOUND 0}
|
||||
assert_error "*syntax error*" {r increx mykey OVERFLOW SAT OVERFLOW SAT LBOUND 0}
|
||||
assert_error "*syntax error*" {r increx mykey OVERFLOW REJECT OVERFLOW SAT LBOUND 0}
|
||||
assert_error "*syntax error*" {r increx mykey OVERFLOW REJECT OVERFLOW REJECT LBOUND 0}
|
||||
assert_error "*syntax error*" {r increx mykey SATURATE SATURATE LBOUND 0}
|
||||
assert_error "*syntax error*" {r increx mykey SAT LBOUND 0}
|
||||
assert_error "*syntax error*" {r increx mykey ENX ENX EX 10}
|
||||
assert_error "*syntax error*" {r increx mykey PERSIST PERSIST}
|
||||
assert_error "*syntax error*" {r increx mykey EX 10 EX 20}
|
||||
|
|
@ -585,7 +560,7 @@ start_server {tags {"increx"}} {
|
|||
|
||||
# LBOUND/UBOUND interleaved with increment
|
||||
r set mykey 5
|
||||
assert_equal [r increx mykey LBOUND 0 BYINT 100 UBOUND 10 OVERFLOW SAT] {10 5}
|
||||
assert_equal [r increx mykey LBOUND 0 BYINT 100 UBOUND 10 SATURATE] {10 5}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
|
@ -713,11 +688,11 @@ start_server {tags {"increx"}} {
|
|||
r flushall
|
||||
set repl [attach_to_replication_stream]
|
||||
r set mykey 50
|
||||
# With UBOUND + OVERFLOW SAT the final value is saturated; the SET
|
||||
# With UBOUND + SATURATE the final value is saturated; the SET
|
||||
# rewrite must carry the saturated value (80), not the unbounded 150.
|
||||
r increx mykey BYINT 100 UBOUND 80 OVERFLOW SAT
|
||||
r increx mykey BYINT 100 UBOUND 80 SATURATE
|
||||
r set myfloat 10
|
||||
r increx myfloat BYFLOAT 100 UBOUND 42.5 OVERFLOW SAT
|
||||
r increx myfloat BYFLOAT 100 UBOUND 42.5 SATURATE
|
||||
assert_replication_stream $repl {
|
||||
{select *}
|
||||
{set mykey 50*}
|
||||
|
|
|
|||
Loading…
Reference in a new issue