Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08e388cab8 | ||
|
|
6348cb52f5 | ||
|
|
fb011c8d5c | ||
|
|
4d3f4d75ef | ||
|
|
e261ae1260 | ||
|
|
ef4e7d6555 | ||
|
|
157a85b9e9 | ||
|
|
1ae5d2d8f1 | ||
|
|
7e6a497ed1 | ||
|
|
e2743ca467 | ||
|
|
9951b16d1b | ||
|
|
f5c368e9e1 | ||
|
|
dc72f47588 | ||
|
|
0befdee478 | ||
|
|
fa564cdc71 | ||
|
|
4b7ccb3ed3 | ||
|
|
31cd2309b8 | ||
|
|
4bbcb0173a | ||
|
|
f363394877 | ||
|
|
c70d660b31 | ||
|
|
18d6e34ce0 | ||
|
|
a14d587ff0 | ||
|
|
41834b157c | ||
|
|
154a633c33 | ||
|
|
1bccc8e3ba | ||
|
|
71b230c62a | ||
|
|
70080e8a97 | ||
|
|
1f5d891080 | ||
|
|
b091afc47a | ||
|
|
06d7a561ae | ||
|
|
0d4ef40768 | ||
|
|
732bb009bb | ||
|
|
8cf8e3fb28 | ||
|
|
bc8a114957 | ||
|
|
b2ffab404d | ||
|
|
1eebcd5f81 | ||
|
|
a1473af68f | ||
|
|
fcd9f0ffac | ||
|
|
c0a9643312 | ||
|
|
908f863870 | ||
|
|
a681073365 | ||
|
|
2bdb021d93 | ||
|
|
10c00687d8 | ||
|
|
6fc7ecb575 | ||
|
|
5dd14d8f4d | ||
|
|
a9656d44cc | ||
|
|
a8622d8f75 | ||
|
|
6bfd2abe00 | ||
|
|
c2c9d1a36d | ||
|
|
17420f6540 | ||
|
|
67d4826522 | ||
|
|
0915747ff4 | ||
|
|
55a5d5c53d | ||
|
|
cf89dd54e5 | ||
|
|
743db67670 | ||
|
|
5b2ad0fe2c | ||
|
|
475153931b | ||
|
|
5a1141d344 | ||
|
|
e449cc9752 | ||
|
|
65d31e744d | ||
|
|
bbd96ab81f | ||
|
|
0ccc15fd0b | ||
|
|
699d8c64d3 | ||
|
|
1691a000ef | ||
|
|
c8687e7326 | ||
|
|
04e7c62ae6 | ||
|
|
0010dc7d3c |
@@ -21,15 +21,15 @@
|
||||
},
|
||||
{
|
||||
"arch": "arm64",
|
||||
"target": "ubuntu20.04",
|
||||
"target": "ubuntu-22.04-arm",
|
||||
"type": "deb",
|
||||
"platform": "focal"
|
||||
"platform": "jammy"
|
||||
},
|
||||
{
|
||||
"arch": "arm64",
|
||||
"target": "ubuntu22.04",
|
||||
"target": "ubuntu-24.04-arm",
|
||||
"type": "deb",
|
||||
"platform": "jammy"
|
||||
"platform": "noble"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
build-valkey:
|
||||
# Capture source tarball and generate checksum for it
|
||||
name: Build package ${{ matrix.distro.target }} ${{ matrix.distro.arch }}
|
||||
runs-on: "ubuntu-latest"
|
||||
runs-on: ${{matrix.distro.target}}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(inputs.build_matrix) }}
|
||||
@@ -51,13 +51,11 @@ jobs:
|
||||
aws-region: ${{ inputs.region }}
|
||||
role-to-assume: ${{ secrets.role_to_assume }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install -y build-essential libssl-dev libsystemd-dev
|
||||
|
||||
- name: Make Valkey
|
||||
uses: uraimo/run-on-arch-action@v2
|
||||
with:
|
||||
arch: aarch64
|
||||
distro: ${{matrix.distro.target}}
|
||||
install: apt-get update && apt-get install -y build-essential libssl-dev libsystemd-dev
|
||||
run: make -C src all BUILD_TLS=yes USE_SYSTEMD=yes
|
||||
run: make -C src all BUILD_TLS=yes USE_SYSTEMD=yes
|
||||
|
||||
- name: Create Tarball and SHA256sums
|
||||
run: |
|
||||
|
||||
+94
-11
@@ -12,10 +12,10 @@ on:
|
||||
inputs:
|
||||
skipjobs:
|
||||
description: "jobs to skip (delete the ones you wanna keep, do not leave empty)"
|
||||
default: "valgrind,sanitizer,tls,freebsd,macos,alpine,32bit,iothreads,ubuntu,rpm-distros,malloc,specific,fortify,reply-schema"
|
||||
default: "valgrind,sanitizer,tls,freebsd,macos,alpine,32bit,iothreads,ubuntu,rpm-distros,malloc,specific,fortify,reply-schema,arm"
|
||||
skiptests:
|
||||
description: "tests to skip (delete the ones you wanna keep, do not leave empty)"
|
||||
default: "valkey,modules,sentinel,cluster,unittest"
|
||||
default: "valkey,modules,sentinel,cluster,unittest,large-memory"
|
||||
test_args:
|
||||
description: "extra test arguments"
|
||||
default: ""
|
||||
@@ -79,6 +79,48 @@ jobs:
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests --accurate
|
||||
|
||||
test-ubuntu-arm:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'schedule' && github.repository == 'valkey-io/valkey') ||
|
||||
(github.event_name == 'pull_request' && (contains(github.event.pull_request.labels.*.name, 'run-extra-tests') || github.event.pull_request.base.ref != 'unstable'))) &&
|
||||
(!contains(github.event.inputs.skipjobs, 'ubuntu') || !contains(github.event.inputs.skipjobs, 'arm'))
|
||||
timeout-minutes: 14400
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
echo "GITHUB_REPOSITORY=${{github.event.inputs.use_repo}}" >> $GITHUB_ENV
|
||||
echo "GITHUB_HEAD_REF=${{github.event.inputs.use_git_ref}}" >> $GITHUB_ENV
|
||||
echo "skipjobs: ${{github.event.inputs.skipjobs}}"
|
||||
echo "skiptests: ${{github.event.inputs.skiptests}}"
|
||||
echo "test_args: ${{github.event.inputs.test_args}}"
|
||||
echo "cluster_test_args: ${{github.event.inputs.cluster_test_args}}"
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: make all-with-unit-tests SERVER_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: sudo apt-get install tcl8.6 tclx
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'valkey')
|
||||
run: ./runtest ${{ github.event_name != 'pull_request' && '--accurate' || '' }} --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: module api test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel ${{github.event.inputs.cluster_test_args}}
|
||||
- name: cluster tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'cluster')
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests --accurate
|
||||
|
||||
test-ubuntu-jemalloc-fortify:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
@@ -675,7 +717,13 @@ jobs:
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests
|
||||
run: ./src/valkey-unit-tests --large-memory
|
||||
- name: large memory tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'valkey') && !contains(github.event.inputs.skiptests, 'large-memory')
|
||||
run: ./runtest --accurate --verbose --dump-logs --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
- name: large memory module api tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules') && !contains(github.event.inputs.skiptests, 'large-memory')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
|
||||
test-sanitizer-undefined:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -725,7 +773,13 @@ jobs:
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
- name: unittest
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/valkey-unit-tests --accurate
|
||||
run: ./src/valkey-unit-tests --accurate --large-memory
|
||||
- name: large memory tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'valkey') && !contains(github.event.inputs.skiptests, 'large-memory')
|
||||
run: ./runtest --accurate --verbose --dump-logs --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
- name: large memory module api tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'modules') && !contains(github.event.inputs.skiptests, 'large-memory')
|
||||
run: CFLAGS='-Werror' ./runtest-moduleapi --verbose --dump-logs --large-memory --tags large-memory ${{github.event.inputs.test_args}}
|
||||
|
||||
test-sanitizer-force-defrag:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -794,8 +848,8 @@ jobs:
|
||||
install_epel: true
|
||||
- name: test-fedoralatest-jemalloc
|
||||
container: fedora:latest
|
||||
- name: test-fedorarawhide-jemalloc
|
||||
container: fedora:rawhide
|
||||
# - name: test-fedorarawhide-jemalloc
|
||||
# container: fedora:rawhide
|
||||
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
@@ -860,8 +914,8 @@ jobs:
|
||||
install_epel: true
|
||||
- name: test-fedoralatest-tls-module
|
||||
container: fedora:latest
|
||||
- name: test-fedorarawhide-tls-module
|
||||
container: fedora:rawhide
|
||||
# - name: test-fedorarawhide-tls-module
|
||||
# container: fedora:rawhide
|
||||
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
@@ -932,8 +986,8 @@ jobs:
|
||||
install_epel: true
|
||||
- name: test-fedoralatest-tls-module-no-tls
|
||||
container: fedora:latest
|
||||
- name: test-fedorarawhide-tls-module-no-tls
|
||||
container: fedora:rawhide
|
||||
# - name: test-fedorarawhide-tls-module-no-tls
|
||||
# container: fedora:rawhide
|
||||
|
||||
name: ${{ matrix.name }}
|
||||
runs-on: ubuntu-latest
|
||||
@@ -1262,7 +1316,36 @@ jobs:
|
||||
notify-about-job-results:
|
||||
runs-on: ubuntu-latest
|
||||
if: always() && github.event_name == 'schedule' && github.repository == 'valkey-io/valkey'
|
||||
needs: [test-ubuntu-jemalloc, test-ubuntu-jemalloc-fortify, test-ubuntu-libc-malloc, test-ubuntu-no-malloc-usable-size, test-ubuntu-32bit, test-ubuntu-tls, test-ubuntu-tls-no-tls, test-ubuntu-io-threads, test-ubuntu-tls-io-threads, test-ubuntu-reclaim-cache, test-valgrind-test, test-valgrind-misc, test-valgrind-no-malloc-usable-size-test, test-valgrind-no-malloc-usable-size-misc, test-sanitizer-address, test-sanitizer-undefined, test-sanitizer-force-defrag, test-rpm-distros-jemalloc, test-rpm-distros-tls-module, test-rpm-distros-tls-module-no-tls, test-macos-latest, test-macos-latest-sentinel, test-macos-latest-cluster, build-macos, test-freebsd, test-alpine-jemalloc, test-alpine-libc-malloc, reply-schemas-validator]
|
||||
needs:
|
||||
- test-ubuntu-jemalloc
|
||||
- test-ubuntu-arm
|
||||
- test-ubuntu-jemalloc-fortify
|
||||
- test-ubuntu-libc-malloc
|
||||
- test-ubuntu-no-malloc-usable-size
|
||||
- test-ubuntu-32bit
|
||||
- test-ubuntu-tls
|
||||
- test-ubuntu-tls-no-tls
|
||||
- test-ubuntu-io-threads
|
||||
- test-ubuntu-tls-io-threads
|
||||
- test-ubuntu-reclaim-cache
|
||||
- test-valgrind-test
|
||||
- test-valgrind-misc
|
||||
- test-valgrind-no-malloc-usable-size-test
|
||||
- test-valgrind-no-malloc-usable-size-misc
|
||||
- test-sanitizer-address
|
||||
- test-sanitizer-undefined
|
||||
- test-sanitizer-force-defrag
|
||||
- test-rpm-distros-jemalloc
|
||||
- test-rpm-distros-tls-module
|
||||
- test-rpm-distros-tls-module-no-tls
|
||||
- test-macos-latest
|
||||
- test-macos-latest-sentinel
|
||||
- test-macos-latest-cluster
|
||||
- build-macos
|
||||
- test-freebsd
|
||||
- test-alpine-jemalloc
|
||||
- test-alpine-libc-malloc
|
||||
- reply-schemas-validator
|
||||
steps:
|
||||
- name: Collect job status
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Trigger Build Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version of Valkey to build
|
||||
required: true
|
||||
environment:
|
||||
description: Environment to build
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- dev
|
||||
- prod
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Determine version and environment
|
||||
id: determine-vars
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "release" ]]; then
|
||||
echo "Triggered by a release event."
|
||||
VERSION=${{ github.event.release.tag_name }}
|
||||
ENVIRONMENT="prod"
|
||||
else
|
||||
echo "Triggered manually (workflow_dispatch)."
|
||||
VERSION=${{ inputs.version }}
|
||||
ENVIRONMENT=${{ inputs.environment }}
|
||||
fi
|
||||
|
||||
# Set the outputs for version and environment
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "environment=$ENVIRONMENT" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Trigger build
|
||||
uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # Version 3
|
||||
with:
|
||||
token: ${{ secrets.AUTOMATION_PAT }}
|
||||
repository: ${{ github.repository_owner }}/valkey-release-automation
|
||||
event-type: build-release
|
||||
client-payload: >
|
||||
{
|
||||
"version": "${{ steps.determine-vars.outputs.version }}",
|
||||
"environment": "${{ steps.determine-vars.outputs.environment }}"
|
||||
}
|
||||
+194
-11
@@ -1,16 +1,199 @@
|
||||
Hello! This file is just a placeholder, since this is the "unstable" branch
|
||||
of Valkey, the place where all the development happens.
|
||||
Valkey 8.1 release notes
|
||||
========================
|
||||
--------------------------------------------------------------------------------
|
||||
Upgrade urgency levels:
|
||||
LOW: No need to upgrade unless there are new features you want to use.
|
||||
MODERATE: Program an upgrade of the server, but it's not urgent.
|
||||
HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
SECURITY: There are security fixes in the release.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
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.
|
||||
================================================================================
|
||||
Valkey 8.1.0 GA - Released Mon 31 March 2025
|
||||
================================================================================
|
||||
Upgrade urgency LOW: This is the first release of Valkey 8.1,
|
||||
a minor version update designed to further enhance performance, reliability, observability and usability
|
||||
over Valkey 8.0 for all Valkey installations. This release is fully compatible with all previous Valkey releases
|
||||
as well as Redis OSS 7.2.4.
|
||||
|
||||
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:
|
||||
Behavior Changes
|
||||
================
|
||||
* Hide input buffer data from being logged on protocol error when hide-user-data-from-log is enabled (#1889)
|
||||
|
||||
https://valkey.io/download/
|
||||
Bug fixes
|
||||
=========
|
||||
* Fix a bug in VM_GetCurrentUserName which leads to engine crash when no valid username provided (#1885)
|
||||
|
||||
More information is available at https://valkey.io
|
||||
================================================================================
|
||||
Valkey 8.1.0 RC2 - Released Thu 20 March 2025
|
||||
================================================================================
|
||||
Upgrade urgency LOW: This is the second release candidate of Valkey 8.1, with several bug fixes,
|
||||
control on manual-failover timeout and expended module API to reduce module executed commands overhead.
|
||||
|
||||
Happy hacking!
|
||||
Performance/Efficiency Improvements - Core
|
||||
==========================================
|
||||
* Optimize bitcount command by using x86 SIMD instructions (#1741)
|
||||
* Embed hash value in hash data type entries to reduce memory footprint (#1579)
|
||||
|
||||
Cluster modifications
|
||||
====================
|
||||
* Add cluster-manual-failover-timeout configuration to control the timeout for manual failover (#1690)
|
||||
* Improve error message reporting when invalid port is provided for cluster meet command. (#1686)
|
||||
* broadcast epoch ASAP when configEpoch changed (#1813)
|
||||
|
||||
Module Improvements
|
||||
===================
|
||||
* Add new module API flag to bypass command validation in order to reduce processing overhead (#1357)
|
||||
|
||||
Behavior Changes
|
||||
================
|
||||
* Enable TCP_NODELAY for engine initiated cluster and replication connections (#1763)
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
* Fix `ACL LOAD` crash on a connected replica node (#1842)
|
||||
* Fix bug where no tracking-redir-broken is issued when the redirect client is in the process of getting closed. (#1823)
|
||||
* Fix replica sometimes disconnecting when replication is using TLS. (#1737)
|
||||
* Fix file descriptor leak when aborting dual channel replication due to error (#1721)
|
||||
* Fix rax crash when using keys larger than 512MB (#1722)
|
||||
* Fix RANDOMKEY command leading to infinite loop during when all CLIENT are PAUSED and all keys are with expiry (#1850)
|
||||
* Removing unicode optimization in Lua cjson library to avoid OOM when very large strings are used. (#1785)
|
||||
* Fix update large-reply in COMMANDLOG when reply is deferred (#1760)
|
||||
* Avoid setting TCP/TLS specific options for UNIX Domain Socket connections (#1706)
|
||||
* Fix a bug in the valkey-cli which would incorrectly render commands with text output in multi/exec (#1782)
|
||||
|
||||
|
||||
Build and Packaging changes
|
||||
=================================
|
||||
* Check both arm64 and aarch64 for ARM based system architecture during CMake builds (#1829)
|
||||
* Cleanup lua object files on make distclean (#1812)
|
||||
* Fixed build error with CMake when using clang v19 (#1806)
|
||||
|
||||
================================================================================
|
||||
Valkey 8.1.0 RC1 - Released Thu 11 Feb 2025
|
||||
================================================================================
|
||||
Upgrade urgency LOW: This is the first release candidate of Valkey 8.1, with
|
||||
performance improvements, extended observability and cluster improvements and different bug fixes.
|
||||
It includes a new implementation of the Valkey dictionary which is more memory and cache efficient,
|
||||
better performance for encryption in transit, reduced replication overhead by offloading work to I/O threads,
|
||||
faster failover support in cluster mode, major improvements to the active defrag process to reduce the impact on command processing,
|
||||
different API changes for improved usability and ability to track large requests and replies.
|
||||
Valkey now supports new new check-and-set feature for native STRINGs.
|
||||
|
||||
API and Interface changes
|
||||
=========================
|
||||
* Introduce cancel argument to bgsave command (#757)
|
||||
* Add conditional update support to the `SET` command using `IFEQ` argument (#1324)
|
||||
* Add more filters to `CLIENT LIST` (#1401)
|
||||
* Add `availability_zone` to the HELLO response (#1487)
|
||||
|
||||
Observability and Monitoring changes
|
||||
====================================
|
||||
* Extend `LATENCY LATEST` to add sum / cnt stats (#1570)
|
||||
* Add `paused_actions` and `paused_timeout_milliseconds` for `INFO CLIENTS` (#1519)
|
||||
* Add paused_reason to `INFO CLIENTS` (#1564)
|
||||
* Added `COMMANDLOG` to record slow executions and large requests/replies (#1294)
|
||||
* Fix cluster info sent stats for message with light header (#1563)
|
||||
* Add latency stats around cluster config file operations (#1534)
|
||||
* Add new flag in `CLIENT LIST` for import-source client (#1398)
|
||||
* Show client capabilities in `CLIENT LIST` / `CLIENT INFO` (#1698)
|
||||
|
||||
Performance/Efficiency Improvements - Core
|
||||
==========================================
|
||||
* Introduce a new memory efficient hash table to store keys (#1186)
|
||||
* Accelerate hash table iterator with prefetching (#1501)
|
||||
* Accelerate hash table iterator with value prefetching (#1568)
|
||||
* Replace dict with new hashtable: hash datatype (#1502)
|
||||
* Replace dict with new hashtable for sets datatype (#1176)
|
||||
* Replace dict with new hashtable: sorted set datatype (#1427)
|
||||
* Free strings during BGSAVE/BGAOFRW to reduce copy-on-write (#905)
|
||||
* Create an empty lua table with specified initial capacity as much as possible (#1092)
|
||||
* Move prepareClientToWrite out of loop for HGETALL command (#1119)
|
||||
* Improved hashing algorithm for Lua tables (#1168)
|
||||
* Replace dict with new hashtable for sets datatype (#1176)
|
||||
* Do security attack check only when command not found to reduce the critical path. (#1212)
|
||||
* Trim free space from inline command argument strings to avoid excess memory usage (#1213)
|
||||
* Increase the max number of io threads to 256. (#1220)
|
||||
* Refactor of ActiveDefrag to reduce latencies (#1242)
|
||||
* Integrate fast_float to optionally replace strtod (#1260)
|
||||
* Improvements for TLS with I/O threads (#1271)
|
||||
* Optimize PFCOUNT, PFMERGE command by SIMD acceleration (#1293)
|
||||
* Optimize sdscatrepr by batch processing printable characters (#1342)
|
||||
* Optimize ZRANK to avoid path comparisons (#1389)
|
||||
* Move clientCron onto a separate timer (#1387)
|
||||
* Client struct: lazy init components and optimize struct layout (#1405)
|
||||
* Offload reading the replication stream to IO threads (#1449)
|
||||
* Skip CRC checksumming during diskless full sync with TLS enabled. (#1479)
|
||||
|
||||
New/Modified configurations
|
||||
===========================
|
||||
* Deprecate `io-threads-do-reads`, which has no effect since io threads will now always do reads. (#1138)
|
||||
* Introduce `import-mode` config to avoid expiration and eviction during data syncing (#1185)
|
||||
* Introduce new `rdb-version-check` config which allows for relaxed RDB version verification (#1604)
|
||||
* Deprecate `dynamic-hz`, since server cron jobs are handled dynamically by default (#1387)
|
||||
* Introduce `log-format` and `log-timestamp-format` to control the log format (#1022)
|
||||
* Introducing `active-defrag-cycle-us` for more fine-grinned control of memory defragmentation run time (#1242)
|
||||
* Introduce new configurations to control the new `COMMANDLOG` reporting thresholds (#1294)
|
||||
|
||||
Build and Packaging changes
|
||||
=================================
|
||||
* Introduce CMake build system for valkey (#1196)
|
||||
* RDMA builtin support (#1209)
|
||||
* Fix Valkey binary build workflow, version support changes. (#1429)
|
||||
* Remove Valkey specific changes in jemalloc source code (#1266)
|
||||
|
||||
Module Improvements
|
||||
===================
|
||||
* Add API UpdateRuntimeArgs for updating the module arguments during runtime (#1041)
|
||||
* Add support for MustObeyClient Module API (#1582)
|
||||
* Adds support for scripting engines as Valkey modules (#1277, #1497)
|
||||
|
||||
Cluster improvements
|
||||
====================
|
||||
* Do election in order based on failed primary rank to avoid voting conflicts (#1018)
|
||||
* Make replica `CLUSTER RESET` flush async based on `lazyfree-lazy-user-flush` (#1190)
|
||||
* Trigger the election as soon as possible when doing a forced manual failover (#1067)
|
||||
* Make manual failover reset the on-going election to promote failover (#1274)
|
||||
* Broadcast a PONG to all node in cluster when role changed (#1295)
|
||||
* Manual failover vote is not limited by two times the node timeout (#1305)
|
||||
* Automatic failover vote is not limited by two times the node timeout (#1356)
|
||||
|
||||
Behavior Changes
|
||||
================
|
||||
* Streams use an additional 8 bytes to track their internal size (#688)
|
||||
* Take hz into account in activerehashing to avoid CPU spikes (#977)
|
||||
* Incr `expired_keys` if the expiration time is already expired (#1517)
|
||||
* Fix replica not able to initiate election in time when epoch fails (#1009)
|
||||
* Make `FUNCTION RESTORE FLUSH` flush async based on `lazyfree-lazy-user-flush` (#1254)
|
||||
* Allow `MEMORY MALLOC-STATS` and `MEMORY PURGE` during loading phase (#1317)
|
||||
* Use `DEEPBIND` flag when loading external modules in order to avoid symbol conflicts (#1703)
|
||||
|
||||
|
||||
Logging and Tooling Improvements
|
||||
================================
|
||||
* Remove the restriction that cli --cluster create requires at least 3 primary nodes (#1075)
|
||||
* Add short client info log to CLUSTER MEET / FORGET / RESET commands (#1249)
|
||||
* Support for reading from replicas in valkey-benchmark (#1392)
|
||||
* valkey-cli will now re-select previously selected database after reconnect (#1694)
|
||||
* valkey-cli will now auto-exit from subscribed mode when there are no more active subscriptions (#1432)
|
||||
|
||||
Bug Fixes
|
||||
=========
|
||||
* Mark the node as FAIL when the node is marked as NOADDR and broadcast the FAIL (#1191)
|
||||
* [Bug Fix] Optimize RDB Load Performance and Fix Cluster Mode Resizing (#1199)
|
||||
* Log as primary role (M) instead of child process (C) during startup (#1282)
|
||||
* Fix empty primary may have dirty slots data due to bad migration (#1285)
|
||||
* RDMA: Fix dead loop when transfer large data (20KB) (#1386)
|
||||
|
||||
We appreciate the efforts of all who contributed code to this release!
|
||||
|
||||
|
||||
Alan Scherger (flyinprogrammer), Amit Nagler (naglera), Anastasia Alexandrova (nastena1606), Basel Naamna (xbasel), Ben Totten (bentotten), Benson-li (li-benson),
|
||||
Binbin (enjoy-binbin), Bogdan Petre (bogdanp05), chzhoo, Caiyi Wu (Codebells), Danish Mehmood (danish-mehmood), Eran Ifrah (eifrah-aws), Guillaume Koenig (knggk),
|
||||
Harkrishn Patro (hpatro), Jacob Murphy (murphyjacob4), jeon1226, Jim Brunner (JimB123), Josef Šimánek (simi), Jungwoo Song (bluayer), Karthick Ariyaratnam (karthyuom),
|
||||
Karthik Subbarao (KarthikSubbarao), kronwerk, Lipeng Zhu (lipzhu), Madelyn Olson (madolson), Marek Zoremba (zori-janea), Masahiro Ide (imasahiro), Meinhard Zhou (MeinhardZhou), Melroy van den Berg (melroy89), Mikhail Koviazin (mkmkme), Nadav Gigi (NadavGigi), Nadav Levanoni (nadav-levanoni), Nikhil Manglore (Nikhil-Manglore), Parth Patel (parthpatel), Pierre (pieturin), Ping Xie (PingXie), Qu Chen (QuChen88), Rain Valentine (SoftlyRaining), Ray Cao (RayaCoo), Ran Shidlansik (ranshid), Ray Cao (RayaCoo), Ricardo Dias (rjd15372), Romain Geissler (Romain-Geissler-1A), Roman Gershman (romange), Roshan Khatri (roshkhatri), Rueian (rueian), Sarthak Aggarwal (sarthakaggarwal97), Seungmin Lee (sungming2),
|
||||
Shai Zarka (zarkash-aws), Shivshankar (Shivshankar-Reddy), Simon Baatz (gmbnomis), Sinkevich Artem (ArtSin), Stav Ben-Tov (stav-bentov), Stefan Mueller (muelstefamzn), secwall,
|
||||
Tal Shachar (talxsha), Thalia Archibald (thaliaarchi), Uri Yagelnik (uriyage), Vadym Khoptynets (poiuj), Vanessa Tang (YueTang-Vanessa), Viktor Söderqvist (zuiderkwast), Viktor Szépe (szepeviktor), VoletiRam,
|
||||
Vu Diep (vudiep411), Wen Hui (hwware), WelongZuo, Yanqi Lv (lyq2333), Yury Fridlyand (Yury-Fridlyand), Zvi Schneider (zvi-code), bodong.ybd (yangbodong22011), chx9 (chx9), otheng (otheng03), skyfirelee (artikell), Shawn Wang (xingbowang), Xuyang WANG (Nugine), zhaozhao.zz (soloestoy), zhenwei pi (pizhenwei), zixuan zhao (azuredream), 烈香 (hengyoush),
|
||||
风去幽墨 (fengquyoumo)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# License 1
|
||||
|
||||
SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2024-present, Valkey contributors
|
||||
|
||||
@@ -240,7 +240,7 @@ endif ()
|
||||
set(BUILDING_ARM64 0)
|
||||
set(BUILDING_ARM32 0)
|
||||
|
||||
if ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64")
|
||||
if ("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "arm64" OR "${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "aarch64")
|
||||
set(BUILDING_ARM64 1)
|
||||
endif ()
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright Valkey Contributors.
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD 3-Clause
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "../fast_float/fast_float.h"
|
||||
|
||||
Vendored
+3
-3
@@ -81,9 +81,9 @@ uint32_t murmur32(const uint8_t* key, size_t len, uint32_t seed) {
|
||||
static const uint32_t n = 0xe6546b64;
|
||||
uint32_t hash = seed;
|
||||
|
||||
const int nblocks = len / 4;
|
||||
const size_t nblocks = len / 4;
|
||||
const uint32_t* blocks = (const uint32_t*) key;
|
||||
for (int i = 0; i < nblocks; i++) {
|
||||
for (size_t i = 0; i < nblocks; i++) {
|
||||
uint32_t k = blocks[i];
|
||||
k *= c1;
|
||||
k = (k << r1) | (k >> (32 - r1));
|
||||
@@ -116,7 +116,7 @@ uint32_t murmur32(const uint8_t* key, size_t len, uint32_t seed) {
|
||||
hash ^= (hash >> 16);
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
|
||||
GCObject *o;
|
||||
|
||||
Vendored
+4
-8
@@ -469,13 +469,9 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
|
||||
|
||||
str = lua_tolstring(l, lindex, &len);
|
||||
|
||||
/* Worst case is len * 6 (all unicode escapes).
|
||||
* This buffer is reused constantly for small strings
|
||||
* If there are any excess pages, they won't be hit anyway.
|
||||
* This gains ~5% speedup. */
|
||||
if (len > SIZE_MAX / 6 - 3)
|
||||
if (len > SIZE_MAX - 3)
|
||||
abort(); /* Overflow check */
|
||||
strbuf_ensure_empty_length(json, len * 6 + 2);
|
||||
strbuf_ensure_empty_length(json, len + 2);
|
||||
|
||||
strbuf_append_char_unsafe(json, '\"');
|
||||
for (i = 0; i < len; i++) {
|
||||
@@ -483,9 +479,9 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex)
|
||||
if (escstr)
|
||||
strbuf_append_string(json, escstr);
|
||||
else
|
||||
strbuf_append_char_unsafe(json, str[i]);
|
||||
strbuf_append_char(json, str[i]);
|
||||
}
|
||||
strbuf_append_char_unsafe(json, '\"');
|
||||
strbuf_append_char(json, '\"');
|
||||
}
|
||||
|
||||
/* Find the size of the array on the top of the Lua stack
|
||||
|
||||
+1
-1
@@ -563,7 +563,7 @@ endif
|
||||
commands.c: $(COMMANDS_DEF_FILENAME).def
|
||||
|
||||
clean:
|
||||
rm -rf $(SERVER_NAME) $(ENGINE_SENTINEL_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CHECK_AOF_NAME) $(ENGINE_UNIT_TESTS) $(ENGINE_LIB_NAME) unit/*.o unit/*.d *.o *.gcda *.gcno *.gcov valkey.info lcov-html Makefile.dep *.so
|
||||
rm -rf $(SERVER_NAME) $(ENGINE_SENTINEL_NAME) $(ENGINE_CLI_NAME) $(ENGINE_BENCHMARK_NAME) $(ENGINE_CHECK_RDB_NAME) $(ENGINE_CHECK_AOF_NAME) $(ENGINE_UNIT_TESTS) $(ENGINE_LIB_NAME) unit/*.o unit/*.d lua/*.o lua/*.d *.o *.gcda *.gcno *.gcov valkey.info lcov-html Makefile.dep *.so
|
||||
rm -f $(DEP)
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
@@ -2377,6 +2377,9 @@ static sds ACLLoadFromFile(const char *filename) {
|
||||
listRewind(server.clients, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *c = listNodeValue(ln);
|
||||
/* Some clients, e.g. the one from the primary to replica, don't have a user
|
||||
* associated with them. */
|
||||
if (!c->user) continue;
|
||||
user *original = c->user;
|
||||
list *channels = NULL;
|
||||
user *new_user = ACLGetUserByName(c->user->name, sdslen(c->user->name));
|
||||
|
||||
+15
-4
@@ -31,6 +31,8 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "adlist.h"
|
||||
|
||||
#include "serverassert.h"
|
||||
#include "zmalloc.h"
|
||||
|
||||
/* Create a new list. The created list can be freed with
|
||||
@@ -187,14 +189,23 @@ void listDelNode(list *list, listNode *node) {
|
||||
* Remove the specified node from the list without freeing it.
|
||||
*/
|
||||
void listUnlinkNode(list *list, listNode *node) {
|
||||
if (node->prev)
|
||||
assert(list->len > 0);
|
||||
|
||||
if (node->prev) {
|
||||
assert(node->prev->next == node);
|
||||
node->prev->next = node->next;
|
||||
else
|
||||
} else {
|
||||
assert(list->head == node);
|
||||
list->head = node->next;
|
||||
if (node->next)
|
||||
}
|
||||
|
||||
if (node->next) {
|
||||
assert(node->next->prev == node);
|
||||
node->next->prev = node->prev;
|
||||
else
|
||||
} else {
|
||||
assert(list->tail == node);
|
||||
list->tail = node->prev;
|
||||
}
|
||||
|
||||
node->next = NULL;
|
||||
node->prev = NULL;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* Copyright 2024- Valkey contributors
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file implements allocator-specific defragmentation logic used
|
||||
* within the Valkey engine. Below is the relationship between various
|
||||
|
||||
@@ -496,6 +496,9 @@ static int anetTcpGenericConnect(char *err, const char *addr, int port, const ch
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Enable TCP_NODELAY by default */
|
||||
anetEnableTcpNoDelay(NULL, s);
|
||||
|
||||
/* If we ended an iteration of the for loop without errors, we
|
||||
* have a connected socket. Let's return to the caller. */
|
||||
goto end;
|
||||
@@ -684,6 +687,10 @@ int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port)
|
||||
if (ip) inet_ntop(AF_INET6, (void *)&(s->sin6_addr), ip, ip_len);
|
||||
if (port) *port = ntohs(s->sin6_port);
|
||||
}
|
||||
|
||||
/* Enable TCP_NODELAY by default */
|
||||
anetEnableTcpNoDelay(NULL, fd);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2012, Redis Ltd.
|
||||
* Copyright (c) 2024, Valkey contributors
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -27,7 +26,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
const char *ascii_logo =
|
||||
" .+^+. \n"
|
||||
" .+#########+. \n"
|
||||
|
||||
+117
-13
@@ -29,26 +29,116 @@
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
#ifdef HAVE_AVX2
|
||||
/* Define __MM_MALLOC_H to prevent importing the memory aligned
|
||||
* allocation functions, which we don't use. */
|
||||
#define __MM_MALLOC_H
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Helpers and low level bit functions.
|
||||
* -------------------------------------------------------------------------- */
|
||||
|
||||
/* Count number of bits set in the binary array pointed by 's' and long
|
||||
* 'count' bytes. The implementation of this function is required to
|
||||
* work with an input string length up to 512 MB or more (server.proto_max_bulk_len) */
|
||||
long long serverPopcount(void *s, long count) {
|
||||
static const unsigned char bitsinbyte[256] = {
|
||||
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2,
|
||||
3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3,
|
||||
3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5,
|
||||
6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4,
|
||||
3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4,
|
||||
5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6,
|
||||
6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
|
||||
|
||||
#ifdef HAVE_AVX2
|
||||
/* The SIMD version of popcount enhances performance through parallel lookup tables which is based on the following article:
|
||||
* https://arxiv.org/pdf/1611.07612 */
|
||||
ATTRIBUTE_TARGET_AVX2
|
||||
long long popcountAVX2(void *s, long count) {
|
||||
long i = 0;
|
||||
unsigned char *p = (unsigned char *)s;
|
||||
long long bits = 0;
|
||||
|
||||
/* clang-format off */
|
||||
const __m256i lookup = _mm256_setr_epi8(
|
||||
/* First Lane [0:127] */
|
||||
/* 0 */ 0, /* 1 */ 1, /* 2 */ 1, /* 3 */ 2,
|
||||
/* 4 */ 1, /* 5 */ 2, /* 6 */ 2, /* 7 */ 3,
|
||||
/* 8 */ 1, /* 9 */ 2, /* a */ 2, /* b */ 3,
|
||||
/* c */ 2, /* d */ 3, /* e */ 3, /* f */ 4,
|
||||
|
||||
/* Second Lane [128:255] identical to first lane due to lane isolation in _mm256_shuffle_epi8.
|
||||
* For more information, see following URL
|
||||
* https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm256_shuffle_epi8 */
|
||||
/* 0 */ 0, /* 1 */ 1, /* 2 */ 1, /* 3 */ 2,
|
||||
/* 4 */ 1, /* 5 */ 2, /* 6 */ 2, /* 7 */ 3,
|
||||
/* 8 */ 1, /* 9 */ 2, /* a */ 2, /* b */ 3,
|
||||
/* c */ 2, /* d */ 3, /* e */ 3, /* f */ 4);
|
||||
/* clang-format on */
|
||||
const __m256i low_mask = _mm256_set1_epi8(0x0f);
|
||||
__m256i acc = _mm256_setzero_si256();
|
||||
|
||||
/* Count 32 bytes per iteration. */
|
||||
#define ITER_32_BYTES \
|
||||
{ \
|
||||
const __m256i vec = _mm256_loadu_si256((const __m256i *)(p + i)); \
|
||||
const __m256i lo = _mm256_and_si256(vec, low_mask); \
|
||||
const __m256i hi = _mm256_and_si256(_mm256_srli_epi16(vec, 4), low_mask); \
|
||||
const __m256i popcnt1 = _mm256_shuffle_epi8(lookup, lo); \
|
||||
const __m256i popcnt2 = _mm256_shuffle_epi8(lookup, hi); \
|
||||
local = _mm256_add_epi8(local, popcnt1); \
|
||||
local = _mm256_add_epi8(local, popcnt2); \
|
||||
i += 32; \
|
||||
}
|
||||
|
||||
/* We divide the array into the following three parts
|
||||
* Part A Part B Part C
|
||||
* +-----------------+--------------+---------+
|
||||
* | 8 * 32bytes * X | 32bytes * Y | Z bytes |
|
||||
* +-----------------+--------------+---------+
|
||||
*/
|
||||
|
||||
/* Part A: loop unrolling, processing 8 * 32 bytes per iteration. */
|
||||
while (i + 8 * 32 <= count) {
|
||||
__m256i local = _mm256_setzero_si256();
|
||||
ITER_32_BYTES
|
||||
ITER_32_BYTES
|
||||
ITER_32_BYTES
|
||||
ITER_32_BYTES
|
||||
ITER_32_BYTES
|
||||
ITER_32_BYTES
|
||||
ITER_32_BYTES
|
||||
ITER_32_BYTES
|
||||
acc = _mm256_add_epi64(acc, _mm256_sad_epu8(local, _mm256_setzero_si256()));
|
||||
}
|
||||
|
||||
/* Part B: when the remaining data length is less than 8 * 32 bytes,
|
||||
* process 32 bytes per iteration. */
|
||||
__m256i local = _mm256_setzero_si256();
|
||||
while (i + 32 <= count) {
|
||||
ITER_32_BYTES;
|
||||
}
|
||||
acc = _mm256_add_epi64(acc, _mm256_sad_epu8(local, _mm256_setzero_si256()));
|
||||
|
||||
#undef ITER_32_BYTES
|
||||
|
||||
bits += _mm256_extract_epi64(acc, 0);
|
||||
bits += _mm256_extract_epi64(acc, 1);
|
||||
bits += _mm256_extract_epi64(acc, 2);
|
||||
bits += _mm256_extract_epi64(acc, 3);
|
||||
|
||||
/* Part C: count the remaining bytes. */
|
||||
for (; i < count; i++) {
|
||||
bits += bitsinbyte[p[i]];
|
||||
}
|
||||
|
||||
return bits;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* The scalar version of popcount based on lookup tables. */
|
||||
long long popcountScalar(void *s, long count) {
|
||||
long long bits = 0;
|
||||
unsigned char *p = s;
|
||||
uint32_t *p4;
|
||||
static const unsigned char bitsinbyte[256] = {
|
||||
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 1, 2, 2, 3, 2,
|
||||
3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3,
|
||||
3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5,
|
||||
6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4,
|
||||
3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4,
|
||||
5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6,
|
||||
6, 7, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
|
||||
|
||||
/* Count initial bytes not aligned to 32 bit. */
|
||||
while ((unsigned long)p & 3 && count) {
|
||||
@@ -97,6 +187,20 @@ long long serverPopcount(void *s, long count) {
|
||||
return bits;
|
||||
}
|
||||
|
||||
/* Count number of bits set in the binary array pointed by 's' and long
|
||||
* 'count' bytes. The implementation of this function is required to
|
||||
* work with an input string length up to 512 MB or more (server.proto_max_bulk_len) */
|
||||
long long serverPopcount(void *s, long count) {
|
||||
#ifdef HAVE_AVX2
|
||||
/* If length of s >= 256 bits and the CPU supports AVX2,
|
||||
* we prefer to use the SIMD version */
|
||||
if (count >= 32) {
|
||||
return popcountAVX2(s, count);
|
||||
}
|
||||
#endif
|
||||
return popcountScalar(s, count);
|
||||
}
|
||||
|
||||
/* Return the position of the first bit set to one (if 'bit' is 1) or
|
||||
* zero (if 'bit' is 0) in the bitmap starting at 's' and long 'count' bytes.
|
||||
*
|
||||
|
||||
+7
-3
@@ -26,9 +26,13 @@
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* ---------------------------------------------------------------------------
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
/*
|
||||
* API:
|
||||
*
|
||||
* blockClient() set the CLIENT_BLOCKED flag in the client, and set the
|
||||
|
||||
+26
-12
@@ -303,12 +303,12 @@ static sds percentDecode(const char *pe, size_t len) {
|
||||
/* Parse a URI and extract the server connection information.
|
||||
* URI scheme is based on the provisional specification[1] excluding support
|
||||
* for query parameters. Valid URIs are:
|
||||
* scheme: "valkey://"
|
||||
* scheme: "valkey://" or "valkeys://" or "redis://" or "rediss://"
|
||||
* authority: [[<username> ":"] <password> "@"] [<hostname> [":" <port>]]
|
||||
* path: ["/" [<db>]]
|
||||
*
|
||||
* [1]: https://www.iana.org/assignments/uri-schemes/prov/redis */
|
||||
void parseRedisUri(const char *uri, const char *tool_name, cliConnInfo *connInfo, int *tls_flag) {
|
||||
void parseUri(const char *uri, const char *tool_name, cliConnInfo *connInfo, int *tls_flag) {
|
||||
#ifdef USE_OPENSSL
|
||||
UNUSED(tool_name);
|
||||
#else
|
||||
@@ -428,19 +428,33 @@ sds cliVersion(void) {
|
||||
}
|
||||
|
||||
/* This is a wrapper to call redisConnect or redisConnectWithTimeout. */
|
||||
redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv) {
|
||||
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
|
||||
return redisConnect(ip, port);
|
||||
} else {
|
||||
return redisConnectWithTimeout(ip, port, tv);
|
||||
redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv, int nonblock) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_TCP(&options, ip, port);
|
||||
|
||||
if (tv.tv_sec || tv.tv_usec) {
|
||||
options.connect_timeout = &tv;
|
||||
}
|
||||
|
||||
if (nonblock) {
|
||||
options.options |= REDIS_OPT_NONBLOCK;
|
||||
}
|
||||
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
/* This is a wrapper to call redisConnectUnix or redisConnectUnixWithTimeout. */
|
||||
redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv) {
|
||||
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
|
||||
return redisConnectUnix(path);
|
||||
} else {
|
||||
return redisConnectUnixWithTimeout(path, tv);
|
||||
redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv, int nonblock) {
|
||||
redisOptions options = {0};
|
||||
REDIS_OPTIONS_SET_UNIX(&options, path);
|
||||
|
||||
if (tv.tv_sec || tv.tv_usec) {
|
||||
options.connect_timeout = &tv;
|
||||
}
|
||||
|
||||
if (nonblock) {
|
||||
options.options |= REDIS_OPT_NONBLOCK;
|
||||
}
|
||||
|
||||
return redisConnectWithOptions(&options);
|
||||
}
|
||||
|
||||
+3
-3
@@ -45,7 +45,7 @@ sds *getSdsArrayFromArgv(int argc, char **argv, int quoted);
|
||||
|
||||
sds unquoteCString(char *str);
|
||||
|
||||
void parseRedisUri(const char *uri, const char *tool_name, cliConnInfo *connInfo, int *tls_flag);
|
||||
void parseUri(const char *uri, const char *tool_name, cliConnInfo *connInfo, int *tls_flag);
|
||||
|
||||
void freeCliConnInfo(cliConnInfo connInfo);
|
||||
|
||||
@@ -53,7 +53,7 @@ sds escapeJsonString(sds s, const char *p, size_t len);
|
||||
|
||||
sds cliVersion(void);
|
||||
|
||||
redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv);
|
||||
redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv);
|
||||
redisContext *redisConnectWrapper(const char *ip, int port, const struct timeval tv, int nonblock);
|
||||
redisContext *redisConnectUnixWrapper(const char *path, const struct timeval tv, int nonblock);
|
||||
|
||||
#endif /* __CLICOMMON_H */
|
||||
|
||||
+11
-9
@@ -26,7 +26,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
/*
|
||||
* cluster.c contains the common parts of a clustering
|
||||
* implementation, the parts that are shared between
|
||||
@@ -357,7 +361,6 @@ migrateCachedSocket *migrateGetSocket(client *c, robj *host, robj *port, long ti
|
||||
sdsfree(name);
|
||||
return NULL;
|
||||
}
|
||||
connEnableTcpNoDelay(conn);
|
||||
|
||||
/* Add to the cache and return it to the caller. */
|
||||
cs = zmalloc(sizeof(*cs));
|
||||
@@ -1341,16 +1344,15 @@ void addNodeToNodeReply(client *c, clusterNode *node) {
|
||||
* not finished their initial sync, in failed state, or are
|
||||
* otherwise considered not available to serve read commands. */
|
||||
int isNodeAvailable(clusterNode *node) {
|
||||
/* We don't consider PFAIL here because it's not a reliable indicator
|
||||
* for node available and we don't want clients to use it. */
|
||||
if (clusterNodeIsFailing(node)) {
|
||||
return 0;
|
||||
}
|
||||
long long repl_offset = clusterNodeReplOffset(node);
|
||||
if (clusterNodeIsMyself(node)) {
|
||||
/* Nodes do not update their own information
|
||||
* in the cluster node list. */
|
||||
repl_offset = getNodeReplicationOffset(node);
|
||||
}
|
||||
return (repl_offset != 0);
|
||||
|
||||
/* Hide empty replicas in here, from a data-path POV, an empty replica
|
||||
* is not available. */
|
||||
return getNodeReplicationOffset(node) != 0;
|
||||
}
|
||||
|
||||
void addNodeReplyForClusterSlot(client *c, clusterNode *node, int start_slot, int end_slot) {
|
||||
|
||||
+1
-2
@@ -77,7 +77,7 @@ const char **clusterCommandExtendedHelp(void);
|
||||
|
||||
int clusterAllowFailoverCmd(client *c);
|
||||
void clusterPromoteSelfToPrimary(void);
|
||||
int clusterManualFailoverTimeLimit(void);
|
||||
mstime_t clusterManualFailoverTimeLimit(void);
|
||||
|
||||
void clusterCommandSlots(client *c);
|
||||
void clusterCommandMyId(client *c);
|
||||
@@ -109,7 +109,6 @@ clusterNode *getNodeBySlot(int slot);
|
||||
int clusterNodeClientPort(clusterNode *n, int use_tls);
|
||||
char *clusterNodeHostname(clusterNode *node);
|
||||
const char *clusterNodePreferredEndpoint(clusterNode *n, client *c);
|
||||
long long clusterNodeReplOffset(clusterNode *node);
|
||||
clusterNode *clusterLookupNode(const char *name, int length);
|
||||
int detectAndUpdateCachedNodeHealth(void);
|
||||
client *createCachedResponseClient(int resp);
|
||||
|
||||
+38
-26
@@ -26,7 +26,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
/*
|
||||
* cluster_legacy.c contains the implementation of the cluster API that is
|
||||
* specific to the standard, cluster-bus based clustering mechanism.
|
||||
@@ -1493,7 +1497,7 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
connClose(conn);
|
||||
return;
|
||||
}
|
||||
connEnableTcpNoDelay(conn);
|
||||
|
||||
connKeepAlive(conn, server.cluster_node_timeout / 1000 * 2);
|
||||
|
||||
/* Use non-blocking I/O for cluster messages. */
|
||||
@@ -1934,7 +1938,7 @@ int clusterBumpConfigEpochWithoutConsensus(void) {
|
||||
if (myself->configEpoch == 0 || myself->configEpoch != maxEpoch) {
|
||||
server.cluster->currentEpoch++;
|
||||
myself->configEpoch = server.cluster->currentEpoch;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_FSYNC_CONFIG);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_FSYNC_CONFIG | CLUSTER_TODO_BROADCAST_ALL);
|
||||
serverLog(LL_NOTICE, "New configEpoch set to %llu", (unsigned long long)myself->configEpoch);
|
||||
return C_OK;
|
||||
} else {
|
||||
@@ -1997,7 +2001,7 @@ void clusterHandleConfigEpochCollision(clusterNode *sender) {
|
||||
/* Get the next ID available at the best of this node knowledge. */
|
||||
server.cluster->currentEpoch++;
|
||||
myself->configEpoch = server.cluster->currentEpoch;
|
||||
clusterSaveConfigOrDie(1);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_FSYNC_CONFIG | CLUSTER_TODO_BROADCAST_ALL);
|
||||
serverLog(LL_NOTICE, "configEpoch collision with node %.40s (%s). configEpoch set to %llu", sender->name,
|
||||
sender->human_nodename, (unsigned long long)myself->configEpoch);
|
||||
}
|
||||
@@ -2197,12 +2201,6 @@ static int clusterStartHandshake(char *ip, int port, int cport) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Port sanity check */
|
||||
if (port <= 0 || port > 65535 || cport <= 0 || cport > 65535) {
|
||||
errno = EINVAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set norm_ip as the normalized string representation of the node
|
||||
* IP address. */
|
||||
memset(norm_ip, 0, NET_IP_STR_LEN);
|
||||
@@ -3045,7 +3043,10 @@ static void clusterProcessModulePacket(clusterMsgModule *module_data, clusterNod
|
||||
uint32_t len = ntohl(module_data->len);
|
||||
uint8_t type = module_data->type;
|
||||
unsigned char *payload = module_data->bulk_data;
|
||||
moduleCallClusterReceivers(sender->name, module_id, type, payload, len);
|
||||
|
||||
sds sender_name = sdsnewlen(sender->name, CLUSTER_NAMELEN);
|
||||
moduleCallClusterReceivers(sender_name, module_id, type, payload, len);
|
||||
sdsfree(sender_name);
|
||||
}
|
||||
|
||||
static void clusterProcessLightPacket(clusterNode *sender, clusterLink *link, uint16_t type) {
|
||||
@@ -3212,7 +3213,8 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
freeClusterLink(link);
|
||||
serverLog(
|
||||
LL_NOTICE,
|
||||
"Closing link for node that sent a lightweight message of type %s as its first message on the link",
|
||||
"Closing link for node %.40s that sent a lightweight message of type %s as its first message on the link",
|
||||
hdr->sender,
|
||||
clusterGetMessageTypeString(type));
|
||||
return 0;
|
||||
}
|
||||
@@ -3716,9 +3718,9 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
/* Manual failover requested from replicas. Initialize the state
|
||||
* accordingly. */
|
||||
resetManualFailover();
|
||||
server.cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
|
||||
server.cluster->mf_end = now + server.cluster_mf_timeout;
|
||||
server.cluster->mf_replica = sender;
|
||||
pauseActions(PAUSE_DURING_FAILOVER, now + (CLUSTER_MF_TIMEOUT * CLUSTER_MF_PAUSE_MULT),
|
||||
pauseActions(PAUSE_DURING_FAILOVER, now + (server.cluster_mf_timeout * CLUSTER_MF_PAUSE_MULT),
|
||||
PAUSE_ACTIONS_CLIENT_WRITE_SET);
|
||||
serverLog(LL_NOTICE, "Manual failover requested by replica %.40s (%s).", sender->name, sender->human_nodename);
|
||||
/* We need to send a ping message to the replica, as it would carry
|
||||
@@ -4773,13 +4775,13 @@ void clusterFailoverReplaceYourPrimary(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 3) Update state and save config. */
|
||||
/* 3) Update state, note that we do not use CLUSTER_TODO_UPDATE_STATE since we want the node
|
||||
* to update the cluster state ASAP after failover. */
|
||||
clusterUpdateState();
|
||||
clusterSaveConfigOrDie(1);
|
||||
|
||||
/* 4) Pong all the other nodes so that they can update the state
|
||||
/* 4) Save and fsync the config, and pong all the other nodes so that they can update the state
|
||||
* accordingly and detect that we switched to primary role. */
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_BROADCAST_ALL);
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_FSYNC_CONFIG | CLUSTER_TODO_BROADCAST_ALL);
|
||||
|
||||
/* 5) If there was a manual failover in progress, clear the state. */
|
||||
resetManualFailover();
|
||||
@@ -4962,6 +4964,7 @@ void clusterHandleReplicaFailover(void) {
|
||||
/* Update my configEpoch to the epoch of the election. */
|
||||
if (myself->configEpoch < server.cluster->failover_auth_epoch) {
|
||||
myself->configEpoch = server.cluster->failover_auth_epoch;
|
||||
clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG | CLUSTER_TODO_FSYNC_CONFIG | CLUSTER_TODO_BROADCAST_ALL);
|
||||
serverLog(LL_NOTICE, "configEpoch set to %llu after successful failover",
|
||||
(unsigned long long)myself->configEpoch);
|
||||
}
|
||||
@@ -5089,7 +5092,7 @@ void clusterHandleReplicaMigration(int max_replicas) {
|
||||
* setting mf_end to the millisecond unix time at which we'll abort the
|
||||
* attempt.
|
||||
* 2) Replica sends a MFSTART message to the primary requesting to pause clients
|
||||
* for two times the manual failover timeout CLUSTER_MF_TIMEOUT.
|
||||
* for CLUSTER_MF_PAUSE_MULT times the server.cluster_mf_timeout.
|
||||
* When primary is paused for manual failover, it also starts to flag
|
||||
* packets with CLUSTERMSG_FLAG0_PAUSED.
|
||||
* 3) Replica waits for primary to send its replication offset flagged as PAUSED.
|
||||
@@ -6219,7 +6222,7 @@ int checkSlotAssignmentsOrReply(client *c, unsigned char *slots, int del, int st
|
||||
return C_ERR;
|
||||
}
|
||||
if (slots[slot]++ == 1) {
|
||||
addReplyErrorFormat(c, "Slot %d specified multiple times", (int)slot);
|
||||
addReplyErrorFormat(c, "Slot %d specified multiple times", slot);
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
@@ -6242,6 +6245,10 @@ void clusterUpdateSlots(client *c, unsigned char *slots, int del) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the replication offset of a node.
|
||||
*
|
||||
* Nodes do not update their own information in the cluster state,
|
||||
* so for self node, we cannot use `node->repl_offset` directly. */
|
||||
long long getNodeReplicationOffset(clusterNode *node) {
|
||||
if (node->flags & CLUSTER_NODE_MYSELF) {
|
||||
return nodeIsReplica(node) ? replicationGetReplicaOffset() : server.primary_repl_offset;
|
||||
@@ -6471,7 +6478,7 @@ clusterNode *getMyClusterNode(void) {
|
||||
return server.cluster->myself;
|
||||
}
|
||||
|
||||
int clusterManualFailoverTimeLimit(void) {
|
||||
mstime_t clusterManualFailoverTimeLimit(void) {
|
||||
return server.cluster->mf_end;
|
||||
}
|
||||
|
||||
@@ -6894,6 +6901,10 @@ int clusterCommandSpecial(client *c) {
|
||||
addReplyErrorFormat(c, "Invalid base port specified: %s", (char *)c->argv[3]->ptr);
|
||||
return 1;
|
||||
}
|
||||
if (port <= 0 || port > 65535) {
|
||||
addReplyErrorFormat(c, "Port number is out of range");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (c->argc == 5) {
|
||||
if (getLongLongFromObject(c->argv[4], &cport) != C_OK) {
|
||||
@@ -6904,6 +6915,11 @@ int clusterCommandSpecial(client *c) {
|
||||
cport = port + CLUSTER_PORT_INCR;
|
||||
}
|
||||
|
||||
if (cport <= 0 || cport > 65535) {
|
||||
addReplyErrorFormat(c, "Cluster bus port number is out of range");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (clusterStartHandshake(c->argv[2]->ptr, port, cport) == 0 && errno == EINVAL) {
|
||||
addReplyErrorFormat(c, "Invalid node address specified: %s:%s", (char *)c->argv[2]->ptr,
|
||||
(char *)c->argv[3]->ptr);
|
||||
@@ -7116,7 +7132,7 @@ int clusterCommandSpecial(client *c) {
|
||||
return 1;
|
||||
}
|
||||
resetManualFailover();
|
||||
server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
|
||||
server.cluster->mf_end = mstime() + server.cluster_mf_timeout;
|
||||
sds client = catClientInfoShortString(sdsempty(), c, server.hide_user_data_from_log);
|
||||
|
||||
if (takeover) {
|
||||
@@ -7279,10 +7295,6 @@ char *clusterNodeHostname(clusterNode *node) {
|
||||
return node->hostname;
|
||||
}
|
||||
|
||||
long long clusterNodeReplOffset(clusterNode *node) {
|
||||
return node->repl_offset;
|
||||
}
|
||||
|
||||
const char *clusterNodePreferredEndpoint(clusterNode *n, client *c) {
|
||||
char *hostname = clusterNodeHostname(n);
|
||||
switch (server.cluster_preferred_endpoint_type) {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
* multiplicators of the node timeout value (when ending with MULT). */
|
||||
#define CLUSTER_FAIL_REPORT_VALIDITY_MULT 2 /* Fail report validity. */
|
||||
#define CLUSTER_FAIL_UNDO_TIME_MULT 2 /* Undo fail if primary is back. */
|
||||
#define CLUSTER_MF_TIMEOUT 5000 /* Milliseconds to do a manual failover. */
|
||||
#define CLUSTER_MF_PAUSE_MULT 2 /* Primary pause manual failover mult. */
|
||||
#define CLUSTER_REPLICA_MIGRATION_DELAY 5000 /* Delay for replica migration. */
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/*
|
||||
* Copyright Valkey Contributors.
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD 3-Clause
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "cluster_slot_stats.h"
|
||||
|
||||
#define UNASSIGNED_SLOT 0
|
||||
|
||||
+6
-4
@@ -1,3 +1,9 @@
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
/* Commandlog implements a system that is able to remember the latest N
|
||||
* queries that took more than M microseconds to execute, or consumed
|
||||
* too much network bandwidth and memory for input/output buffers.
|
||||
@@ -14,10 +20,6 @@
|
||||
* but is accessible thanks to the COMMANDLOG command.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright Valkey Contributors.
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD 3-Clause
|
||||
*/
|
||||
|
||||
#include "commandlog.h"
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"const": 1
|
||||
},
|
||||
{
|
||||
"description": "Source was not copied.",
|
||||
"description": "Source was not copied when the destination key already exists",
|
||||
"const": 0
|
||||
}
|
||||
]
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"const": 1
|
||||
},
|
||||
{
|
||||
"description": "Key wasn't moved.",
|
||||
"description": "Key wasn't moved. When key already exists in the destination database, or it does not exist in the source database",
|
||||
"const": 0
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3325,6 +3325,7 @@ standardConfig static_configs[] = {
|
||||
createLongLongConfig("proto-max-bulk-len", NULL, DEBUG_CONFIG | MODIFIABLE_CONFIG, 1024 * 1024, LONG_MAX, server.proto_max_bulk_len, 512ll * 1024 * 1024, MEMORY_CONFIG, NULL, NULL), /* Bulk request max size */
|
||||
createLongLongConfig("stream-node-max-entries", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, server.stream_node_max_entries, 100, INTEGER_CONFIG, NULL, NULL),
|
||||
createLongLongConfig("repl-backlog-size", NULL, MODIFIABLE_CONFIG, 1, LLONG_MAX, server.repl_backlog_size, 10 * 1024 * 1024, MEMORY_CONFIG, NULL, updateReplBacklogSize), /* Default: 10mb */
|
||||
createLongLongConfig("cluster-manual-failover-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, server.cluster_mf_timeout, 5000, INTEGER_CONFIG, NULL, NULL),
|
||||
|
||||
/* Unsigned Long Long configs */
|
||||
createULongLongConfig("maxmemory", NULL, MODIFIABLE_CONFIG, 0, ULLONG_MAX, server.maxmemory, 0, MEMORY_CONFIG, NULL, updateMaxmemory),
|
||||
|
||||
@@ -380,8 +380,6 @@ static inline const char *connGetInfo(connection *conn, char *buf, size_t buf_le
|
||||
/* anet-style wrappers to conns */
|
||||
int connBlock(connection *conn);
|
||||
int connNonBlock(connection *conn);
|
||||
int connEnableTcpNoDelay(connection *conn);
|
||||
int connDisableTcpNoDelay(connection *conn);
|
||||
int connKeepAlive(connection *conn, int interval);
|
||||
int connSendTimeout(connection *conn, long long ms);
|
||||
int connRecvTimeout(connection *conn, long long ms);
|
||||
|
||||
@@ -445,7 +445,7 @@ robj *dbRandomKey(serverDb *db) {
|
||||
sds key = objectGetKey(valkey);
|
||||
robj *keyobj = createStringObject(key, sdslen(key));
|
||||
if (objectIsExpired(valkey)) {
|
||||
if (allvolatile && (server.primary_host || server.import_mode) && --maxtries == 0) {
|
||||
if (allvolatile && (server.primary_host || server.import_mode || isPausedActions(PAUSE_ACTION_EXPIRE)) && --maxtries == 0) {
|
||||
/* If the DB is composed only of keys with an expire set,
|
||||
* it could happen that all the keys are already logically
|
||||
* expired in the replica, so the function cannot stop because
|
||||
|
||||
+21
-18
@@ -32,12 +32,18 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "hashtable.h"
|
||||
#include "eval.h"
|
||||
#include "script.h"
|
||||
#include "module.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef HAVE_DEFRAG
|
||||
@@ -346,7 +352,7 @@ static void activeDefragSdsDict(dict *d, int val_type) {
|
||||
} while (cursor != 0);
|
||||
}
|
||||
|
||||
void activeDefragSdsHashtableCallback(void *privdata, void *entry_ref) {
|
||||
static void activeDefragSdsHashtableCallback(void *privdata, void *entry_ref) {
|
||||
UNUSED(privdata);
|
||||
sds *sds_ref = (sds *)entry_ref;
|
||||
sds new_sds = activeDefragSds(*sds_ref);
|
||||
@@ -398,7 +404,7 @@ static long scanLaterList(robj *ob, unsigned long *cursor, monotime endtime) {
|
||||
quicklistNode *node;
|
||||
long iterations = 0;
|
||||
int bookmark_failed = 0;
|
||||
if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST) return 0;
|
||||
serverAssert(ob->type == OBJ_LIST && ob->encoding == OBJ_ENCODING_QUICKLIST);
|
||||
|
||||
if (*cursor == 0) {
|
||||
/* if cursor is 0, we start new iteration */
|
||||
@@ -441,7 +447,7 @@ static void scanLaterZsetCallback(void *privdata, void *element_ref) {
|
||||
}
|
||||
|
||||
static void scanLaterZset(robj *ob, unsigned long *cursor) {
|
||||
if (ob->type != OBJ_ZSET || ob->encoding != OBJ_ENCODING_SKIPLIST) return;
|
||||
serverAssert(ob->type == OBJ_ZSET && ob->encoding == OBJ_ENCODING_SKIPLIST);
|
||||
zset *zs = (zset *)ob->ptr;
|
||||
*cursor = hashtableScanDefrag(zs->ht, *cursor, scanLaterZsetCallback, zs->zsl, activeDefragAlloc, HASHTABLE_SCAN_EMIT_REF);
|
||||
}
|
||||
@@ -455,7 +461,7 @@ static void scanHashtableCallbackCountScanned(void *privdata, void *elemref) {
|
||||
}
|
||||
|
||||
static void scanLaterSet(robj *ob, unsigned long *cursor) {
|
||||
if (ob->type != OBJ_SET || ob->encoding != OBJ_ENCODING_HASHTABLE) return;
|
||||
serverAssert(ob->type == OBJ_SET && ob->encoding == OBJ_ENCODING_HASHTABLE);
|
||||
hashtable *ht = ob->ptr;
|
||||
*cursor = hashtableScanDefrag(ht, *cursor, activeDefragSdsHashtableCallback, NULL, activeDefragAlloc, HASHTABLE_SCAN_EMIT_REF);
|
||||
}
|
||||
@@ -470,7 +476,7 @@ static void activeDefragHashTypeEntry(void *privdata, void *element_ref) {
|
||||
}
|
||||
|
||||
static void scanLaterHash(robj *ob, unsigned long *cursor) {
|
||||
if (ob->type != OBJ_HASH || ob->encoding != OBJ_ENCODING_HASHTABLE) return;
|
||||
serverAssert(ob->type == OBJ_HASH && ob->encoding == OBJ_ENCODING_HASHTABLE);
|
||||
hashtable *ht = ob->ptr;
|
||||
*cursor = hashtableScanDefrag(ht, *cursor, activeDefragHashTypeEntry, NULL, activeDefragAlloc, HASHTABLE_SCAN_EMIT_REF);
|
||||
}
|
||||
@@ -557,10 +563,7 @@ static int scanLaterStreamListpacks(robj *ob, unsigned long *cursor, monotime en
|
||||
static unsigned char last[sizeof(streamID)];
|
||||
raxIterator ri;
|
||||
long iterations = 0;
|
||||
if (ob->type != OBJ_STREAM || ob->encoding != OBJ_ENCODING_STREAM) {
|
||||
*cursor = 0;
|
||||
return 0;
|
||||
}
|
||||
serverAssert(ob->type == OBJ_STREAM && ob->encoding == OBJ_ENCODING_STREAM);
|
||||
|
||||
stream *s = ob->ptr;
|
||||
raxStart(&ri, s->rax);
|
||||
@@ -822,15 +825,15 @@ static void defragPubsubScanCallback(void *privdata, void *elemref) {
|
||||
* and 1 if time is up and more work is needed. */
|
||||
static int defragLaterItem(robj *ob, unsigned long *cursor, monotime endtime, int dbid) {
|
||||
if (ob) {
|
||||
if (ob->type == OBJ_LIST) {
|
||||
if (ob->type == OBJ_LIST && ob->encoding == OBJ_ENCODING_QUICKLIST) {
|
||||
return scanLaterList(ob, cursor, endtime);
|
||||
} else if (ob->type == OBJ_SET) {
|
||||
} else if (ob->type == OBJ_SET && ob->encoding == OBJ_ENCODING_HASHTABLE) {
|
||||
scanLaterSet(ob, cursor);
|
||||
} else if (ob->type == OBJ_ZSET) {
|
||||
} else if (ob->type == OBJ_ZSET && ob->encoding == OBJ_ENCODING_SKIPLIST) {
|
||||
scanLaterZset(ob, cursor);
|
||||
} else if (ob->type == OBJ_HASH) {
|
||||
} else if (ob->type == OBJ_HASH && ob->encoding == OBJ_ENCODING_HASHTABLE) {
|
||||
scanLaterHash(ob, cursor);
|
||||
} else if (ob->type == OBJ_STREAM) {
|
||||
} else if (ob->type == OBJ_STREAM && ob->encoding == OBJ_ENCODING_STREAM) {
|
||||
return scanLaterStreamListpacks(ob, cursor, endtime);
|
||||
} else if (ob->type == OBJ_MODULE) {
|
||||
/* Fun fact (and a bug since forever): The key is passed to
|
||||
@@ -839,10 +842,9 @@ static int defragLaterItem(robj *ob, unsigned long *cursor, monotime endtime, in
|
||||
* callbacks. Nobody can ever have used this, i.e. accessed the key
|
||||
* name in the defrag module type callback. */
|
||||
void *sds_key_passed_as_robj = objectGetKey(ob);
|
||||
long long endtimeWallClock = ustime() + (endtime - getMonotonicUs());
|
||||
return moduleLateDefrag(sds_key_passed_as_robj, ob, cursor, endtimeWallClock, dbid);
|
||||
return moduleLateDefrag(sds_key_passed_as_robj, ob, cursor, endtime, dbid);
|
||||
} else {
|
||||
*cursor = 0; /* object type may have changed since we schedule it for later */
|
||||
*cursor = 0; /* object type/encoding may have changed since we schedule it for later */
|
||||
}
|
||||
} else {
|
||||
*cursor = 0; /* object may have been deleted already */
|
||||
@@ -1042,7 +1044,6 @@ static void endDefragCycle(bool normal_termination) {
|
||||
// For normal termination, we expect...
|
||||
serverAssert(!defrag.current_stage);
|
||||
serverAssert(listLength(defrag.remaining_stages) == 0);
|
||||
serverAssert(!defrag_later || listLength(defrag_later) == 0);
|
||||
} else {
|
||||
// Defrag is being terminated abnormally
|
||||
aeDeleteTimeEvent(server.el, defrag.timeproc_id);
|
||||
@@ -1058,6 +1059,8 @@ static void endDefragCycle(bool normal_termination) {
|
||||
listRelease(defrag.remaining_stages);
|
||||
defrag.remaining_stages = NULL;
|
||||
|
||||
/* For a normal termination, this list is usually empty, but it might contain elements
|
||||
* if a stage was aborted due to a flushall or some other DB swap. */
|
||||
if (defrag_later) {
|
||||
listRelease(defrag_later);
|
||||
defrag_later = NULL;
|
||||
|
||||
+5
-1
@@ -32,7 +32,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include "fmacros.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file initializes the global LUA object and registers functions to call Valkey API from within the LUA language.
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "functions.h"
|
||||
#include "sds.h"
|
||||
|
||||
+2
-1
@@ -1032,7 +1032,7 @@ void hashtableEmpty(hashtable *ht, void(callback)(hashtable *)) {
|
||||
if (ht->bucket_exp[table_index] < 0) {
|
||||
continue;
|
||||
}
|
||||
if (ht->used[table_index] > 0) {
|
||||
if (ht->used[table_index] > 0 || ht->child_buckets[table_index] > 0) {
|
||||
for (size_t idx = 0; idx < numBuckets(ht->bucket_exp[table_index]); idx++) {
|
||||
if (callback && (idx & 65535) == 0) callback(ht);
|
||||
bucket *b = &ht->tables[table_index][idx];
|
||||
@@ -1058,6 +1058,7 @@ void hashtableEmpty(hashtable *ht, void(callback)(hashtable *)) {
|
||||
} while (b != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
zfree(ht->tables[table_index]);
|
||||
if (ht->type->trackMemUsage) {
|
||||
ht->type->trackMemUsage(ht, -sizeof(bucket) * numBuckets(ht->bucket_exp[table_index]));
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "intrinsics.h"
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Copyright Valkey Contributors.
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD 3-Clause
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "io_threads.h"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright Valkey Contributors.
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/*
|
||||
* Copyright Valkey Contributors.
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD 3-Clause
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
/*
|
||||
* This file utilizes prefetching keys and data for multiple commands in a batch,
|
||||
* to improve performance by amortizing memory access costs across multiple operations.
|
||||
*/
|
||||
|
||||
+46
-15
@@ -26,6 +26,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
* Modules API documentation information
|
||||
@@ -2566,7 +2571,16 @@ void VM_Yield(ValkeyModuleCtx *ctx, int flags, const char *busy_reply) {
|
||||
* By default, the server will not fire key-space notifications that happened inside
|
||||
* a key-space notification callback. This flag allows to change this behavior
|
||||
* and fire nested key-space notifications. Notice: if enabled, the module
|
||||
* should protected itself from infinite recursion. */
|
||||
* should protected itself from infinite recursion.
|
||||
*
|
||||
* VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION:
|
||||
* When set, this option allows the module to skip command validation.
|
||||
* This is useful in scenarios where the module needs to bypass
|
||||
* command validation for specific operations
|
||||
* to reduce overhead or handle trusted custom command logic.
|
||||
* ValkeyModule_Replicate and ValkeyModule_EmitAOF
|
||||
* are affected by this option, allowing them to operate without
|
||||
* command validation check. */
|
||||
void VM_SetModuleOptions(ValkeyModuleCtx *ctx, int options) {
|
||||
ctx->module->options = options;
|
||||
}
|
||||
@@ -3630,8 +3644,10 @@ int VM_Replicate(ValkeyModuleCtx *ctx, const char *cmdname, const char *fmt, ...
|
||||
int argc = 0, flags = 0, j;
|
||||
va_list ap;
|
||||
|
||||
cmd = lookupCommandByCString((char *)cmdname);
|
||||
if (!cmd) return VALKEYMODULE_ERR;
|
||||
if (!ctx->module || !(ctx->module->options & VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION)) {
|
||||
cmd = lookupCommandByCString((char *)cmdname);
|
||||
if (!cmd) return VALKEYMODULE_ERR;
|
||||
}
|
||||
|
||||
/* Create the client and dispatch the command. */
|
||||
va_start(ap, fmt);
|
||||
@@ -4183,7 +4199,7 @@ static void moduleCloseKey(ValkeyModuleKey *key) {
|
||||
decrRefCount(key->key);
|
||||
}
|
||||
|
||||
/* Close a key handle. */
|
||||
/* Close a key handle. The key handle is freed and should not be accessed anymore. */
|
||||
void VM_CloseKey(ValkeyModuleKey *key) {
|
||||
if (key == NULL) return;
|
||||
moduleCloseKey(key);
|
||||
@@ -7535,15 +7551,17 @@ void VM_EmitAOF(ValkeyModuleIO *io, const char *cmdname, const char *fmt, ...) {
|
||||
int argc = 0, flags = 0, j;
|
||||
va_list ap;
|
||||
|
||||
cmd = lookupCommandByCString((char *)cmdname);
|
||||
if (!cmd) {
|
||||
serverLog(LL_WARNING,
|
||||
"Fatal: AOF method for module data type '%s' tried to "
|
||||
"emit unknown command '%s'",
|
||||
io->type->name, cmdname);
|
||||
io->error = 1;
|
||||
errno = EINVAL;
|
||||
return;
|
||||
if (!io->ctx || !io->ctx->module || !(io->ctx->module->options & VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION)) {
|
||||
cmd = lookupCommandByCString((char *)cmdname);
|
||||
if (!cmd) {
|
||||
serverLog(LL_WARNING,
|
||||
"Fatal: AOF method for module data type '%s' tried to "
|
||||
"emit unknown command '%s'",
|
||||
io->type->name, cmdname);
|
||||
io->error = 1;
|
||||
errno = EINVAL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Emit the arguments into the AOF in RESP format. */
|
||||
@@ -8950,7 +8968,13 @@ void moduleCallClusterReceivers(const char *sender_id,
|
||||
* was already a registered callback, this will replace the callback function
|
||||
* with the one provided, otherwise if the callback is set to NULL and there
|
||||
* is already a callback for this function, the callback is unregistered
|
||||
* (so this API call is also used in order to delete the receiver). */
|
||||
* (so this API call is also used in order to delete the receiver).
|
||||
*
|
||||
* When a message of this type is received, the registered callback function
|
||||
* will be invoked with details, including the 40-byte node ID of the sender.
|
||||
*
|
||||
* In Valkey 8.1 and later, the node ID is null-terminated. Prior to 8.1, it was
|
||||
* not null-terminated */
|
||||
void VM_RegisterClusterMessageReceiver(ValkeyModuleCtx *ctx,
|
||||
uint8_t type,
|
||||
ValkeyModuleClusterMessageReceiver callback) {
|
||||
@@ -9737,8 +9761,15 @@ ValkeyModuleString *VM_GetModuleUserACLString(ValkeyModuleUser *user) {
|
||||
* See more information in VM_GetModuleUserFromUserName.
|
||||
*
|
||||
* The returned string must be released with ValkeyModule_FreeString() or by
|
||||
* enabling automatic memory management. */
|
||||
* enabling automatic memory management.
|
||||
*
|
||||
* If the context is not associated with a client connection, NULL is returned
|
||||
* and errno is set to EINVAL. */
|
||||
ValkeyModuleString *VM_GetCurrentUserName(ValkeyModuleCtx *ctx) {
|
||||
if (ctx == NULL || ctx->client == NULL || ctx->client->user == NULL || ctx->client->user->name == NULL) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
return VM_CreateString(ctx, ctx->client->user->name, sdslen(ctx->client->user->name));
|
||||
}
|
||||
|
||||
|
||||
+46
-45
@@ -71,7 +71,7 @@ static void pauseClientsByClient(mstime_t end, int isPauseClientAll);
|
||||
int postponeClientRead(client *c);
|
||||
char *getClientSockname(client *c);
|
||||
static int parseClientFiltersOrReply(client *c, int index, clientFilter *filter);
|
||||
static int clientMatchesFilter(client *client, clientFilter client_filter);
|
||||
static int clientMatchesFilter(client *client, clientFilter *client_filter);
|
||||
static sds getAllFilteredClientsInfoString(clientFilter *client_filter, int hide_user_data);
|
||||
|
||||
int ProcessingEventsWhileBlocked = 0; /* See processEventsWhileBlocked(). */
|
||||
@@ -157,8 +157,6 @@ client *createClient(connection *conn) {
|
||||
* in the context of a client. When commands are executed in other
|
||||
* contexts (for instance a Lua script) we need a non connected client. */
|
||||
if (conn) {
|
||||
connEnableTcpNoDelay(conn);
|
||||
if (server.tcpkeepalive) connKeepAlive(conn, server.tcpkeepalive);
|
||||
connSetReadHandler(conn, readQueryFromClient);
|
||||
connSetPrivateData(conn, c);
|
||||
conn->flags |= CONN_FLAG_ALLOW_ACCEPT_OFFLOAD;
|
||||
@@ -842,6 +840,7 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
|
||||
size_t len_to_copy = prev->size - prev->used;
|
||||
if (len_to_copy > length) len_to_copy = length;
|
||||
memcpy(prev->buf + prev->used, s, len_to_copy);
|
||||
c->net_output_bytes_curr_cmd += len_to_copy;
|
||||
prev->used += len_to_copy;
|
||||
length -= len_to_copy;
|
||||
if (length == 0) {
|
||||
@@ -855,6 +854,7 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
|
||||
next->used < PROTO_REPLY_CHUNK_BYTES * 4 && c->io_write_state != CLIENT_PENDING_IO) {
|
||||
memmove(next->buf + length, next->buf, next->used);
|
||||
memcpy(next->buf, s, length);
|
||||
c->net_output_bytes_curr_cmd += length;
|
||||
next->used += length;
|
||||
listDelNode(c->reply, ln);
|
||||
} else {
|
||||
@@ -865,6 +865,7 @@ void setDeferredReply(client *c, void *node, const char *s, size_t length) {
|
||||
buf->size = usable_size - sizeof(clientReplyBlock);
|
||||
buf->used = length;
|
||||
memcpy(buf->buf, s, length);
|
||||
c->net_output_bytes_curr_cmd += length;
|
||||
listNodeValue(ln) = buf;
|
||||
c->reply_bytes += buf->size;
|
||||
|
||||
@@ -1582,7 +1583,6 @@ void unlinkClient(client *c) {
|
||||
|
||||
/* Remove from the list of pending writes if needed. */
|
||||
if (c->flag.pending_write) {
|
||||
serverAssert(&c->clients_pending_write_node.next != NULL || &c->clients_pending_write_node.prev != NULL);
|
||||
if (c->io_write_state == CLIENT_IDLE) {
|
||||
listUnlinkNode(server.clients_pending_write, &c->clients_pending_write_node);
|
||||
} else {
|
||||
@@ -2764,24 +2764,27 @@ static void setProtocolError(const char *errstr, client *c) {
|
||||
/* Sample some protocol to given an idea about what was inside. */
|
||||
char buf[256];
|
||||
buf[0] = '\0';
|
||||
if (c->querybuf && sdslen(c->querybuf) - c->qb_pos < PROTO_DUMP_LEN) {
|
||||
snprintf(buf, sizeof(buf), "Query buffer during protocol error: '%s'", c->querybuf + c->qb_pos);
|
||||
} else if (c->querybuf) {
|
||||
snprintf(buf, sizeof(buf), "Query buffer during protocol error: '%.*s' (... more %zu bytes ...) '%.*s'",
|
||||
PROTO_DUMP_LEN / 2, c->querybuf + c->qb_pos, sdslen(c->querybuf) - c->qb_pos - PROTO_DUMP_LEN,
|
||||
PROTO_DUMP_LEN / 2, c->querybuf + sdslen(c->querybuf) - PROTO_DUMP_LEN / 2);
|
||||
}
|
||||
if (server.hide_user_data_from_log) {
|
||||
snprintf(buf, sizeof(buf), "*redacted*");
|
||||
} else {
|
||||
if (c->querybuf && sdslen(c->querybuf) - c->qb_pos < PROTO_DUMP_LEN) {
|
||||
snprintf(buf, sizeof(buf), "'%s'", c->querybuf + c->qb_pos);
|
||||
} else if (c->querybuf) {
|
||||
snprintf(buf, sizeof(buf), "'%.*s' (... more %zu bytes ...) '%.*s'",
|
||||
PROTO_DUMP_LEN / 2, c->querybuf + c->qb_pos, sdslen(c->querybuf) - c->qb_pos - PROTO_DUMP_LEN,
|
||||
PROTO_DUMP_LEN / 2, c->querybuf + sdslen(c->querybuf) - PROTO_DUMP_LEN / 2);
|
||||
}
|
||||
|
||||
/* Remove non printable chars. */
|
||||
char *p = buf;
|
||||
while (*p != '\0') {
|
||||
if (!isprint(*p)) *p = '.';
|
||||
p++;
|
||||
/* Remove non printable chars. */
|
||||
char *p = buf;
|
||||
while (*p != '\0') {
|
||||
if (!isprint(*p)) *p = '.';
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Log all the client and protocol info. */
|
||||
int loglevel = (c->flag.primary) ? LL_WARNING : LL_VERBOSE;
|
||||
serverLog(loglevel, "Protocol error (%s) from client: %s. %s", errstr, client, buf);
|
||||
serverLog(loglevel, "Protocol error (%s) from client: %s. Query buffer: %s", errstr, client, buf);
|
||||
sdsfree(client);
|
||||
}
|
||||
c->flag.close_after_reply = 1;
|
||||
@@ -2854,30 +2857,30 @@ void processMultibulkBuffer(client *c) {
|
||||
*
|
||||
* Calculation: For multi bulk buffer, we accumulate four factors, namely;
|
||||
*
|
||||
* 1) multibulklen_slen + 1
|
||||
* 1) multibulklen_slen + 3
|
||||
* Cumulative string length (and not the value of) of multibulklen,
|
||||
* including +1 from RESP first byte.
|
||||
* 2) bulklen_slen + c->argc
|
||||
* including the first "*" byte and last "\r\n" 2 bytes from RESP.
|
||||
* 2) bulklen_slen + 3
|
||||
* Cumulative string length (and not the value of) of bulklen,
|
||||
* including +1 from RESP first byte per argument count.
|
||||
* including +3 from RESP first "$" byte and last "\r\n" 2 bytes per argument count.
|
||||
* 3) c->argv_len_sum
|
||||
* Cumulative string length of all argument vectors.
|
||||
* 4) c->argc * 4 + 2
|
||||
* Cumulative string length of all white-spaces, for which there exists a total of
|
||||
* 4 bytes per argument, plus 2 bytes from the leading '\r\n' from multibulklen.
|
||||
* 4) c->argc * 2
|
||||
* Cumulative string length of the arguments' white-spaces, for which there exists a total of
|
||||
* "\r\n" 2 bytes per argument.
|
||||
*
|
||||
* For example;
|
||||
* Command) SET key value
|
||||
* RESP) *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
|
||||
*
|
||||
* 1) String length of "*3" is 2, obtained from (multibulklen_slen + 1).
|
||||
* 2) String length of "$3" "$3" "$5" is 6, obtained from (bulklen_slen + c->argc).
|
||||
* 1) String length of "*3\r\n" is 4, obtained from (multibulklen_slen + 3).
|
||||
* 2) String length of "$3\r\n" "$3\r\n" "$5\r\n" is 12, obtained from (bulklen_slen + 3).
|
||||
* 3) String length of "SET" "key" "value" is 11, obtained from (c->argv_len_sum).
|
||||
* 4) String length of all white-spaces "\r\n" is 14, obtained from (c->argc * 4 + 2).
|
||||
* 4) String length of the 3 arguments' white-spaces "\r\n" is 6, obtained from (c->argc * 2).
|
||||
*
|
||||
* The 1st component is calculated within the below line.
|
||||
* */
|
||||
c->net_input_bytes_curr_cmd += (multibulklen_slen + 1);
|
||||
c->net_input_bytes_curr_cmd += (multibulklen_slen + 3);
|
||||
}
|
||||
|
||||
serverAssertWithInfo(c, NULL, c->multibulklen > 0);
|
||||
@@ -2942,9 +2945,8 @@ void processMultibulkBuffer(client *c) {
|
||||
}
|
||||
}
|
||||
c->bulklen = ll;
|
||||
/* Per-slot network bytes-in calculation, 2nd component.
|
||||
* c->argc portion is deferred, as it may not have been fully populated at this point. */
|
||||
c->net_input_bytes_curr_cmd += bulklen_slen;
|
||||
/* Per-slot network bytes-in calculation, 2nd component. */
|
||||
c->net_input_bytes_curr_cmd += (bulklen_slen + 3);
|
||||
}
|
||||
|
||||
/* Read bulk argument */
|
||||
@@ -2982,9 +2984,8 @@ void processMultibulkBuffer(client *c) {
|
||||
|
||||
/* We're done when c->multibulk == 0 */
|
||||
if (c->multibulklen == 0) {
|
||||
/* Per-slot network bytes-in calculation, 3rd and 4th components.
|
||||
* Here, the deferred c->argc from 2nd component is added, resulting in c->argc * 5 instead of * 4. */
|
||||
c->net_input_bytes_curr_cmd += (c->argv_len_sum + (c->argc * 5 + 2));
|
||||
/* Per-slot network bytes-in calculation, 3rd and 4th components. */
|
||||
c->net_input_bytes_curr_cmd += (c->argv_len_sum + (c->argc * 2));
|
||||
c->read_flags |= READ_FLAGS_PARSING_COMPLETED;
|
||||
}
|
||||
}
|
||||
@@ -3484,7 +3485,7 @@ static sds getAllFilteredClientsInfoString(clientFilter *client_filter, int hide
|
||||
listRewind(server.clients, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client = listNodeValue(ln);
|
||||
if (!clientMatchesFilter(client, *client_filter)) continue;
|
||||
if (!clientMatchesFilter(client, client_filter)) continue;
|
||||
o = catClientInfoString(o, client, hide_user_data);
|
||||
o = sdscatlen(o, "\n", 1);
|
||||
}
|
||||
@@ -3687,15 +3688,15 @@ static int parseClientFiltersOrReply(client *c, int index, clientFilter *filter)
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
static int clientMatchesFilter(client *client, clientFilter client_filter) {
|
||||
static int clientMatchesFilter(client *client, clientFilter *client_filter) {
|
||||
/* Check each filter condition and return false if the client does not match. */
|
||||
if (client_filter.addr && strcmp(getClientPeerId(client), client_filter.addr) != 0) return 0;
|
||||
if (client_filter.laddr && strcmp(getClientSockname(client), client_filter.laddr) != 0) return 0;
|
||||
if (client_filter.type != -1 && getClientType(client) != client_filter.type) return 0;
|
||||
if (client_filter.ids && !intsetFind(client_filter.ids, client->id)) return 0;
|
||||
if (client_filter.user && client->user != client_filter.user) return 0;
|
||||
if (client_filter.skipme && client == server.current_client) return 0;
|
||||
if (client_filter.max_age != 0 && (long long)(commandTimeSnapshot() / 1000 - client->ctime) < client_filter.max_age) return 0;
|
||||
if (client_filter->addr && strcmp(getClientPeerId(client), client_filter->addr) != 0) return 0;
|
||||
if (client_filter->laddr && strcmp(getClientSockname(client), client_filter->laddr) != 0) return 0;
|
||||
if (client_filter->type != -1 && getClientType(client) != client_filter->type) return 0;
|
||||
if (client_filter->ids && !intsetFind(client_filter->ids, client->id)) return 0;
|
||||
if (client_filter->user && client->user != client_filter->user) return 0;
|
||||
if (client_filter->skipme && client == server.current_client) return 0;
|
||||
if (client_filter->max_age != 0 && (long long)(commandTimeSnapshot() / 1000 - client->ctime) < client_filter->max_age) return 0;
|
||||
|
||||
/* If all conditions are satisfied, the client matches the filter. */
|
||||
return 1;
|
||||
@@ -3888,7 +3889,7 @@ void clientKillCommand(client *c) {
|
||||
listRewind(server.clients, &li);
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client *client = listNodeValue(ln);
|
||||
if (!clientMatchesFilter(client, client_filter)) continue;
|
||||
if (!clientMatchesFilter(client, &client_filter)) continue;
|
||||
|
||||
/* Kill it. */
|
||||
if (c == client) {
|
||||
|
||||
+1
-1
@@ -1194,7 +1194,7 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) {
|
||||
|
||||
asize = sizeof(*o) + hashtableMemUsage(ht);
|
||||
while (hashtableNext(&iter, &next) && samples < sample_size) {
|
||||
elesize += hashTypeEntryAllocSize(next);
|
||||
elesize += hashTypeEntryMemUsage(next);
|
||||
samples++;
|
||||
}
|
||||
hashtableResetIterator(&iter);
|
||||
|
||||
@@ -1187,6 +1187,7 @@ int raxRemove(rax *rax, unsigned char *s, size_t len, void **old) {
|
||||
rax_free(tofree);
|
||||
rax->numnodes--;
|
||||
if (h->iskey || (!h->iscompr && h->size != 1)) break;
|
||||
if (comprsize + h->size > RAX_NODE_MAX_SIZE) break;
|
||||
}
|
||||
debugnode("New node", new);
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "lzf.h" /* LZF compression library */
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
* the top-level directory.
|
||||
* ==========================================================================
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#define VALKEYMODULE_CORE_MODULE
|
||||
#include "server.h"
|
||||
|
||||
+36
-28
@@ -27,7 +27,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "cluster.h"
|
||||
@@ -46,6 +50,8 @@
|
||||
#include <sys/stat.h>
|
||||
#include <ctype.h>
|
||||
|
||||
void cleanupTransferResources(void);
|
||||
void replicationAbortSyncTransfer(void);
|
||||
void replicationDiscardCachedPrimary(void);
|
||||
void replicationResurrectCachedPrimary(connection *conn);
|
||||
void replicationResurrectProvisionalPrimary(void);
|
||||
@@ -1152,7 +1158,7 @@ void syncCommand(client *c) {
|
||||
/* Setup the replica as one waiting for BGSAVE to start. The following code
|
||||
* paths will change the state if we handle the replica differently. */
|
||||
c->repl_data->repl_state = REPLICA_STATE_WAIT_BGSAVE_START;
|
||||
if (server.repl_disable_tcp_nodelay) connDisableTcpNoDelay(c->conn); /* Non critical if it fails. */
|
||||
if (server.repl_disable_tcp_nodelay) anetDisableTcpNoDelay(NULL, c->conn->fd); /* Non critical if it fails. */
|
||||
c->repl_data->repldbfd = -1;
|
||||
c->flag.replica = 1;
|
||||
listAddNodeTail(server.replicas, c);
|
||||
@@ -2653,12 +2659,7 @@ void replicationAbortDualChannelSyncTransfer(void) {
|
||||
connClose(server.repl_rdb_transfer_s);
|
||||
server.repl_rdb_transfer_s = NULL;
|
||||
}
|
||||
zfree(server.repl_transfer_tmpfile);
|
||||
server.repl_transfer_tmpfile = NULL;
|
||||
if (server.repl_transfer_fd != -1) {
|
||||
close(server.repl_transfer_fd);
|
||||
server.repl_transfer_fd = -1;
|
||||
}
|
||||
cleanupTransferResources();
|
||||
server.repl_rdb_channel_state = REPL_DUAL_CHANNEL_STATE_NONE;
|
||||
server.repl_provisional_primary.read_reploff = 0;
|
||||
server.repl_provisional_primary.reploff = 0;
|
||||
@@ -2867,14 +2868,8 @@ error:
|
||||
connClose(server.repl_transfer_s);
|
||||
server.repl_transfer_s = NULL;
|
||||
}
|
||||
if (server.repl_rdb_transfer_s) {
|
||||
connClose(server.repl_rdb_transfer_s);
|
||||
server.repl_rdb_transfer_s = NULL;
|
||||
}
|
||||
if (server.repl_transfer_fd != -1) close(server.repl_transfer_fd);
|
||||
server.repl_transfer_fd = -1;
|
||||
server.repl_state = REPL_STATE_CONNECT;
|
||||
replicationAbortDualChannelSyncTransfer();
|
||||
server.repl_state = REPL_STATE_CONNECT;
|
||||
}
|
||||
|
||||
/* Replication: Replica side.
|
||||
@@ -3816,7 +3811,8 @@ void syncWithPrimary(connection *conn) {
|
||||
server.repl_rdb_transfer_s = connCreate(connTypeOfReplication());
|
||||
if (connConnect(server.repl_rdb_transfer_s, server.primary_host, server.primary_port, server.bind_source_addr,
|
||||
dualChannelFullSyncWithPrimary) == C_ERR) {
|
||||
serverLog(LL_WARNING, "Unable to connect to Primary: %s", connGetLastError(server.repl_transfer_s));
|
||||
dualChannelServerLog(LL_WARNING, "Unable to connect to Primary: %s",
|
||||
connGetLastError(server.repl_transfer_s));
|
||||
connClose(server.repl_rdb_transfer_s);
|
||||
server.repl_rdb_transfer_s = NULL;
|
||||
goto error;
|
||||
@@ -3856,10 +3852,7 @@ error:
|
||||
connClose(server.repl_rdb_transfer_s);
|
||||
server.repl_rdb_transfer_s = NULL;
|
||||
}
|
||||
if (server.repl_transfer_fd != -1) close(server.repl_transfer_fd);
|
||||
if (server.repl_transfer_tmpfile) zfree(server.repl_transfer_tmpfile);
|
||||
server.repl_transfer_tmpfile = NULL;
|
||||
server.repl_transfer_fd = -1;
|
||||
replicationAbortSyncTransfer();
|
||||
server.repl_state = REPL_STATE_CONNECT;
|
||||
return;
|
||||
|
||||
@@ -3886,11 +3879,33 @@ int connectWithPrimary(void) {
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* In disk-based replication, replica will open a temp db file to store the RDB file.
|
||||
* Before entering the REPL_STATE_TRANSFER or after entering the REPL_STATE_TRANSFER,
|
||||
* if an error occurs, we need to clean up related resources, such as closing the tmp
|
||||
* file fd and deleting the temp file.
|
||||
*
|
||||
* Noted that repl_transfer_fd and repl_transfer_tmpfile should be set/unset together. */
|
||||
void cleanupTransferResources(void) {
|
||||
if (server.repl_transfer_fd == -1) {
|
||||
serverAssert(server.repl_transfer_tmpfile == NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
serverAssert(server.repl_transfer_tmpfile != NULL);
|
||||
close(server.repl_transfer_fd);
|
||||
bg_unlink(server.repl_transfer_tmpfile);
|
||||
zfree(server.repl_transfer_tmpfile);
|
||||
server.repl_transfer_tmpfile = NULL;
|
||||
server.repl_transfer_fd = -1;
|
||||
}
|
||||
|
||||
/* This function can be called when a non blocking connection is currently
|
||||
* in progress to undo it.
|
||||
* Never call this function directly, use cancelReplicationHandshake() instead.
|
||||
*/
|
||||
void undoConnectWithPrimary(void) {
|
||||
if (server.repl_transfer_s == NULL) return;
|
||||
|
||||
connClose(server.repl_transfer_s);
|
||||
server.repl_transfer_s = NULL;
|
||||
}
|
||||
@@ -3899,15 +3914,8 @@ void undoConnectWithPrimary(void) {
|
||||
* Never call this function directly, use cancelReplicationHandshake() instead.
|
||||
*/
|
||||
void replicationAbortSyncTransfer(void) {
|
||||
serverAssert(server.repl_state == REPL_STATE_TRANSFER);
|
||||
undoConnectWithPrimary();
|
||||
if (server.repl_transfer_fd != -1) {
|
||||
close(server.repl_transfer_fd);
|
||||
bg_unlink(server.repl_transfer_tmpfile);
|
||||
zfree(server.repl_transfer_tmpfile);
|
||||
server.repl_transfer_tmpfile = NULL;
|
||||
server.repl_transfer_fd = -1;
|
||||
}
|
||||
cleanupTransferResources();
|
||||
}
|
||||
|
||||
/* This function aborts a non blocking replication attempt if there is one
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright Valkey Contributors.
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
@@ -95,6 +95,28 @@ static inline unsigned char sdsType(const_sds s) {
|
||||
return flags & SDS_TYPE_MASK;
|
||||
}
|
||||
|
||||
/* Returns a user data bit stored in the SDS header by sdsSetAuxBit. The bit
|
||||
* index is 0-4. Returns 0 or 1. Always returns 0 for SDS_TYPE_5. */
|
||||
static inline int sdsGetAuxBit(const_sds s, int bit) {
|
||||
unsigned char flags = s[-1];
|
||||
return sdsType(s) == SDS_TYPE_5 ? 0 : flags >> (SDS_TYPE_BITS + bit);
|
||||
}
|
||||
|
||||
/* Stores a bit in an unused area in the SDS header, except for SDS_TYPE_5. The
|
||||
* bit index is 0-4. The value is 0 or 1. The aux bits are lost if the SDS is
|
||||
* auto-resized. This is only for special uses like immutable SDS embedded in
|
||||
* other structures. */
|
||||
static inline void sdsSetAuxBit(sds s, int bit, int value) {
|
||||
if (sdsType(s) == SDS_TYPE_5) return;
|
||||
unsigned char flags = s[-1];
|
||||
if (value) {
|
||||
flags |= 1 << (SDS_TYPE_BITS + bit);
|
||||
} else {
|
||||
flags &= ~(1 << (SDS_TYPE_BITS + bit));
|
||||
}
|
||||
s[-1] = (char)flags;
|
||||
}
|
||||
|
||||
static inline size_t sdslen(const_sds s) {
|
||||
switch (sdsType(s)) {
|
||||
case SDS_TYPE_5: return SDS_TYPE_5_LEN(s[-1]);
|
||||
|
||||
+5
-1
@@ -26,7 +26,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include "server.h"
|
||||
#include "monotonic.h"
|
||||
#include "cluster.h"
|
||||
|
||||
+3
-4
@@ -2084,6 +2084,7 @@ struct valkeyServer {
|
||||
unsigned long cluster_blacklist_ttl; /* Duration in seconds that a node is denied re-entry into
|
||||
* the cluster after it is forgotten with CLUSTER FORGET. */
|
||||
int cluster_slot_stats_enabled; /* Cluster slot usage statistics tracking enabled. */
|
||||
mstime_t cluster_mf_timeout; /* Milliseconds to do a manual failover. */
|
||||
/* Debug config that goes along with cluster_drop_packet_filter. When set, the link is closed on packet drop. */
|
||||
uint32_t debug_cluster_close_link_on_packet_drop : 1;
|
||||
/* Debug config to control the random ping. When set, we will disable the random ping in clusterCron. */
|
||||
@@ -2584,8 +2585,6 @@ void populateCommandLegacyRangeSpec(struct serverCommand *c);
|
||||
long long ustime(void);
|
||||
mstime_t mstime(void);
|
||||
mstime_t commandTimeSnapshot(void);
|
||||
void getRandomHexChars(char *p, size_t len);
|
||||
void getRandomBytes(unsigned char *p, size_t len);
|
||||
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
|
||||
void exitFromChild(int retcode);
|
||||
long long serverPopcount(void *s, long count);
|
||||
@@ -3250,11 +3249,11 @@ robj *setTypeDup(robj *o);
|
||||
#define HASH_SET_TAKE_VALUE (1 << 1)
|
||||
#define HASH_SET_COPY 0
|
||||
|
||||
typedef struct hashTypeEntry hashTypeEntry;
|
||||
typedef void hashTypeEntry;
|
||||
hashTypeEntry *hashTypeCreateEntry(sds field, sds value);
|
||||
sds hashTypeEntryGetField(const hashTypeEntry *entry);
|
||||
sds hashTypeEntryGetValue(const hashTypeEntry *entry);
|
||||
size_t hashTypeEntryAllocSize(hashTypeEntry *entry);
|
||||
size_t hashTypeEntryMemUsage(hashTypeEntry *entry);
|
||||
hashTypeEntry *hashTypeEntryDefrag(hashTypeEntry *entry, void *(*defragfn)(void *), sds (*sdsdefragfn)(sds));
|
||||
void dismissHashTypeEntry(hashTypeEntry *entry);
|
||||
void freeHashTypeEntry(hashTypeEntry *entry);
|
||||
|
||||
+2
-10
@@ -326,6 +326,8 @@ static void connSocketAcceptHandler(aeEventLoop *el, int fd, void *privdata, int
|
||||
return;
|
||||
}
|
||||
serverLog(LL_VERBOSE, "Accepted %s:%d", cip, cport);
|
||||
|
||||
if (server.tcpkeepalive) anetKeepAlive(NULL, cfd, server.tcpkeepalive);
|
||||
acceptCommonHandler(connCreateAcceptedSocket(cfd, NULL), flags, cip);
|
||||
}
|
||||
}
|
||||
@@ -462,16 +464,6 @@ int connNonBlock(connection *conn) {
|
||||
return anetNonBlock(NULL, conn->fd);
|
||||
}
|
||||
|
||||
int connEnableTcpNoDelay(connection *conn) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetEnableTcpNoDelay(NULL, conn->fd);
|
||||
}
|
||||
|
||||
int connDisableTcpNoDelay(connection *conn) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetDisableTcpNoDelay(NULL, conn->fd);
|
||||
}
|
||||
|
||||
int connKeepAlive(connection *conn, int interval) {
|
||||
if (conn->fd == -1) return C_ERR;
|
||||
return anetKeepAlive(NULL, conn->fd, interval);
|
||||
|
||||
+197
-37
@@ -26,55 +26,200 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Hash Entry API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
struct hashTypeEntry {
|
||||
sds value;
|
||||
char field_offset;
|
||||
char field_data[];
|
||||
};
|
||||
/* The hashTypeEntry pointer is the field sds. We encode the entry layout type
|
||||
* in the field SDS header. Field type SDS_TYPE_5 doesn't have any spare bits to
|
||||
* encode this so we use it only for the first layout type.
|
||||
*
|
||||
* Entry with embedded value, used for small sizes. The value is stored as
|
||||
* SDS_TYPE_8. The field can use any SDS type.
|
||||
*
|
||||
* +--------------+---------------+
|
||||
* | field | value |
|
||||
* | hdr "foo" \0 | hdr8 "bar" \0 |
|
||||
* +------^-------+---------------+
|
||||
* |
|
||||
* |
|
||||
* entry pointer = field sds
|
||||
*
|
||||
* Entry with value pointer, used for larger fields and values. The field is SDS
|
||||
* type 8 or higher.
|
||||
*
|
||||
* +-------+--------------+
|
||||
* | value | field |
|
||||
* | ptr | hdr "foo" \0 |
|
||||
* +-------+------^-------+
|
||||
* |
|
||||
* |
|
||||
* entry pointer = field sds
|
||||
*/
|
||||
|
||||
/* The maximum allocation size we want to use for entries with embedded
|
||||
* values. */
|
||||
#define EMBED_VALUE_MAX_ALLOC_SIZE 128
|
||||
|
||||
/* SDS aux flag. If set, it indicates that the entry has an embedded value
|
||||
* pointer located in memory before the embedded field. If unset, the entry
|
||||
* instead has an embedded value located after the embedded field. */
|
||||
#define FIELD_SDS_AUX_BIT_ENTRY_HAS_VALUE_PTR 0
|
||||
|
||||
static inline bool entryHasValuePtr(const hashTypeEntry *entry) {
|
||||
return sdsGetAuxBit(entry, FIELD_SDS_AUX_BIT_ENTRY_HAS_VALUE_PTR);
|
||||
}
|
||||
|
||||
/* Returns the location of a pointer to a separately allocated value. Only for
|
||||
* an entry without an embedded value. */
|
||||
static sds *hashTypeEntryGetValueRef(const hashTypeEntry *entry) {
|
||||
serverAssert(entryHasValuePtr(entry));
|
||||
char *field_data = sdsAllocPtr(entry);
|
||||
field_data -= sizeof(sds *);
|
||||
return (sds *)field_data;
|
||||
}
|
||||
|
||||
/* takes ownership of value, does not take ownership of field */
|
||||
hashTypeEntry *hashTypeCreateEntry(sds field, sds value) {
|
||||
size_t field_len = sdslen(field);
|
||||
char field_sds_type = sdsReqType(field_len);
|
||||
int field_sds_type = sdsReqType(field_len);
|
||||
size_t field_size = sdsReqSize(field_len, field_sds_type);
|
||||
size_t total_size = sizeof(hashTypeEntry) + field_size;
|
||||
hashTypeEntry *entry = zmalloc(total_size);
|
||||
|
||||
entry->value = value;
|
||||
entry->field_offset = sdsHdrSize(field_sds_type);
|
||||
sdswrite(entry->field_data, field_size, field_sds_type, field, field_len);
|
||||
return entry;
|
||||
size_t value_len = sdslen(value);
|
||||
size_t value_size = sdsReqSize(value_len, SDS_TYPE_8);
|
||||
sds embedded_field_sds;
|
||||
if (field_size + value_size <= EMBED_VALUE_MAX_ALLOC_SIZE) {
|
||||
/* Embed field and value. Value is fixed to SDS_TYPE_8. Unused
|
||||
* allocation space is recorded in the embedded value's SDS header.
|
||||
*
|
||||
* +--------------+---------------+
|
||||
* | field | value |
|
||||
* | hdr "foo" \0 | hdr8 "bar" \0 |
|
||||
* +--------------+---------------+
|
||||
*/
|
||||
size_t min_size = field_size + value_size;
|
||||
size_t buf_size;
|
||||
char *buf = zmalloc_usable(min_size, &buf_size);
|
||||
embedded_field_sds = sdswrite(buf, field_size, field_sds_type, field, field_len);
|
||||
sdswrite(buf + field_size, buf_size - field_size, SDS_TYPE_8, value, value_len);
|
||||
/* Field sds aux bits are zero, which we use for this entry encoding. */
|
||||
sdsSetAuxBit(embedded_field_sds, FIELD_SDS_AUX_BIT_ENTRY_HAS_VALUE_PTR, 0);
|
||||
serverAssert(!entryHasValuePtr(embedded_field_sds));
|
||||
sdsfree(value);
|
||||
} else {
|
||||
/* Embed field, but not value. Field must be >= SDS_TYPE_8 to encode to
|
||||
* indicate this type of entry.
|
||||
*
|
||||
* +-------+---------------+
|
||||
* | value | field |
|
||||
* | ptr | hdr8 "foo" \0 |
|
||||
* +-------+---------------+
|
||||
*/
|
||||
char field_sds_type = sdsReqType(field_len);
|
||||
if (field_sds_type == SDS_TYPE_5) field_sds_type = SDS_TYPE_8;
|
||||
field_size = sdsReqSize(field_len, field_sds_type);
|
||||
size_t alloc_size = sizeof(sds *) + field_size;
|
||||
char *buf = zmalloc(alloc_size);
|
||||
*(sds *)buf = value;
|
||||
embedded_field_sds = sdswrite(buf + sizeof(sds *), field_size, field_sds_type, field, field_len);
|
||||
/* Store the entry encoding type in sds aux bits. */
|
||||
sdsSetAuxBit(embedded_field_sds, FIELD_SDS_AUX_BIT_ENTRY_HAS_VALUE_PTR, 1);
|
||||
serverAssert(entryHasValuePtr(embedded_field_sds));
|
||||
}
|
||||
return (void *)embedded_field_sds;
|
||||
}
|
||||
|
||||
/* The entry pointer is the field sds, but that's an implementation detail. */
|
||||
sds hashTypeEntryGetField(const hashTypeEntry *entry) {
|
||||
const char *field = entry->field_data + entry->field_offset;
|
||||
return (sds)field;
|
||||
return (sds)entry;
|
||||
}
|
||||
|
||||
sds hashTypeEntryGetValue(const hashTypeEntry *entry) {
|
||||
return entry->value;
|
||||
if (entryHasValuePtr(entry)) {
|
||||
return *hashTypeEntryGetValueRef(entry);
|
||||
} else {
|
||||
/* Skip field content, field null terminator and value sds8 hdr. */
|
||||
size_t offset = sdslen(entry) + 1 + sdsHdrSize(SDS_TYPE_8);
|
||||
return (char *)entry + offset;
|
||||
}
|
||||
}
|
||||
|
||||
/* frees previous value, takes ownership of new value */
|
||||
static void hashTypeEntryReplaceValue(hashTypeEntry *entry, sds value) {
|
||||
sdsfree(entry->value);
|
||||
entry->value = value;
|
||||
/* Returns the address of the entry allocation. */
|
||||
static void *hashTypeEntryAllocPtr(hashTypeEntry *entry) {
|
||||
char *buf = sdsAllocPtr(entry);
|
||||
if (entryHasValuePtr(entry)) {
|
||||
buf -= sizeof(sds *);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Returns allocation size of hashTypeEntry and data owned by hashTypeEntry,
|
||||
* even if not embedded in the same allocation. */
|
||||
size_t hashTypeEntryAllocSize(hashTypeEntry *entry) {
|
||||
size_t size = zmalloc_usable_size(entry);
|
||||
size += sdsAllocSize(entry->value);
|
||||
return size;
|
||||
/* Frees previous value, takes ownership of new value, returns entry (may be
|
||||
* reallocated). */
|
||||
static hashTypeEntry *hashTypeEntryReplaceValue(hashTypeEntry *entry, sds value) {
|
||||
sds field = (sds)entry;
|
||||
size_t field_size = sdsHdrSize(sdsType(field)) + sdsalloc(field) + 1;
|
||||
size_t value_len = sdslen(value);
|
||||
size_t value_size = sdsReqSize(value_len, SDS_TYPE_8);
|
||||
if (!entryHasValuePtr(entry)) {
|
||||
/* Reuse the allocation if the new value fits and leaves no more than
|
||||
* 25% unused space after replacing the value. */
|
||||
char *alloc_ptr = sdsAllocPtr(entry);
|
||||
size_t required_size = field_size + value_size;
|
||||
size_t alloc_size;
|
||||
if (required_size <= EMBED_VALUE_MAX_ALLOC_SIZE &&
|
||||
required_size <= (alloc_size = hashTypeEntryMemUsage(entry)) &&
|
||||
required_size >= alloc_size * 3 / 4) {
|
||||
/* It fits in the allocation and leaves max 25% unused space. */
|
||||
sdswrite(alloc_ptr + field_size, alloc_size - field_size, SDS_TYPE_8, value, value_len);
|
||||
sdsfree(value);
|
||||
return entry;
|
||||
}
|
||||
hashTypeEntry *new_entry = hashTypeCreateEntry(hashTypeEntryGetField(entry), value);
|
||||
freeHashTypeEntry(entry);
|
||||
return new_entry;
|
||||
} else {
|
||||
/* The value pointer is located before the embedded field. */
|
||||
if (field_size + value_size <= EMBED_VALUE_MAX_ALLOC_SIZE) {
|
||||
/* Convert to entry with embedded value. */
|
||||
hashTypeEntry *new_entry = hashTypeCreateEntry(field, value);
|
||||
freeHashTypeEntry(entry);
|
||||
return new_entry;
|
||||
} else {
|
||||
/* Not embedded value. */
|
||||
sds *value_ref = hashTypeEntryGetValueRef(entry);
|
||||
sdsfree(*value_ref);
|
||||
*value_ref = value;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns memory usage of a hashTypeEntry, including all allocations owned by
|
||||
* the hashTypeEntry. */
|
||||
size_t hashTypeEntryMemUsage(hashTypeEntry *entry) {
|
||||
size_t mem = 0;
|
||||
if (entryHasValuePtr(entry)) {
|
||||
/* Alloc size is not stored in the embedded field. */
|
||||
mem = zmalloc_usable_size(hashTypeEntryAllocPtr(entry));
|
||||
mem += sdsAllocSize(*hashTypeEntryGetValueRef(entry));
|
||||
} else {
|
||||
/* Remaining alloc size is encoded in the embedded value SDS header. */
|
||||
sds field = entry;
|
||||
sds value = (char *)entry + sdslen(field) + 1 + sdsHdrSize(SDS_TYPE_8);
|
||||
size_t field_size = sdsHdrSize(sdsType(field)) + sdslen(field) + 1;
|
||||
size_t value_size = sdsHdrSize(SDS_TYPE_8) + sdsalloc(value) + 1;
|
||||
mem = field_size + value_size;
|
||||
}
|
||||
return mem;
|
||||
}
|
||||
|
||||
/* Defragments a hashtable entry (field-value pair) if needed, using the
|
||||
@@ -85,25 +230,35 @@ size_t hashTypeEntryAllocSize(hashTypeEntry *entry) {
|
||||
* If the location of the hashTypeEntry changed we return the new location,
|
||||
* otherwise we return NULL. */
|
||||
hashTypeEntry *hashTypeEntryDefrag(hashTypeEntry *entry, void *(*defragfn)(void *), sds (*sdsdefragfn)(sds)) {
|
||||
hashTypeEntry *new_entry = defragfn(entry);
|
||||
if (new_entry) entry = new_entry;
|
||||
|
||||
sds new_value = sdsdefragfn(entry->value);
|
||||
if (new_value) entry->value = new_value;
|
||||
|
||||
return new_entry;
|
||||
if (entryHasValuePtr(entry)) {
|
||||
sds *value_ref = hashTypeEntryGetValueRef(entry);
|
||||
sds new_value = sdsdefragfn(*value_ref);
|
||||
if (new_value) *value_ref = new_value;
|
||||
}
|
||||
char *allocation = hashTypeEntryAllocPtr(entry);
|
||||
char *new_allocation = defragfn(allocation);
|
||||
if (new_allocation != NULL) {
|
||||
/* Return the same offset into the new allocation as the entry's offset
|
||||
* in the old allocation. */
|
||||
return new_allocation + ((char *)entry - allocation);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Used for releasing memory to OS to avoid unnecessary CoW. Called when we've
|
||||
* forked and memory won't be used again. See zmadvise_dontneed() */
|
||||
void dismissHashTypeEntry(hashTypeEntry *entry) {
|
||||
/* Only dismiss values memory since the field size usually is small. */
|
||||
dismissSds(entry->value);
|
||||
if (entryHasValuePtr(entry)) {
|
||||
dismissSds(*hashTypeEntryGetValueRef(entry));
|
||||
}
|
||||
}
|
||||
|
||||
void freeHashTypeEntry(hashTypeEntry *entry) {
|
||||
sdsfree(entry->value);
|
||||
zfree(entry);
|
||||
if (entryHasValuePtr(entry)) {
|
||||
sdsfree(*hashTypeEntryGetValueRef(entry));
|
||||
}
|
||||
zfree(hashTypeEntryAllocPtr(entry));
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
@@ -321,7 +476,12 @@ int hashTypeSet(robj *o, sds field, sds value, int flags) {
|
||||
hashtableInsertAtPosition(ht, entry, &position);
|
||||
} else {
|
||||
/* exists: replace value */
|
||||
hashTypeEntryReplaceValue(existing, v);
|
||||
void *new_entry = hashTypeEntryReplaceValue(existing, v);
|
||||
if (new_entry != existing) {
|
||||
/* It has been reallocated. */
|
||||
int replaced = hashtableReplaceReallocatedEntry(ht, existing, new_entry);
|
||||
serverAssert(replaced);
|
||||
}
|
||||
update = 1;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include "hashtable.h"
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "server.h"
|
||||
#include <math.h> /* isnan(), isinf() */
|
||||
|
||||
+5
-1
@@ -27,7 +27,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Sorted set API
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
+1
-1
@@ -166,7 +166,7 @@ int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int
|
||||
return C_ERR;
|
||||
|
||||
ftval *= 1000.0; /* seconds => millisec */
|
||||
if (ftval > LLONG_MAX) {
|
||||
if (ftval > (long double)LLONG_MAX) {
|
||||
addReplyError(c, "timeout is out of range");
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
@@ -415,12 +415,6 @@ error:
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
#ifdef TLS_DEBUGGING
|
||||
#define TLSCONN_DEBUG(fmt, ...) serverLog(LL_DEBUG, "TLSCONN: " fmt, __VA_ARGS__)
|
||||
#else
|
||||
#define TLSCONN_DEBUG(fmt, ...)
|
||||
#endif
|
||||
|
||||
static ConnectionType CT_TLS;
|
||||
|
||||
/* Normal socket connections have a simple events/handler correlation.
|
||||
@@ -452,6 +446,10 @@ typedef struct tls_connection {
|
||||
SSL *ssl;
|
||||
char *ssl_error;
|
||||
listNode *pending_list_node;
|
||||
/* Per https://docs.openssl.org/master/man3/SSL_write, after a write call with partially written data,
|
||||
* we must make subsequent write calls with the same length. We use this field to keep track of
|
||||
* the previous write length. */
|
||||
size_t last_failed_write_data_len;
|
||||
} tls_connection;
|
||||
|
||||
static connection *createTLSConnection(int client_side) {
|
||||
@@ -693,9 +691,6 @@ static void TLSAccept(void *_conn) {
|
||||
static void tlsHandleEvent(tls_connection *conn, int mask) {
|
||||
int ret, conn_error;
|
||||
|
||||
TLSCONN_DEBUG("tlsEventHandler(): fd=%d, state=%d, mask=%d, r=%d, w=%d, flags=%d", fd, conn->c.state, mask,
|
||||
conn->c.read_handler != NULL, conn->c.write_handler != NULL, conn->flags);
|
||||
|
||||
switch (conn->c.state) {
|
||||
case CONN_STATE_CONNECTING:
|
||||
conn_error = anetGetError(conn->c.fd);
|
||||
@@ -798,6 +793,8 @@ static void tlsAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask)
|
||||
return;
|
||||
}
|
||||
serverLog(LL_VERBOSE, "Accepted %s:%d", cip, cport);
|
||||
|
||||
if (server.tcpkeepalive) anetKeepAlive(NULL, cfd, server.tcpkeepalive);
|
||||
acceptCommonHandler(connCreateAcceptedTLS(cfd, &server.tls_auth_clients), flags, cip);
|
||||
}
|
||||
}
|
||||
@@ -910,11 +907,23 @@ static int connTLSWrite(connection *conn_, const void *data, size_t data_len) {
|
||||
|
||||
if (conn->c.state != CONN_STATE_CONNECTED) return -1;
|
||||
ERR_clear_error();
|
||||
/* In case when last write failed due to some internal reason, retry has to provide
|
||||
* at least the same amount of bytes (https://docs.openssl.org/master/man3/SSL_write).
|
||||
* If that condition is not met, OpenSSL will return "SSL routines::bad length".
|
||||
* Currently we only suspect this can happen during primary cron sending '\n'
|
||||
* indication to the replica, so we silently return from this function without
|
||||
* impacting the connection state. */
|
||||
if (data_len < conn->last_failed_write_data_len) {
|
||||
// TODO: place debugAssert for this case once the known issue described is resolved
|
||||
return -1;
|
||||
}
|
||||
ret = SSL_write(conn->ssl, data, data_len);
|
||||
conn->last_failed_write_data_len = ret <= 0 ? data_len : 0;
|
||||
return updateStateAfterSSLIO(conn, ret, 1);
|
||||
}
|
||||
|
||||
static int connTLSWritev(connection *conn_, const struct iovec *iov, int iovcnt) {
|
||||
tls_connection *conn = (tls_connection *)conn_;
|
||||
if (iovcnt == 1) return connTLSWrite(conn_, iov[0].iov_base, iov[0].iov_len);
|
||||
|
||||
/* Accumulate the amount of bytes of each buffer and check if it exceeds NET_MAX_WRITES_PER_EVENT. */
|
||||
@@ -924,10 +933,15 @@ static int connTLSWritev(connection *conn_, const struct iovec *iov, int iovcnt)
|
||||
if (iov_bytes_len > NET_MAX_WRITES_PER_EVENT) break;
|
||||
}
|
||||
|
||||
/* The amount of all buffers is greater than NET_MAX_WRITES_PER_EVENT,
|
||||
* which is not worth doing so much memory copying to reduce system calls,
|
||||
* therefore, invoke connTLSWrite() multiple times to avoid memory copies. */
|
||||
if (iov_bytes_len > NET_MAX_WRITES_PER_EVENT) {
|
||||
/* In case the amount of all buffers is greater than NET_MAX_WRITES_PER_EVENT,
|
||||
* it might not worth doing so much memory copying to reduce system calls,
|
||||
* therefore, invoke connTLSWrite() multiple times to avoid memory copies.
|
||||
* However, in case when last write failed we still have to repeat sending last_failed_write_data_len
|
||||
* bytes. Because of openssl implementation we cannot repeat sending writes with length smaller than
|
||||
* the last failed write (https://docs.openssl.org/master/man3/SSL_write) so in case the first io buffer
|
||||
* does not provide at least the same amount of bytes as previous failed write, we will have to fallback to
|
||||
* memory copy to a static buffer before calling SSL_write. */
|
||||
if (iov_bytes_len > NET_MAX_WRITES_PER_EVENT && iovcnt > 0 && iov[0].iov_len >= conn->last_failed_write_data_len) {
|
||||
ssize_t tot_sent = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ssize_t sent = connTLSWrite(conn_, iov[i].iov_base, iov[i].iov_len);
|
||||
@@ -941,10 +955,14 @@ static int connTLSWritev(connection *conn_, const struct iovec *iov, int iovcnt)
|
||||
/* The amount of all buffers is less than NET_MAX_WRITES_PER_EVENT,
|
||||
* which is worth doing more memory copies in exchange for fewer system calls,
|
||||
* so concatenate these scattered buffers into a contiguous piece of memory
|
||||
* and send it away by one call to connTLSWrite(). */
|
||||
* and send it away by one call to connTLSWrite().
|
||||
* However, code can fallback here in case when last write failed and first
|
||||
* element of io is buffer not big enough to provide required amount of bytes
|
||||
* to retry, so iov_bytes_len may exceed NET_MAX_WRITES_PER_EVENT by the amount
|
||||
* of remaining bytes from last taken io. */
|
||||
char buf[iov_bytes_len];
|
||||
size_t offset = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
for (int i = 0; i < iovcnt && offset < iov_bytes_len; i++) {
|
||||
memcpy(buf + offset, iov[i].iov_base, iov[i].iov_len);
|
||||
offset += iov[i].iov_len;
|
||||
}
|
||||
|
||||
+1
-1
@@ -280,7 +280,7 @@ void sendTrackingMessage(client *c, char *keyname, size_t keylen, int proto) {
|
||||
int using_redirection = 0;
|
||||
if (c->pubsub_data->client_tracking_redirection) {
|
||||
client *redir = lookupClientByID(c->pubsub_data->client_tracking_redirection);
|
||||
if (!redir) {
|
||||
if (!redir || redir->flag.close_after_reply || redir->flag.close_asap) {
|
||||
c->flag.tracking_broken_redir = 1;
|
||||
/* We need to signal to the original connection that we
|
||||
* are unable to send invalidation messages to the redirected
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "test_help.h"
|
||||
|
||||
#include "../config.h"
|
||||
#include "../zmalloc.h"
|
||||
|
||||
extern long long popcountScalar(void *s, long count);
|
||||
#ifdef HAVE_AVX2
|
||||
extern long long popcountAVX2(void *s, long count);
|
||||
#endif
|
||||
|
||||
static long long bitcount(void *s, long count) {
|
||||
long long bits = 0;
|
||||
uint8_t *p = (uint8_t *)s;
|
||||
for (int x = 0; x < count; x += 1) {
|
||||
uint8_t val = *(x + p);
|
||||
while (val) {
|
||||
bits += val & 1;
|
||||
val >>= 1;
|
||||
}
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
static int test_case(const char *msg, int size) {
|
||||
size_t bufsize = size > 0 ? size : 1;
|
||||
uint8_t buf[bufsize];
|
||||
int fuzzing = 1000;
|
||||
for (int y = 0; y < fuzzing; y += 1) {
|
||||
for (int z = 0; z < size; z += 1) {
|
||||
buf[z] = random() % 256;
|
||||
}
|
||||
|
||||
long long expect = bitcount(buf, size);
|
||||
long long ret_scalar = popcountScalar(buf, size);
|
||||
TEST_ASSERT_MESSAGE(msg, expect == ret_scalar);
|
||||
#ifdef HAVE_AVX2
|
||||
long long ret_avx2 = popcountAVX2(buf, size);
|
||||
TEST_ASSERT_MESSAGE(msg, expect == ret_avx2);
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_popcount(int argc, char **argv, int flags) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
UNUSED(flags);
|
||||
|
||||
#define TEST_CASE(MSG, SIZE) \
|
||||
if (test_case("Test failed: " MSG, SIZE)) { \
|
||||
return 1; \
|
||||
}
|
||||
|
||||
/* The AVX2 version divides the array into the following 3 parts."
|
||||
* Part A Part B Part C
|
||||
* +-----------------+--------------+---------+
|
||||
* | 8 * 32bytes * X | 32bytes * Y | Z bytes |
|
||||
* +-----------------+--------------+---------+
|
||||
*/
|
||||
/* So we test the following cases */
|
||||
TEST_CASE("Popcount(Part A)", 8 * 32 * 2);
|
||||
TEST_CASE("Popcount(Part B)", 32 * 2);
|
||||
TEST_CASE("Popcount(Part C)", 2);
|
||||
TEST_CASE("Popcount(Part A + Part B)", 8 * 32 * 7 + 32 * 2);
|
||||
TEST_CASE("Popcount(Part A + Part C)", 8 * 32 * 11 + 7);
|
||||
TEST_CASE("Popcount(Part A + Part B + Part C)", 8 * 32 * 3 + 3 * 32 + 5);
|
||||
TEST_CASE("Popcount(Corner case)", 0);
|
||||
#undef TEST_CASE
|
||||
|
||||
return 0;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
+48
-21
@@ -5,6 +5,11 @@ uint64_t hashTestCallback(const void *key) {
|
||||
return hashtableGenHashFunction((char *)key, strlen((char *)key));
|
||||
}
|
||||
|
||||
uint64_t hashConflictTestCallback(const void *key) {
|
||||
UNUSED(key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmpTestCallback(const void *k1, const void *k2) {
|
||||
return strcmp(k1, k2);
|
||||
}
|
||||
@@ -23,6 +28,16 @@ hashtableType KvstoreHashtableTestType = {
|
||||
.getMetadataSize = kvstoreHashtableMetadataSize,
|
||||
};
|
||||
|
||||
hashtableType KvstoreConflictHashtableTestType = {
|
||||
.hashFunction = hashConflictTestCallback,
|
||||
.keyCompare = cmpTestCallback,
|
||||
.entryDestructor = freeTestCallback,
|
||||
.rehashingStarted = kvstoreHashtableRehashingStarted,
|
||||
.rehashingCompleted = kvstoreHashtableRehashingCompleted,
|
||||
.trackMemUsage = kvstoreHashtableTrackMemUsage,
|
||||
.getMetadataSize = kvstoreHashtableMetadataSize,
|
||||
};
|
||||
|
||||
char *stringFromInt(int value) {
|
||||
char buf[32];
|
||||
int len;
|
||||
@@ -70,31 +85,43 @@ int test_kvstoreIteratorRemoveAllKeysNoDeleteEmptyHashtable(int argc, char **arg
|
||||
UNUSED(argv);
|
||||
UNUSED(flags);
|
||||
|
||||
int i;
|
||||
void *key;
|
||||
kvstoreIterator *kvs_it;
|
||||
hashtableType *type[] = {
|
||||
&KvstoreHashtableTestType,
|
||||
&KvstoreConflictHashtableTestType,
|
||||
NULL,
|
||||
};
|
||||
|
||||
int didx = 0;
|
||||
int curr_slot = 0;
|
||||
kvstore *kvs1 = kvstoreCreate(&KvstoreHashtableTestType, 0, KVSTORE_ALLOCATE_HASHTABLES_ON_DEMAND);
|
||||
for (int t = 0; type[t] != NULL; t++) {
|
||||
hashtableType *testType = type[t];
|
||||
TEST_PRINT_INFO("Testing %d hashtableType\n", t);
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
TEST_ASSERT(kvstoreHashtableAdd(kvs1, didx, stringFromInt(i)));
|
||||
int i;
|
||||
void *key;
|
||||
kvstoreIterator *kvs_it;
|
||||
|
||||
int didx = 0;
|
||||
int curr_slot = 0;
|
||||
kvstore *kvs1 = kvstoreCreate(testType, 0, KVSTORE_ALLOCATE_HASHTABLES_ON_DEMAND);
|
||||
|
||||
for (i = 0; i < 16; i++) {
|
||||
TEST_ASSERT(kvstoreHashtableAdd(kvs1, didx, stringFromInt(i)));
|
||||
}
|
||||
|
||||
kvs_it = kvstoreIteratorInit(kvs1, HASHTABLE_ITER_SAFE);
|
||||
while (kvstoreIteratorNext(kvs_it, &key)) {
|
||||
curr_slot = kvstoreIteratorGetCurrentHashtableIndex(kvs_it);
|
||||
TEST_ASSERT(kvstoreHashtableDelete(kvs1, curr_slot, key));
|
||||
}
|
||||
kvstoreIteratorRelease(kvs_it);
|
||||
|
||||
hashtable *ht = kvstoreGetHashtable(kvs1, didx);
|
||||
TEST_ASSERT(ht != NULL);
|
||||
TEST_ASSERT(kvstoreHashtableSize(kvs1, didx) == 0);
|
||||
TEST_ASSERT(kvstoreSize(kvs1) == 0);
|
||||
|
||||
kvstoreRelease(kvs1);
|
||||
}
|
||||
|
||||
kvs_it = kvstoreIteratorInit(kvs1, HASHTABLE_ITER_SAFE);
|
||||
while (kvstoreIteratorNext(kvs_it, &key)) {
|
||||
curr_slot = kvstoreIteratorGetCurrentHashtableIndex(kvs_it);
|
||||
TEST_ASSERT(kvstoreHashtableDelete(kvs1, curr_slot, key));
|
||||
}
|
||||
kvstoreIteratorRelease(kvs_it);
|
||||
|
||||
hashtable *ht = kvstoreGetHashtable(kvs1, didx);
|
||||
TEST_ASSERT(ht != NULL);
|
||||
TEST_ASSERT(kvstoreHashtableSize(kvs1, didx) == 0);
|
||||
TEST_ASSERT(kvstoreSize(kvs1) == 0);
|
||||
|
||||
kvstoreRelease(kvs1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+41
-2
@@ -1,13 +1,18 @@
|
||||
/*
|
||||
* Copyright Valkey contributors.
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD 3-Clause
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "test_files.h"
|
||||
#include "test_help.h"
|
||||
#include "../util.h"
|
||||
#include "../mt19937-64.h"
|
||||
#include "../hashtable.h"
|
||||
#include "../zmalloc.h"
|
||||
|
||||
/* We override the default assertion mechanism, so that it prints out info and then dies. */
|
||||
void _serverAssert(const char *estr, const char *file, int line) {
|
||||
@@ -32,6 +37,12 @@ int runTestSuite(struct unitTestSuite *test, int argc, char **argv, int flags) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if the test suite has cleaned up all the memory used. */
|
||||
if (zmalloc_used_memory() > 0) {
|
||||
printf("[" KRED "%s" KRESET "] Memory leak detected of %zu bytes\n", test->filename, zmalloc_used_memory());
|
||||
failed_tests++;
|
||||
}
|
||||
|
||||
printf("[" KBLUE "END" KRESET "] - %s: ", test->filename);
|
||||
printf("%d tests, %d passed, %d failed\n", test_num, test_num - failed_tests, failed_tests);
|
||||
return !failed_tests;
|
||||
@@ -40,6 +51,7 @@ int runTestSuite(struct unitTestSuite *test, int argc, char **argv, int flags) {
|
||||
int main(int argc, char **argv) {
|
||||
int flags = 0;
|
||||
char *file = NULL;
|
||||
char *seed = NULL;
|
||||
for (int j = 1; j < argc; j++) {
|
||||
char *arg = argv[j];
|
||||
if (!strcasecmp(arg, "--accurate"))
|
||||
@@ -51,13 +63,40 @@ int main(int argc, char **argv) {
|
||||
file = argv[j + 1];
|
||||
} else if (!strcasecmp(arg, "--valgrind")) {
|
||||
flags |= UNIT_TEST_VALGRIND;
|
||||
} else if (!strcasecmp(arg, "--seed")) {
|
||||
seed = argv[j + 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (seed) {
|
||||
setRandomSeedCString(seed, strlen(seed));
|
||||
}
|
||||
|
||||
char seed_cstr[129];
|
||||
getRandomSeedCString(seed_cstr, 129);
|
||||
|
||||
printf("Tests will run with seed=%s\n", seed_cstr);
|
||||
|
||||
unsigned long long genrandseed;
|
||||
getRandomBytes((void *)&genrandseed, sizeof(genrandseed));
|
||||
|
||||
uint8_t hashseed[16];
|
||||
getRandomBytes(hashseed, sizeof(hashseed));
|
||||
|
||||
|
||||
int numtests = sizeof(unitTestSuite) / sizeof(struct unitTestSuite);
|
||||
int failed_num = 0, suites_executed = 0;
|
||||
for (int j = 0; j < numtests; j++) {
|
||||
if (file && strcasecmp(file, unitTestSuite[j].filename)) continue;
|
||||
|
||||
/* We need to explicitly set the seed in the several random numbers
|
||||
* generator that valkey server uses so that the unit tests reproduce
|
||||
* the random values in a deterministic way. */
|
||||
setRandomSeedCString(seed_cstr, strlen(seed_cstr));
|
||||
init_genrand64(genrandseed);
|
||||
srandom((unsigned)genrandseed);
|
||||
hashtableSetHashFunctionSeed(hashseed);
|
||||
|
||||
if (!runTestSuite(&unitTestSuite[j], argc, argv, flags)) {
|
||||
failed_num++;
|
||||
}
|
||||
|
||||
@@ -2294,6 +2294,7 @@ int test_quicklistCompressAndDecomressQuicklistPlainNodeLargeThanUINT32MAX(int a
|
||||
zfree(node->next);
|
||||
zfree(node->entry);
|
||||
zfree(node);
|
||||
zfree(s);
|
||||
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
@@ -1023,3 +1023,55 @@ int test_raxFuzz(int argc, char **argv, int flags) {
|
||||
}
|
||||
return !!errors;
|
||||
}
|
||||
|
||||
/* This test verifies that raxRemove correctly handles compression when two keys
|
||||
* share a common prefix. Upon deletion of one key, rax attempts to recompress
|
||||
* the structure back to its original form for other key. Historically, there was
|
||||
* a crash when deleting one key because rax would attempt to recompress the
|
||||
* structure without checking the 512MB size limit.
|
||||
*
|
||||
* This test is disabled by default because it uses a lot of memory. */
|
||||
int test_raxRecompressHugeKey(int argc, char **argv, int flags) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
|
||||
if (!(flags & UNIT_TEST_LARGE_MEMORY)) return 0;
|
||||
|
||||
rax *rt = raxNew();
|
||||
|
||||
/* Insert small keys */
|
||||
char small_key[32];
|
||||
const char *small_prefix = ",({5oM}";
|
||||
int i;
|
||||
for (i = 1; i <= 20; i++) {
|
||||
snprintf(small_key, sizeof(small_key), "%s%d", small_prefix, i);
|
||||
size_t keylen = strlen(small_key);
|
||||
raxInsert(rt, (unsigned char *)small_key, keylen,
|
||||
(void *)(long)i, NULL);
|
||||
}
|
||||
|
||||
/* Insert large key exceeding compressed node size limit */
|
||||
size_t max_keylen = ((1 << 29) - 1) + 100; // Compressed node limit + overflow
|
||||
const char *large_prefix = ",({ABC}";
|
||||
unsigned char *large_key = zmalloc(max_keylen + strlen(large_prefix));
|
||||
if (!large_key) {
|
||||
fprintf(stderr, "Failed to allocate memory for large key\n");
|
||||
raxFree(rt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
memcpy(large_key, large_prefix, strlen(large_prefix));
|
||||
memset(large_key + strlen(large_prefix), '1', max_keylen);
|
||||
raxInsert(rt, large_key, max_keylen + strlen(large_prefix), NULL, NULL);
|
||||
|
||||
/* Remove small keys to trigger recompression crash in raxRemove() */
|
||||
for (i = 20; i >= 1; i--) {
|
||||
snprintf(small_key, sizeof(small_key), "%s%d", small_prefix, i);
|
||||
size_t keylen = strlen(small_key);
|
||||
raxRemove(rt, (unsigned char *)small_key, keylen, NULL);
|
||||
}
|
||||
|
||||
zfree(large_key);
|
||||
raxFree(rt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
/*
|
||||
* Copyright Valkey Contributors.
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD 3-Clause
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
|
||||
#include "../valkey_strtod.h"
|
||||
#include "errno.h"
|
||||
#include "math.h"
|
||||
|
||||
+54
-22
@@ -51,6 +51,7 @@
|
||||
#include "sha256.h"
|
||||
#include "config.h"
|
||||
#include "zmalloc.h"
|
||||
#include "serverassert.h"
|
||||
|
||||
#include "valkey_strtod.h"
|
||||
|
||||
@@ -905,36 +906,67 @@ int version2num(const char *version) {
|
||||
return v;
|
||||
}
|
||||
|
||||
/* Global state. */
|
||||
static int seed_initialized = 0;
|
||||
static unsigned char seed[64]; /* 512 bit internal block size. */
|
||||
|
||||
static void initializeRandomSeed(void) {
|
||||
assert(seed_initialized == 0);
|
||||
/* Initialize a seed and use SHA1 in counter mode, where we hash
|
||||
* the same seed with a progressive counter. For the goals of this
|
||||
* function we just need non-colliding strings, there are no
|
||||
* cryptographic security needs. */
|
||||
FILE *fp = fopen("/dev/urandom", "r");
|
||||
if (fp == NULL || fread(seed, sizeof(seed), 1, fp) != 1) {
|
||||
/* Revert to a weaker seed, and in this case reseed again
|
||||
* at every call.*/
|
||||
for (unsigned int j = 0; j < sizeof(seed); j++) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
pid_t pid = getpid();
|
||||
seed[j] = tv.tv_sec ^ tv.tv_usec ^ pid ^ (long)fp;
|
||||
}
|
||||
} else {
|
||||
seed_initialized = 1;
|
||||
}
|
||||
if (fp) fclose(fp);
|
||||
}
|
||||
|
||||
/* This function receives a string with 64 bytes encoded as 128 hexadecimal
|
||||
* digits, and sets the 64 byte random seed. */
|
||||
void setRandomSeedCString(char *seed_str, size_t len) {
|
||||
assert(len == sizeof(seed) * 2);
|
||||
for (size_t i = 0; i < sizeof(seed); i++) {
|
||||
sscanf(seed_str + (i * 2), "%02hhX", &seed[i]);
|
||||
}
|
||||
seed_initialized = 1;
|
||||
}
|
||||
|
||||
/* This function populates a char buffer with 129 bytes with the 64 bytes
|
||||
* random seed encoded as 128 hexadecimal digits and a null terminator. */
|
||||
void getRandomSeedCString(char *buff, size_t len) {
|
||||
assert(len == (sizeof(seed) * 2 + 1));
|
||||
|
||||
if (!seed_initialized) {
|
||||
initializeRandomSeed();
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(seed); i++) {
|
||||
snprintf(buff + (i * 2), 3, "%02hhX", seed[i]);
|
||||
}
|
||||
buff[sizeof(seed) * 2] = '\0';
|
||||
}
|
||||
|
||||
/* Get random bytes, attempts to get an initial seed from /dev/urandom and
|
||||
* the uses a one way hash function in counter mode to generate a random
|
||||
* stream. However if /dev/urandom is not available, a weaker seed is used.
|
||||
*
|
||||
* This function is not thread safe, since the state is global. */
|
||||
void getRandomBytes(unsigned char *p, size_t len) {
|
||||
/* Global state. */
|
||||
static int seed_initialized = 0;
|
||||
static unsigned char seed[64]; /* 512 bit internal block size. */
|
||||
static uint64_t counter = 0; /* The counter we hash with the seed. */
|
||||
static uint64_t counter = 0; /* The counter we hash with the seed. */
|
||||
|
||||
if (!seed_initialized) {
|
||||
/* Initialize a seed and use SHA1 in counter mode, where we hash
|
||||
* the same seed with a progressive counter. For the goals of this
|
||||
* function we just need non-colliding strings, there are no
|
||||
* cryptographic security needs. */
|
||||
FILE *fp = fopen("/dev/urandom", "r");
|
||||
if (fp == NULL || fread(seed, sizeof(seed), 1, fp) != 1) {
|
||||
/* Revert to a weaker seed, and in this case reseed again
|
||||
* at every call.*/
|
||||
for (unsigned int j = 0; j < sizeof(seed); j++) {
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
pid_t pid = getpid();
|
||||
seed[j] = tv.tv_sec ^ tv.tv_usec ^ pid ^ (long)fp;
|
||||
}
|
||||
} else {
|
||||
seed_initialized = 1;
|
||||
}
|
||||
if (fp) fclose(fp);
|
||||
initializeRandomSeed();
|
||||
}
|
||||
|
||||
while (len) {
|
||||
|
||||
@@ -99,5 +99,9 @@ int snprintf_async_signal_safe(char *to, size_t n, const char *fmt, ...);
|
||||
#endif
|
||||
size_t valkey_strlcpy(char *dst, const char *src, size_t dsize);
|
||||
size_t valkey_strlcat(char *dst, const char *src, size_t dsize);
|
||||
void getRandomSeedCString(char *buff, size_t len);
|
||||
void setRandomSeedCString(char *seed_str, size_t len);
|
||||
void getRandomHexChars(char *p, size_t len);
|
||||
void getRandomBytes(unsigned char *p, size_t len);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -244,10 +244,11 @@ static dictType dtype = {
|
||||
static redisContext *getRedisContext(const char *ip, int port, const char *hostsocket) {
|
||||
redisContext *ctx = NULL;
|
||||
redisReply *reply = NULL;
|
||||
struct timeval tv = {0};
|
||||
if (hostsocket == NULL)
|
||||
ctx = redisConnect(ip, port);
|
||||
ctx = redisConnectWrapper(ip, port, tv, 0);
|
||||
else
|
||||
ctx = redisConnectUnix(hostsocket);
|
||||
ctx = redisConnectUnixWrapper(hostsocket, tv, 0);
|
||||
if (ctx == NULL || ctx->err) {
|
||||
fprintf(stderr, "Could not connect to server at ");
|
||||
char *err = (ctx != NULL ? ctx->errstr : "");
|
||||
@@ -637,6 +638,7 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
|
||||
|
||||
const char *ip = NULL;
|
||||
int port = 0;
|
||||
struct timeval tv = {0};
|
||||
c->cluster_node = NULL;
|
||||
if (config.hostsocket == NULL || is_cluster_client) {
|
||||
if (!is_cluster_client) {
|
||||
@@ -654,9 +656,9 @@ static client createClient(char *cmd, size_t len, client from, int thread_id) {
|
||||
port = node->port;
|
||||
c->cluster_node = node;
|
||||
}
|
||||
c->context = redisConnectNonBlock(ip, port);
|
||||
c->context = redisConnectWrapper(ip, port, tv, 1);
|
||||
} else {
|
||||
c->context = redisConnectUnixNonBlock(config.hostsocket);
|
||||
c->context = redisConnectUnixWrapper(config.hostsocket, tv, 1);
|
||||
}
|
||||
if (c->context->err) {
|
||||
fprintf(stderr, "Could not connect to server at ");
|
||||
@@ -1340,7 +1342,7 @@ int parseOptions(int argc, char **argv) {
|
||||
if (lastarg) goto invalid;
|
||||
config.conn_info.user = sdsnew(argv[++i]);
|
||||
} else if (!strcmp(argv[i], "-u") && !lastarg) {
|
||||
parseRedisUri(argv[++i], "redis-benchmark", &config.conn_info, &config.tls);
|
||||
parseUri(argv[++i], "valkey-benchmark", &config.conn_info, &config.tls);
|
||||
if (config.conn_info.hostport < 0 || config.conn_info.hostport > 65535) {
|
||||
fprintf(stderr, "Invalid server port.\n");
|
||||
exit(1);
|
||||
|
||||
+14
-7
@@ -27,7 +27,11 @@
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
#include "fmacros.h"
|
||||
|
||||
#include <stdio.h>
|
||||
@@ -1631,9 +1635,9 @@ static int cliConnect(int flags) {
|
||||
|
||||
/* Do not use hostsocket when we got redirected in cluster mode */
|
||||
if (config.hostsocket == NULL || (config.cluster_mode && config.cluster_reissue_command)) {
|
||||
context = redisConnectWrapper(config.conn_info.hostip, config.conn_info.hostport, config.connect_timeout);
|
||||
context = redisConnectWrapper(config.conn_info.hostip, config.conn_info.hostport, config.connect_timeout, 0);
|
||||
} else {
|
||||
context = redisConnectUnixWrapper(config.hostsocket, config.connect_timeout);
|
||||
context = redisConnectUnixWrapper(config.hostsocket, config.connect_timeout, 0);
|
||||
}
|
||||
|
||||
if (!context->err && config.tls) {
|
||||
@@ -2356,6 +2360,9 @@ static int cliSendCommand(int argc, char **argv, long repeat) {
|
||||
output_raw = 1;
|
||||
}
|
||||
|
||||
/* In a multi block, commands will return status strings instead of verbatim strings. */
|
||||
if (config.in_multi) output_raw = 0;
|
||||
|
||||
if (!strcasecmp(command, "shutdown")) config.shutdown = 1;
|
||||
if (!strcasecmp(command, "monitor")) config.monitor_mode = 1;
|
||||
int is_subscribe =
|
||||
@@ -2518,7 +2525,7 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
|
||||
fflush(stdout);
|
||||
|
||||
redisFree(c);
|
||||
c = redisConnectWrapper(config.conn_info.hostip, config.conn_info.hostport, config.connect_timeout);
|
||||
c = redisConnectWrapper(config.conn_info.hostip, config.conn_info.hostport, config.connect_timeout, 0);
|
||||
if (!c->err && config.tls) {
|
||||
const char *err = NULL;
|
||||
if (cliSecureConnection(c, config.sslconfig, &err) == REDIS_ERR && err) {
|
||||
@@ -2601,7 +2608,7 @@ static int parseOptions(int argc, char **argv) {
|
||||
} else if (!strcmp(argv[i], "--user") && !lastarg) {
|
||||
config.conn_info.user = sdsnew(argv[++i]);
|
||||
} else if (!strcmp(argv[i], "-u") && !lastarg) {
|
||||
parseRedisUri(argv[++i], "valkey-cli", &config.conn_info, &config.tls);
|
||||
parseUri(argv[++i], "valkey-cli", &config.conn_info, &config.tls);
|
||||
if (config.conn_info.hostport < 0 || config.conn_info.hostport > 65535) {
|
||||
fprintf(stderr, "Invalid server port.\n");
|
||||
exit(1);
|
||||
@@ -3949,7 +3956,7 @@ cleanup:
|
||||
|
||||
static int clusterManagerNodeConnect(clusterManagerNode *node) {
|
||||
if (node->context) redisFree(node->context);
|
||||
node->context = redisConnectWrapper(node->ip, node->port, config.connect_timeout);
|
||||
node->context = redisConnectWrapper(node->ip, node->port, config.connect_timeout, 0);
|
||||
if (!node->context->err && config.tls) {
|
||||
const char *err = NULL;
|
||||
if (cliSecureConnection(node->context, config.sslconfig, &err) == REDIS_ERR && err) {
|
||||
@@ -7666,7 +7673,7 @@ static int clusterManagerCommandImport(int argc, char **argv) {
|
||||
char *reply_err = NULL;
|
||||
redisReply *src_reply = NULL;
|
||||
// Connect to the source node.
|
||||
redisContext *src_ctx = redisConnectWrapper(src_ip, src_port, config.connect_timeout);
|
||||
redisContext *src_ctx = redisConnectWrapper(src_ip, src_port, config.connect_timeout, 0);
|
||||
if (src_ctx->err) {
|
||||
success = 0;
|
||||
fprintf(stderr, "Could not connect to Valkey at %s:%d: %s.\n", src_ip, src_port, src_ctx->errstr);
|
||||
|
||||
+6
-1
@@ -325,10 +325,15 @@ typedef uint64_t ValkeyModuleTimerID;
|
||||
* If enabled, the module is responsible to break endless loop. */
|
||||
#define VALKEYMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS (1 << 3)
|
||||
|
||||
/* Skipping command validation can improve performance by reducing the overhead associated
|
||||
* with command checking, especially in high-throughput scenarios where commands
|
||||
* are already pre-validated or trusted. */
|
||||
#define VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION (1 << 4)
|
||||
|
||||
/* Next option flag, must be updated when adding new module flags above!
|
||||
* This flag should not be used directly by the module.
|
||||
* Use ValkeyModule_GetModuleOptionsAll instead. */
|
||||
#define _VALKEYMODULE_OPTIONS_FLAGS_NEXT (1 << 4)
|
||||
#define _VALKEYMODULE_OPTIONS_FLAGS_NEXT (1 << 5)
|
||||
|
||||
/* Definitions for ValkeyModule_SetCommandInfo. */
|
||||
|
||||
|
||||
+3
-3
@@ -4,13 +4,13 @@
|
||||
* similar. */
|
||||
#define SERVER_NAME "valkey"
|
||||
#define SERVER_TITLE "Valkey"
|
||||
#define VALKEY_VERSION "255.255.255"
|
||||
#define VALKEY_VERSION_NUM 0x00ffffff
|
||||
#define VALKEY_VERSION "8.1.0"
|
||||
#define VALKEY_VERSION_NUM 0x00080100
|
||||
/* The release stage is used in order to provide release status information.
|
||||
* In unstable branch the status is always "dev".
|
||||
* During release process the status will be set to rc1,rc2...rcN.
|
||||
* When the version is released the status will be "ga". */
|
||||
#define VALKEY_RELEASE_STAGE "dev"
|
||||
#define VALKEY_RELEASE_STAGE "ga"
|
||||
|
||||
/* Redis OSS compatibility version, should never
|
||||
* exceed 7.2.x. */
|
||||
|
||||
@@ -109,6 +109,27 @@ if {!$::valgrind} {
|
||||
assert_equal "PONG" [r ping]
|
||||
}
|
||||
}
|
||||
|
||||
# test hide-user-data-from-log
|
||||
set server_path [tmpdir server5.log]
|
||||
start_server [list overrides [list dir $server_path crash-memcheck-enabled no hide-user-data-from-log no]] {
|
||||
test "Config hide-user-data-from-log is off" {
|
||||
r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$blabla\r\n"
|
||||
r flush
|
||||
catch {r debug segfault}
|
||||
wait_for_log_messages 0 {"*Query buffer: *\$blabla*"} 0 10 1000
|
||||
}
|
||||
}
|
||||
|
||||
set server_path [tmpdir server6.log]
|
||||
start_server [list overrides [list dir $server_path crash-memcheck-enabled no hide-user-data-from-log yes]] {
|
||||
test "Config hide-user-data-from-log is on" {
|
||||
r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$blabla\r\n"
|
||||
r flush
|
||||
catch {r debug segfault}
|
||||
wait_for_log_messages 0 {"*Query buffer: *redacted*"} 0 10 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# test DEBUG ASSERT
|
||||
|
||||
@@ -46,6 +46,15 @@ int PingallCommand(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) {
|
||||
return ValkeyModule_ReplyWithSimpleString(ctx, "OK");
|
||||
}
|
||||
|
||||
void DingReceiver(ValkeyModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len) {
|
||||
ValkeyModule_Log(ctx, "notice", "DING (type %d) RECEIVED from %.*s: '%.*s'", type, VALKEYMODULE_NODE_ID_LEN, sender_id, (int)len, payload);
|
||||
ValkeyModule_SendClusterMessage(ctx, sender_id, MSGTYPE_DONG, "Message Received!", 17);
|
||||
}
|
||||
|
||||
void DongReceiver(ValkeyModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len) {
|
||||
ValkeyModule_Log(ctx, "notice", "DONG (type %d) RECEIVED from %.*s: '%.*s'", type, VALKEYMODULE_NODE_ID_LEN, sender_id, (int)len, payload);
|
||||
}
|
||||
|
||||
int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int argc) {
|
||||
VALKEYMODULE_NOT_USED(argv);
|
||||
VALKEYMODULE_NOT_USED(argc);
|
||||
@@ -63,5 +72,8 @@ int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int arg
|
||||
if (ValkeyModule_CreateCommand(ctx, "test.cluster_shards", test_cluster_shards, "", 0, 0, 0) == VALKEYMODULE_ERR)
|
||||
return VALKEYMODULE_ERR;
|
||||
|
||||
/* Register our handlers for different message types. */
|
||||
ValkeyModule_RegisterClusterMessageReceiver(ctx, MSGTYPE_DING, DingReceiver);
|
||||
ValkeyModule_RegisterClusterMessageReceiver(ctx, MSGTYPE_DONG, DongReceiver);
|
||||
return VALKEYMODULE_OK;
|
||||
}
|
||||
|
||||
@@ -703,6 +703,9 @@ int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int arg
|
||||
return VALKEYMODULE_ERR;
|
||||
}
|
||||
|
||||
/* This option tests skip command validation for ValkeyModule_EmitAOF */
|
||||
ValkeyModule_SetModuleOptions(ctx, VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION);
|
||||
|
||||
if (ValkeyModule_CreateCommand(ctx, "mem.alloc", MemAlloc_RedisCommand, "write deny-oom", 1, 1, 1) == VALKEYMODULE_ERR) {
|
||||
return VALKEYMODULE_ERR;
|
||||
}
|
||||
|
||||
@@ -249,8 +249,8 @@ int propagateTestSimpleCommand(ValkeyModuleCtx *ctx, ValkeyModuleString **argv,
|
||||
VALKEYMODULE_NOT_USED(argc);
|
||||
|
||||
/* Replicate two commands to test MULTI/EXEC wrapping. */
|
||||
ValkeyModule_Replicate(ctx,"INCR","c","counter-1");
|
||||
ValkeyModule_Replicate(ctx,"INCR","c","counter-2");
|
||||
ValkeyModule_Replicate(ctx, "INCR", "c", "counter-1");
|
||||
ValkeyModule_Replicate(ctx, "INCR", "c", "counter-2");
|
||||
ValkeyModule_ReplyWithSimpleString(ctx,"OK");
|
||||
return VALKEYMODULE_OK;
|
||||
}
|
||||
@@ -265,8 +265,8 @@ int propagateTestMixedCommand(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, i
|
||||
reply = ValkeyModule_Call(ctx, "INCR", "c!", "using-call");
|
||||
ValkeyModule_FreeCallReply(reply);
|
||||
|
||||
ValkeyModule_Replicate(ctx,"INCR","c","counter-1");
|
||||
ValkeyModule_Replicate(ctx,"INCR","c","counter-2");
|
||||
ValkeyModule_Replicate(ctx, "INCR", "c", "counter-1");
|
||||
ValkeyModule_Replicate(ctx, "INCR", "c", "counter-2");
|
||||
|
||||
reply = ValkeyModule_Call(ctx, "INCR", "c!", "after-call");
|
||||
ValkeyModule_FreeCallReply(reply);
|
||||
@@ -347,6 +347,9 @@ int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv, int arg
|
||||
|
||||
detached_ctx = ValkeyModule_GetDetachedThreadSafeContext(ctx);
|
||||
|
||||
/* This option tests skip command validation for ValkeyModule_Replicate */
|
||||
ValkeyModule_SetModuleOptions(ctx, VALKEYMODULE_OPTIONS_SKIP_COMMAND_VALIDATION);
|
||||
|
||||
if (ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_ALL, KeySpace_NotificationGeneric) == VALKEYMODULE_ERR)
|
||||
return VALKEYMODULE_ERR;
|
||||
|
||||
|
||||
+6
-3
@@ -27,7 +27,7 @@ def build_program():
|
||||
return 0
|
||||
|
||||
|
||||
# iterate /sys/class/infiniband, find any usable RDMA device, and return IPv4 address
|
||||
# iterate /sys/class/infiniband, find any usable RDMA device, and return IPv4/IPV6 address
|
||||
def find_rdma_dev():
|
||||
# Ex, /sys/class/infiniband/mlx5_0
|
||||
# Ex, /sys/class/infiniband/rxe_eth0
|
||||
@@ -39,9 +39,12 @@ def find_rdma_dev():
|
||||
netdev = ibclass + dev + "/ports/1/gid_attrs/ndevs/0"
|
||||
with open(netdev) as fp:
|
||||
addrs = netifaces.ifaddresses(fp.readline().strip("\n"))
|
||||
if netifaces.AF_INET not in addrs:
|
||||
if netifaces.AF_INET in addrs:
|
||||
ipaddr = addrs[netifaces.AF_INET][0]["addr"]
|
||||
elif netifaces.AF_INET6 in addrs:
|
||||
ipaddr = addrs[netifaces.AF_INET6][0]["addr"]
|
||||
else:
|
||||
continue
|
||||
ipaddr = addrs[netifaces.AF_INET][0]["addr"]
|
||||
print("Valkey Over RDMA test prepare " + dev + " <" + ipaddr + "> [OK]")
|
||||
return ipaddr
|
||||
except os.error:
|
||||
|
||||
@@ -124,8 +124,9 @@ proc assert_refcount_morethan {key ref} {
|
||||
|
||||
# Wait for the specified condition to be true, with the specified number of
|
||||
# max retries and delay between retries. Otherwise the 'elsescript' is
|
||||
# executed.
|
||||
proc wait_for_condition {maxtries delay e _else_ elsescript} {
|
||||
# executed. If 'debugscript' is provided, it is executed after failure of
|
||||
# the confition (before the retry delay).
|
||||
proc wait_for_condition {maxtries delay e _else_ elsescript {_debug_ ""} {debugscript ""}} {
|
||||
while {[incr maxtries -1] >= 0} {
|
||||
set errcode [catch {uplevel 1 [list expr $e]} result]
|
||||
if {$errcode == 0} {
|
||||
@@ -133,6 +134,9 @@ proc wait_for_condition {maxtries delay e _else_ elsescript} {
|
||||
} else {
|
||||
return -code $errcode $result
|
||||
}
|
||||
if {$_debug_ == "debug"} {
|
||||
uplevel 1 $debugscript
|
||||
}
|
||||
after $delay
|
||||
}
|
||||
if {$maxtries == -1} {
|
||||
|
||||
@@ -1306,3 +1306,41 @@ tags {acl external:skip} {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set server_path [tmpdir "server.acl"]
|
||||
exec cp -f tests/assets/user.acl $server_path
|
||||
start_server [list overrides [list "dir" $server_path "aclfile" "user.acl"] tags {"repl external:skip"}] {
|
||||
set primary [srv 0 client]
|
||||
set primary_host [srv 0 host]
|
||||
set primary_port [srv 0 port]
|
||||
|
||||
test "Test ACL LOAD works on primary" {
|
||||
exec cp -f tests/assets/user.acl $server_path
|
||||
$primary ACL setuser harry on nopass resetchannels &test +@all ~*
|
||||
$primary ACL save
|
||||
$primary ACL load
|
||||
$primary AUTH harry anything
|
||||
}
|
||||
|
||||
start_server [list overrides [list "dir" $server_path "aclfile" "user.acl"]] {
|
||||
set replica [srv 0 client]
|
||||
$replica replicaof $primary_host $primary_port
|
||||
|
||||
test "Test ACL LOAD works on replica" {
|
||||
# wait for replication to be in sync
|
||||
wait_for_condition 50 100 {
|
||||
[lindex [$replica role] 0] eq {slave} &&
|
||||
[string match {*master_link_status:up*} [$replica info replication]]
|
||||
} else {
|
||||
fail "Can't turn the instance into a replica"
|
||||
}
|
||||
|
||||
# Check ACL LOAD works as expected
|
||||
exec cp -f tests/assets/user.acl $server_path
|
||||
$replica ACL setuser joe on nopass resetchannels &test +@all ~*
|
||||
$replica ACL save
|
||||
$replica ACL load
|
||||
$replica AUTH joe anything
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,7 +631,7 @@ start_server {tags {"bitops"}} {
|
||||
}
|
||||
|
||||
run_solo {bitops-large-memory} {
|
||||
start_server {tags {"bitops"}} {
|
||||
start_server {tags {"bitops large-memory"}} {
|
||||
test "BIT pos larger than UINT_MAX" {
|
||||
set bytes [expr (1 << 29) + 1]
|
||||
set bitpos [expr (1 << 32)]
|
||||
|
||||
+533
-665
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,17 @@ start_cluster 3 0 [list config_lines $modules] {
|
||||
} else {
|
||||
fail "node 2 or node 3 didn't receive cluster module message"
|
||||
}
|
||||
verify_log_message -1 "*DING (type 1) RECEIVED*Hey*" 0
|
||||
verify_log_message -2 "*DING (type 1) RECEIVED*Hey*" 0
|
||||
}
|
||||
|
||||
test "Cluster module receive message API - VM_RegisterClusterMessageReceiver" {
|
||||
wait_for_condition 50 100 {
|
||||
[CI 0 cluster_stats_messages_module_received] eq 2
|
||||
} else {
|
||||
fail "node 1 didn't receive DONG messages"
|
||||
}
|
||||
assert_equal 2 [count_log_message 0 "* <cluster> DONG (type 2) RECEIVED*"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -597,11 +597,13 @@ tags "modules" {
|
||||
|
||||
assert_equal [$master get k1] 1
|
||||
assert_equal [$master ttl k1] -1
|
||||
wait_for_ofs_sync $master $replica
|
||||
assert_equal [$replica get k1] 1
|
||||
assert_equal [$replica ttl k1] -1
|
||||
# Validate that replicated commands were "obeyed" from primary.
|
||||
assert_equal [$replica propagate-test.obeyed] 1
|
||||
$master propagate-test.incr k1
|
||||
wait_for_ofs_sync $master $replica
|
||||
assert_equal [$replica propagate-test.obeyed] 2
|
||||
}
|
||||
|
||||
|
||||
@@ -61,23 +61,6 @@ start_server {tags {"modules"}} {
|
||||
assert_equal $result $n
|
||||
}
|
||||
|
||||
test {Module stream XADD big fields doesn't create empty key} {
|
||||
set original_proto [config_get_set proto-max-bulk-len 2147483647] ;#2gb
|
||||
set original_query [config_get_set client-query-buffer-limit 2147483647] ;#2gb
|
||||
|
||||
r del mystream
|
||||
r write "*4\r\n\$10\r\nstream.add\r\n\$8\r\nmystream\r\n\$5\r\nfield\r\n"
|
||||
catch {
|
||||
write_big_bulk 1073741824 ;#1gb
|
||||
} err
|
||||
assert {$err eq "ERR StreamAdd failed"}
|
||||
assert_equal 0 [r exists mystream]
|
||||
|
||||
# restore defaults
|
||||
r config set proto-max-bulk-len $original_proto
|
||||
r config set client-query-buffer-limit $original_query
|
||||
} {OK} {large-memory}
|
||||
|
||||
test {Module stream iterator} {
|
||||
r del mystream
|
||||
set streamid1 [r xadd mystream * item 1 value a]
|
||||
@@ -174,3 +157,30 @@ start_server {tags {"modules"}} {
|
||||
assert_equal {OK} [r module unload stream]
|
||||
}
|
||||
}
|
||||
|
||||
run_solo {stream-large-memory} {
|
||||
set testmodule [file normalize tests/modules/stream.so]
|
||||
start_server {tags {"modules large-memory"}} {
|
||||
r module load $testmodule
|
||||
|
||||
test {Module stream XADD big fields doesn't create empty key} {
|
||||
set original_proto [config_get_set proto-max-bulk-len 2147483647] ;#2gb
|
||||
set original_query [config_get_set client-query-buffer-limit 2147483647] ;#2gb
|
||||
|
||||
r del mystream
|
||||
r write "*4\r\n\$10\r\nstream.add\r\n\$8\r\nmystream\r\n\$5\r\nfield\r\n"
|
||||
catch {
|
||||
write_big_bulk 1073741824 ;#1gb
|
||||
} err
|
||||
assert {$err eq "ERR StreamAdd failed"}
|
||||
assert_equal 0 [r exists mystream]
|
||||
|
||||
# restore defaults
|
||||
r config set proto-max-bulk-len $original_proto
|
||||
r config set client-query-buffer-limit $original_query
|
||||
} {OK} {large-memory}
|
||||
|
||||
assert_equal {OK} [r module unload stream]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -515,6 +515,7 @@ start_server {tags {"multi"}} {
|
||||
{select *}
|
||||
{set foo bar}
|
||||
}
|
||||
close_replication_stream $repl
|
||||
r replicaof no one
|
||||
} {OK} {needs:repl cluster:skip}
|
||||
|
||||
|
||||
@@ -412,6 +412,28 @@ start_server {tags {"pause network"}} {
|
||||
} {bar2}
|
||||
}
|
||||
|
||||
test "Test the randomkey command will not cause the server to get into an infinite loop during the client pause write" {
|
||||
# first, clear the database to avoid interference from existing keys on the test results
|
||||
r flushall
|
||||
|
||||
# then set a key with expire time
|
||||
r set key value px 3
|
||||
|
||||
# set pause-write model and wait key expired
|
||||
r client pause 10000 write
|
||||
after 5
|
||||
|
||||
wait_for_condition 50 100 {
|
||||
[r randomkey] == "key"
|
||||
} else {
|
||||
fail "execute randomkey failed, caused by the infinite loop"
|
||||
}
|
||||
|
||||
r client unpause
|
||||
assert_equal [r randomkey] {}
|
||||
|
||||
}
|
||||
|
||||
# Make sure we unpause at the end
|
||||
r client unpause
|
||||
}
|
||||
|
||||
@@ -241,6 +241,7 @@ start_server {tags {"tracking network logreqres:skip"}} {
|
||||
# make sure r is working resp 3
|
||||
r HELLO 3
|
||||
r CLIENT TRACKING on REDIRECT $redir_id
|
||||
set redir_error "tracking-redir-broken $redir_id"
|
||||
$rd_sg SET key1 1
|
||||
r GET key1
|
||||
$rd_redirection QUIT
|
||||
@@ -257,11 +258,12 @@ start_server {tags {"tracking network logreqres:skip"}} {
|
||||
|
||||
# Wait to read the tracking-redir-broken
|
||||
wait_for_condition 1000 50 {
|
||||
[lsearch -exact [r PING] "tracking-redir-broken"]
|
||||
[set response [r PING]] != "PONG"
|
||||
} else {
|
||||
fail "Failed to get redirect broken indication"
|
||||
}
|
||||
# Consume PING reply
|
||||
assert_equal $redir_error $response
|
||||
# Consume PING reply
|
||||
assert_equal PONG [r read]
|
||||
}
|
||||
|
||||
|
||||
@@ -282,7 +282,7 @@ foreach type {listpack quicklist} {
|
||||
}
|
||||
|
||||
run_solo {list-large-memory} {
|
||||
start_server [list overrides [list save ""] ] {
|
||||
start_server [list overrides [list save ""] tags {"large-memory"}] {
|
||||
|
||||
# test if the server supports such large configs (avoid 32 bit builds)
|
||||
catch {
|
||||
|
||||
@@ -1169,7 +1169,7 @@ foreach type {single multiple single_multiple} {
|
||||
}
|
||||
|
||||
run_solo {set-large-memory} {
|
||||
start_server [list overrides [list save ""] ] {
|
||||
start_server [list overrides [list save ""] tags {"large-memory"}] {
|
||||
|
||||
# test if the server supports such large configs (avoid 32 bit builds)
|
||||
catch {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# One XADD with one huge 5GB field
|
||||
# Expected to fail resulting in an empty stream
|
||||
run_solo {violations} {
|
||||
start_server [list overrides [list save ""] ] {
|
||||
start_server [list overrides [list save ""] tags {"large-memory"}] {
|
||||
test {XADD one huge field} {
|
||||
r config set proto-max-bulk-len 10000000000 ;#10gb
|
||||
r config set client-query-buffer-limit 10000000000 ;#10gb
|
||||
@@ -18,7 +18,7 @@ start_server [list overrides [list save ""] ] {
|
||||
# One XADD with one huge (exactly nearly) 4GB field
|
||||
# This uncovers the overflow in lpEncodeGetType
|
||||
# Expected to fail resulting in an empty stream
|
||||
start_server [list overrides [list save ""] ] {
|
||||
start_server [list overrides [list save ""] tags {"large-memory"}] {
|
||||
test {XADD one huge field - 1} {
|
||||
r config set proto-max-bulk-len 10000000000 ;#10gb
|
||||
r config set client-query-buffer-limit 10000000000 ;#10gb
|
||||
@@ -33,7 +33,7 @@ start_server [list overrides [list save ""] ] {
|
||||
}
|
||||
|
||||
# Gradually add big stream fields using repeated XADD calls
|
||||
start_server [list overrides [list save ""] ] {
|
||||
start_server [list overrides [list save ""] tags {"large-memory"}] {
|
||||
test {several XADD big fields} {
|
||||
r config set stream-node-max-bytes 0
|
||||
for {set j 0} {$j<10} {incr j} {
|
||||
@@ -46,7 +46,7 @@ start_server [list overrides [list save ""] ] {
|
||||
|
||||
# Add over 4GB to a single stream listpack (one XADD command)
|
||||
# Expected to fail resulting in an empty stream
|
||||
start_server [list overrides [list save ""] ] {
|
||||
start_server [list overrides [list save ""] tags {"large-memory"}] {
|
||||
test {single XADD big fields} {
|
||||
r write "*23\r\n\$4\r\nXADD\r\n\$1\r\nS\r\n\$1\r\n*\r\n"
|
||||
for {set j 0} {$j<10} {incr j} {
|
||||
@@ -63,7 +63,7 @@ start_server [list overrides [list save ""] ] {
|
||||
# Gradually add big hash fields using repeated HSET calls
|
||||
# This reproduces the overflow in the call to ziplistResize
|
||||
# Object will be converted to hashtable encoding
|
||||
start_server [list overrides [list save ""] ] {
|
||||
start_server [list overrides [list save ""] tags {"large-memory"}] {
|
||||
r config set hash-max-ziplist-value 1000000000 ;#1gb
|
||||
test {hash with many big fields} {
|
||||
for {set j 0} {$j<10} {incr j} {
|
||||
@@ -75,7 +75,7 @@ start_server [list overrides [list save ""] ] {
|
||||
|
||||
# Add over 4GB to a single hash field (one HSET command)
|
||||
# Object will be converted to hashtable encoding
|
||||
start_server [list overrides [list save ""] ] {
|
||||
start_server [list overrides [list save ""] tags {"large-memory"}] {
|
||||
test {hash with one huge field} {
|
||||
catch {r config set hash-max-ziplist-value 10000000000} ;#10gb
|
||||
r config set proto-max-bulk-len 10000000000 ;#10gb
|
||||
|
||||
+20
-2
@@ -1320,8 +1320,8 @@ acllog-max-len 128
|
||||
# replica-ignore-maxmemory yes
|
||||
|
||||
# The server reclaims expired keys in two ways: upon access when those keys are
|
||||
# found to be expired, and also in background, in what is called the
|
||||
# "active expire key". The key space is slowly and interactively scanned
|
||||
# found to be expired, and also in the background, in what is called the
|
||||
# "active expire key". The key space is slowly and incrementally scanned
|
||||
# looking for expired keys to reclaim, so that it is possible to free memory
|
||||
# of keys that are expired and will never be accessed again in a short time.
|
||||
#
|
||||
@@ -1841,6 +1841,24 @@ aof-timestamp-enabled no
|
||||
#
|
||||
# cluster-replica-no-failover no
|
||||
|
||||
# The timeout in milliseconds for cluster manual failover. If a manual failover
|
||||
# does not complete within the specified time, both the replica and the primary
|
||||
# will abort it.
|
||||
#
|
||||
# A manual failover is a special kind of failover that is usually executed when
|
||||
# there are no actual failures, and we wish to swap the current primary with one
|
||||
# of its replicas in a safe way, without any window for data loss.
|
||||
#
|
||||
# To avoid data loss, the primary and the replica need to wait for each other for
|
||||
# a period of time, the primary need to pause the clients writes to stop processing
|
||||
# traffic. The default failover timeout is 5000ms, it is possible to configure the
|
||||
# timeout and decide how long the primary will pause in the worst case scenario,
|
||||
# i.e. the manual failover timed out due to the insufficient votes.
|
||||
#
|
||||
# Check https://valkey.io/commands/cluster-failover/ for more information.
|
||||
#
|
||||
# cluster-manual-failover-timeout 5000
|
||||
|
||||
# This option, when set to yes, allows nodes to serve read traffic while the
|
||||
# cluster is in a down state, as long as it believes it owns the slots.
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user