Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b3117ecc1 | ||
|
|
4db70fb6d4 | ||
|
|
9ab3126921 | ||
|
|
cc06c87a13 | ||
|
|
7720ee494e | ||
|
|
13a8f3cd0f | ||
|
|
90751b822d | ||
|
|
ef1eae8223 | ||
|
|
ac1ffeb4d1 | ||
|
|
1886f70cf2 | ||
|
|
c02e7e178c | ||
|
|
c6936db7ae | ||
|
|
ab0d886681 | ||
|
|
b02cd3fe85 | ||
|
|
b8366ee935 | ||
|
|
b9ce578cc3 | ||
|
|
b84434282e | ||
|
|
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 |
@@ -1,44 +0,0 @@
|
||||
name: Generate target matrix.
|
||||
description: Matrix creation for building Valkey for different architectures and platforms.
|
||||
|
||||
inputs:
|
||||
ref:
|
||||
description: The commit, tag or branch of Valkey to checkout to determine what version to use.
|
||||
required: true
|
||||
outputs:
|
||||
x86_64-build-matrix:
|
||||
description: The x86_64 build matrix.
|
||||
value: ${{ steps.set-matrix.outputs.x86matrix }}
|
||||
arm64-build-matrix:
|
||||
description: The arm64 build matrix.
|
||||
value: ${{ steps.set-matrix.outputs.armmatrix }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Checkout code for version check
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
path: version-check
|
||||
|
||||
- name: Get targets
|
||||
run: |
|
||||
x86_arch=$(jq -c '[.linux_targets[] | select(.arch=="x86_64")]' .github/actions/generate-package-build-matrix/build-config.json)
|
||||
x86_matrix=$(echo "{ \"distro\" : $x86_arch }" | jq -c .)
|
||||
echo "X86_MATRIX=$x86_matrix" >> $GITHUB_ENV
|
||||
|
||||
arm_arch=$(jq -c '[.linux_targets[] | select(.arch=="arm64")]' .github/actions/generate-package-build-matrix/build-config.json)
|
||||
arm_matrix=$(echo "{ \"distro\" : $arm_arch }" | jq -c .)
|
||||
echo "ARM_MATRIX=$arm_matrix" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- id: set-matrix
|
||||
run: |
|
||||
echo $X86_MATRIX
|
||||
echo $X86_MATRIX| jq .
|
||||
echo "x86matrix=$X86_MATRIX" >> $GITHUB_OUTPUT
|
||||
echo $ARM_MATRIX
|
||||
echo $ARM_MATRIX| jq .
|
||||
echo "armmatrix=$ARM_MATRIX" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"linux_targets": [
|
||||
|
||||
{
|
||||
"arch": "x86_64",
|
||||
"target": "ubuntu-20.04",
|
||||
"type": "deb",
|
||||
"platform": "focal"
|
||||
},
|
||||
{
|
||||
"arch": "x86_64",
|
||||
"target": "ubuntu-22.04",
|
||||
"type": "deb",
|
||||
"platform": "jammy"
|
||||
},
|
||||
{
|
||||
"arch": "x86_64",
|
||||
"target": "ubuntu-24.04",
|
||||
"type": "deb",
|
||||
"platform": "noble"
|
||||
},
|
||||
{
|
||||
"arch": "arm64",
|
||||
"target": "ubuntu20.04",
|
||||
"type": "deb",
|
||||
"platform": "focal"
|
||||
},
|
||||
{
|
||||
"arch": "arm64",
|
||||
"target": "ubuntu22.04",
|
||||
"type": "deb",
|
||||
"platform": "jammy"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
name: 'Install TCL8'
|
||||
description: 'Installs tcl8 and tcltls from source on Fedora-based or uses the system package on others.'
|
||||
|
||||
inputs:
|
||||
matrix_name:
|
||||
description: 'The name of the matrix to check for fedora'
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
# Fedora 42 comes with Tcl 9 by default, but tcltls is currently incompatible with Tcl 9.
|
||||
# As a workaround, we install Tcl 8 and manually build tcltls 1.7.22 from source.
|
||||
# Once tcltls adds support for Tcl 9, this logic can be removed and system packages used instead.
|
||||
- run: |
|
||||
if [[ "${{ inputs.matrix_name }}" =~ "fedora" ]]; then
|
||||
dnf -y install tcl8 tcl8-devel gcc make awk openssl openssl-devel
|
||||
ln -s /usr/bin/tclsh8.6 /usr/bin/tclsh
|
||||
curl -LO https://core.tcl-lang.org/tcltls/uv/tcltls-1.7.22.tar.gz
|
||||
tar -xzf tcltls-1.7.22.tar.gz
|
||||
pushd tcltls-1.7.22
|
||||
./configure --with-tcl=/usr/lib64/tcl8.6
|
||||
make
|
||||
mkdir -p /usr/lib64/tcl8.6/tls1.7.22
|
||||
cp tcltls.so pkgIndex.tcl /usr/lib64/tcl8.6/tls1.7.22/
|
||||
popd
|
||||
else
|
||||
dnf -y install tcl tcltls
|
||||
fi
|
||||
./utils/gen-test-certs.sh
|
||||
shell: bash
|
||||
@@ -1,112 +0,0 @@
|
||||
name: Build Release Packages
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/build-release-packages.yml'
|
||||
- '.github/workflows/call-build-linux-arm-packages.yml'
|
||||
- '.github/workflows/call-build-linux-x86-packages.yml'
|
||||
- '.github/actions/generate-package-build-matrix/build-config.json'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version of Valkey to build
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# This job provides the version metadata from the tag for the other jobs to use.
|
||||
release-build-get-meta:
|
||||
name: Get metadata to build
|
||||
if: github.event_name == 'workflow_dispatch' || github.repository == 'valkey-io/valkey'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.get_version.outputs.VERSION }}
|
||||
is_test: ${{ steps.check-if-testing.outputs.IS_TEST }}
|
||||
steps:
|
||||
- run: |
|
||||
echo "Version: ${{ inputs.version || github.ref_name }}"
|
||||
shell: bash
|
||||
|
||||
# This step is to consolidate the three different triggers into a single "version"
|
||||
# 1. If manual dispatch - use the version provided.
|
||||
# 3. If tag trigger, use that tag.
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
VERSION=${{ github.ref_name }}
|
||||
else
|
||||
VERSION="${INPUT_VERSION}"
|
||||
fi
|
||||
if [ -z "${VERSION}" ]; then
|
||||
echo "Error: No version specified"
|
||||
exit 1
|
||||
fi
|
||||
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
env:
|
||||
# Use the dispatch variable in preference, if empty use the context ref_name which should
|
||||
# only ever be a tag
|
||||
INPUT_VERSION: ${{ inputs.version || github.ref_name }}
|
||||
|
||||
- name: Check if we are testing
|
||||
id: check-if-testing
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
echo "This is a test workflow -> We will upload to the Test S3 Bucket"
|
||||
echo "IS_TEST=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "This is a Release workflow -> We will upload to the Release S3 Bucket"
|
||||
echo "IS_TEST=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
generate-build-matrix:
|
||||
name: Generating build matrix
|
||||
if: github.event_name == 'workflow_dispatch' || github.repository == 'valkey-io/valkey'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
x86_64-build-matrix: ${{ steps.set-matrix.outputs.x86_64-build-matrix }}
|
||||
arm64-build-matrix: ${{ steps.set-matrix.outputs.arm64-build-matrix }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
# Set up the list of target to build so we can pass the JSON to the reusable job
|
||||
- uses: ./.github/actions/generate-package-build-matrix
|
||||
id: set-matrix
|
||||
with:
|
||||
ref: ${{ needs.release-build-get-meta.outputs.version }}
|
||||
|
||||
release-build-linux-x86-packages:
|
||||
needs:
|
||||
- release-build-get-meta
|
||||
- generate-build-matrix
|
||||
uses: ./.github/workflows/call-build-linux-x86-packages.yml
|
||||
with:
|
||||
version: ${{ needs.release-build-get-meta.outputs.version }}
|
||||
ref: ${{ inputs.version || github.ref_name }}
|
||||
build_matrix: ${{ needs.generate-build-matrix.outputs.x86_64-build-matrix }}
|
||||
region: us-west-2
|
||||
secrets:
|
||||
bucket_name: ${{ needs.release-build-get-meta.outputs.is_test == 'true' && secrets.AWS_S3_TEST_BUCKET || secrets.AWS_S3_BUCKET }}
|
||||
role_to_assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
|
||||
|
||||
release-build-linux-arm-packages:
|
||||
needs:
|
||||
- release-build-get-meta
|
||||
- generate-build-matrix
|
||||
uses: ./.github/workflows/call-build-linux-arm-packages.yml
|
||||
with:
|
||||
version: ${{ needs.release-build-get-meta.outputs.version }}
|
||||
ref: ${{ inputs.version || github.ref_name }}
|
||||
build_matrix: ${{ needs.generate-build-matrix.outputs.arm64-build-matrix }}
|
||||
region: us-west-2
|
||||
secrets:
|
||||
bucket_name: ${{ needs.release-build-get-meta.outputs.is_test == 'true' && secrets.AWS_S3_TEST_BUCKET || secrets.AWS_S3_BUCKET }}
|
||||
role_to_assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
|
||||
@@ -1,74 +0,0 @@
|
||||
name: Builds Linux arm binary packages into S3 bucket.
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: The version of Valkey to create.
|
||||
type: string
|
||||
required: true
|
||||
ref:
|
||||
description: The commit, tag or branch of Valkey to checkout for building that creates the version above.
|
||||
type: string
|
||||
required: true
|
||||
build_matrix:
|
||||
description: The build targets to produce as a JSON matrix.
|
||||
type: string
|
||||
required: true
|
||||
region:
|
||||
description: The AWS region to push packages into.
|
||||
type: string
|
||||
required: true
|
||||
secrets:
|
||||
bucket_name:
|
||||
description: The S3 bucket to push packages into.
|
||||
required: true
|
||||
role_to_assume:
|
||||
description: The role to assume for the S3 bucket.
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-valkey:
|
||||
# Capture source tarball and generate checksum for it
|
||||
name: Build package ${{ matrix.distro.target }} ${{ matrix.distro.arch }}
|
||||
runs-on: "ubuntu-latest"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(inputs.build_matrix) }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.version }}
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
aws-region: ${{ inputs.region }}
|
||||
role-to-assume: ${{ secrets.role_to_assume }}
|
||||
|
||||
- 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
|
||||
|
||||
- name: Create Tarball and SHA256sums
|
||||
run: |
|
||||
TAR_FILE_NAME=valkey-${{inputs.version}}-${{matrix.distro.platform}}-${{ matrix.distro.arch}}
|
||||
mkdir -p "$TAR_FILE_NAME/bin" "$TAR_FILE_NAME/share"
|
||||
rsync -av --exclude='*.c' --exclude='*.d' --exclude='*.o' src/valkey-* "$TAR_FILE_NAME/bin/"
|
||||
cp -v /home/runner/work/valkey/valkey/COPYING "$TAR_FILE_NAME/share/LICENSE"
|
||||
tar -czvf $TAR_FILE_NAME.tar.gz $TAR_FILE_NAME
|
||||
sha256sum $TAR_FILE_NAME.tar.gz > $TAR_FILE_NAME.tar.gz.sha256
|
||||
mkdir -p packages-files
|
||||
cp -rfv $TAR_FILE_NAME.tar* packages-files/
|
||||
|
||||
- name: Sync to S3
|
||||
run: aws s3 sync packages-files s3://${{ secrets.bucket_name }}/releases/
|
||||
@@ -1,72 +0,0 @@
|
||||
name: Builds Linux X86 binary packages into S3 bucket.
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: The version of Valkey to create.
|
||||
type: string
|
||||
required: true
|
||||
ref:
|
||||
description: The commit, tag or branch of Valkey to checkout for building that creates the version above.
|
||||
type: string
|
||||
required: true
|
||||
build_matrix:
|
||||
description: The build targets to produce as a JSON matrix.
|
||||
type: string
|
||||
required: true
|
||||
region:
|
||||
description: The AWS region to upload the packages to.
|
||||
type: string
|
||||
required: true
|
||||
secrets:
|
||||
bucket_name:
|
||||
description: The name of the S3 bucket to upload the packages to.
|
||||
required: true
|
||||
role_to_assume:
|
||||
description: The role to assume for the S3 bucket.
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-valkey:
|
||||
# Capture source tarball and generate checksum for it
|
||||
name: Build package ${{ matrix.distro.target }} ${{ matrix.distro.arch }}
|
||||
runs-on: ${{matrix.distro.target}}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(inputs.build_matrix) }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.version }}
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v4
|
||||
with:
|
||||
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
|
||||
run: make -C src all BUILD_TLS=yes USE_SYSTEMD=yes
|
||||
|
||||
- name: Create Tarball and SHA256sums
|
||||
run: |
|
||||
TAR_FILE_NAME=valkey-${{inputs.version}}-${{matrix.distro.platform}}-${{ matrix.distro.arch}}
|
||||
mkdir -p "$TAR_FILE_NAME/bin" "$TAR_FILE_NAME/share"
|
||||
rsync -av --exclude='*.c' --exclude='*.d' --exclude='*.o' src/valkey-* "$TAR_FILE_NAME/bin/"
|
||||
cp -v /home/runner/work/valkey/valkey/COPYING "$TAR_FILE_NAME/share/LICENSE"
|
||||
tar -czvf $TAR_FILE_NAME.tar.gz $TAR_FILE_NAME
|
||||
sha256sum $TAR_FILE_NAME.tar.gz > $TAR_FILE_NAME.tar.gz.sha256
|
||||
mkdir -p packages-files
|
||||
cp -rfv $TAR_FILE_NAME.tar* packages-files/
|
||||
|
||||
- name: Sync to S3
|
||||
run: aws s3 sync packages-files s3://${{ secrets.bucket_name }}/releases/
|
||||
+100
-15
@@ -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
|
||||
@@ -822,10 +876,12 @@ jobs:
|
||||
run: dnf -y install epel-release
|
||||
- name: make
|
||||
run: |
|
||||
dnf -y install gcc make procps-ng which /usr/bin/kill
|
||||
dnf -y install gcc make procps-ng openssl-devel openssl which /usr/bin/kill /usr/bin/awk
|
||||
make -j SERVER_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: dnf -y install tcl tcltls
|
||||
uses: ./.github/actions/rpm-distros-tcl8
|
||||
with:
|
||||
matrix_name: ${{ matrix.name }}
|
||||
- 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}}
|
||||
@@ -888,12 +944,12 @@ jobs:
|
||||
run: dnf -y install epel-release
|
||||
- name: make
|
||||
run: |
|
||||
dnf -y install make gcc openssl-devel openssl procps-ng which /usr/bin/kill
|
||||
dnf -y install make gcc openssl-devel openssl procps-ng which /usr/bin/kill /usr/bin/awk
|
||||
make -j BUILD_TLS=module SERVER_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
dnf -y install tcl tcltls
|
||||
./utils/gen-test-certs.sh
|
||||
uses: ./.github/actions/rpm-distros-tcl8
|
||||
with:
|
||||
matrix_name: ${{ matrix.name }}
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'valkey')
|
||||
run: |
|
||||
@@ -960,12 +1016,12 @@ jobs:
|
||||
run: dnf -y install epel-release
|
||||
- name: make
|
||||
run: |
|
||||
dnf -y install make gcc openssl-devel openssl procps-ng which /usr/bin/kill
|
||||
dnf -y install make gcc openssl-devel openssl procps-ng which /usr/bin/kill /usr/bin/awk
|
||||
make -j BUILD_TLS=module SERVER_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
dnf -y install tcl tcltls
|
||||
./utils/gen-test-certs.sh
|
||||
uses: ./.github/actions/rpm-distros-tcl8
|
||||
with:
|
||||
matrix_name: ${{ matrix.name }}
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'valkey')
|
||||
run: |
|
||||
@@ -1262,7 +1318,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 }}"
|
||||
}
|
||||
+219
-11
@@ -1,16 +1,224 @@
|
||||
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.1 - Released Wed 23 Apr 2025
|
||||
================================================================================
|
||||
|
||||
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:
|
||||
Upgrade urgency SECURITY: This release includes security fixes we recommend you
|
||||
apply as soon as possible.
|
||||
|
||||
https://valkey.io/download/
|
||||
Bug fixes
|
||||
=========
|
||||
* Fix the build on less common platforms in zmalloc.c (#1922)
|
||||
* fix: add samples to stream object consumer trees (#1825)
|
||||
* Fix crash during TLS handshake with I/O threads (#1955)
|
||||
* Fix cluster slot stats assertion during promotion of replica (#1950)
|
||||
* Fix panic in primary when blocking shutdown after previous block with timeout (#1948)
|
||||
* Ignore stale gossip packets that arrive out of order (#1777)
|
||||
* Fix incorrect lag reported in XINFO GROUPS (#1952)
|
||||
* Fix engine crash on module client blocking during keyspace events (#1819)
|
||||
* Avoid shard id update of replica if not matching with primary shard id (#573)
|
||||
* Only enable defrag for vendored jemalloc (#1985)
|
||||
* Allow scripts to support null characters again (#1984)
|
||||
|
||||
More information is available at https://valkey.io
|
||||
Security fixes
|
||||
==============
|
||||
* CVE-2025-21605 Limit output buffer for unauthenticated clients (#1994)
|
||||
|
||||
Happy hacking!
|
||||
================================================================================
|
||||
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.
|
||||
|
||||
Behavior Changes
|
||||
================
|
||||
* Hide input buffer data from being logged on protocol error when hide-user-data-from-log is enabled (#1889)
|
||||
|
||||
Bug fixes
|
||||
=========
|
||||
* Fix a bug in VM_GetCurrentUserName which leads to engine crash when no valid username provided (#1885)
|
||||
|
||||
================================================================================
|
||||
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.
|
||||
|
||||
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"
|
||||
|
||||
+5
@@ -5,6 +5,11 @@ objroot=$1
|
||||
cat <<EOF
|
||||
#ifndef JEMALLOC_H_
|
||||
#define JEMALLOC_H_
|
||||
|
||||
/* A macro that is used to indicate that this the jemalloc vendored with the project
|
||||
* and has been tested with active defragmentation. */
|
||||
#define VALKEY_VENDORED_JEMALLOC 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
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
|
||||
|
||||
@@ -495,8 +495,7 @@ void ACLFreeUserAndKillClients(user *u) {
|
||||
* this may result in some security hole: it's much
|
||||
* more defensive to set the default user and put
|
||||
* it in non authenticated mode. */
|
||||
c->user = DefaultUser;
|
||||
c->flag.authenticated = 0;
|
||||
clientSetUser(c, DefaultUser, 0);
|
||||
/* We will write replies to this client later, so we can't
|
||||
* close it directly even if async. */
|
||||
if (c == server.current_client) {
|
||||
@@ -1455,8 +1454,8 @@ void addAuthErrReply(client *c, robj *err) {
|
||||
* The return value is AUTH_OK on success (valid username / password pair) & AUTH_ERR otherwise. */
|
||||
static int checkPasswordBasedAuth(client *c, robj *username, robj *password) {
|
||||
if (ACLCheckUserCredentials(username, password) == C_OK) {
|
||||
c->flag.authenticated = 1;
|
||||
c->user = ACLGetUserByName(username->ptr, sdslen(username->ptr));
|
||||
user *user = ACLGetUserByName(username->ptr, sdslen(username->ptr));
|
||||
clientSetUser(c, user, 1);
|
||||
moduleNotifyUserChanged(c);
|
||||
return AUTH_OK;
|
||||
} else {
|
||||
@@ -2377,6 +2376,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));
|
||||
|
||||
+16
-5
@@ -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
|
||||
@@ -53,6 +55,7 @@ list *listCreate(void) {
|
||||
|
||||
/* Remove all the elements from the list without destroying the list itself. */
|
||||
void listEmpty(list *list) {
|
||||
if (!list) return;
|
||||
unsigned long len;
|
||||
listNode *current, *next;
|
||||
|
||||
@@ -72,7 +75,6 @@ void listEmpty(list *list) {
|
||||
*
|
||||
* This function can't fail. */
|
||||
void listRelease(list *list) {
|
||||
if (!list) return;
|
||||
listEmpty(list);
|
||||
zfree(list);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include <jemalloc/jemalloc.h>
|
||||
/* We can enable the server defrag capabilities only if we are using Jemalloc
|
||||
* and the version that has the experimental.utilization namespace in mallctl . */
|
||||
#if (defined(JEMALLOC_VERSION_MAJOR) && \
|
||||
#if (defined(VALKEY_VENDORED_JEMALLOC) && defined(JEMALLOC_VERSION_MAJOR) && \
|
||||
(JEMALLOC_VERSION_MAJOR > 5 || \
|
||||
(JEMALLOC_VERSION_MAJOR == 5 && JEMALLOC_VERSION_MINOR > 2) || \
|
||||
(JEMALLOC_VERSION_MAJOR == 5 && JEMALLOC_VERSION_MINOR == 2 && JEMALLOC_VERSION_BUGFIX >= 1))) || \
|
||||
|
||||
@@ -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"
|
||||
|
||||
+119
-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.
|
||||
*
|
||||
@@ -1149,6 +1253,7 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
}
|
||||
}
|
||||
|
||||
initDeferredReplyBuffer(c);
|
||||
addReplyArrayLen(c, numops);
|
||||
|
||||
/* Actually process the operations. */
|
||||
@@ -1260,6 +1365,7 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
notifyKeyspaceEvent(NOTIFY_STRING, "setbit", c->argv[1], c->db->id);
|
||||
server.dirty += changes;
|
||||
}
|
||||
commitDeferredReplyBuffer(c, 1);
|
||||
zfree(ops);
|
||||
}
|
||||
|
||||
|
||||
+9
-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
|
||||
@@ -649,6 +653,8 @@ void blockPostponeClient(client *c) {
|
||||
|
||||
/* Block client due to shutdown command */
|
||||
void blockClientShutdown(client *c) {
|
||||
initClientBlockingState(c);
|
||||
c->bstate->timeout = 0;
|
||||
blockClient(c, BLOCKED_SHUTDOWN);
|
||||
}
|
||||
|
||||
|
||||
+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);
|
||||
|
||||
+66
-33
@@ -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.
|
||||
@@ -811,10 +815,7 @@ int clusterLoadConfig(char *filename) {
|
||||
return C_OK;
|
||||
|
||||
fmterr:
|
||||
serverLog(LL_WARNING, "Unrecoverable error: corrupted cluster config file \"%s\".", line);
|
||||
zfree(line);
|
||||
if (fp) fclose(fp);
|
||||
exit(1);
|
||||
serverPanic("Unrecoverable error: corrupted cluster config file \"%s\".", line);
|
||||
}
|
||||
|
||||
/* Cluster node configuration is exactly the same as CLUSTER NODES output.
|
||||
@@ -1085,6 +1086,27 @@ static void updateAnnouncedClientIpV6(clusterNode *node, char *value) {
|
||||
}
|
||||
|
||||
static void updateShardId(clusterNode *node, const char *shard_id) {
|
||||
/* Ensure replica shard IDs match their primary's to maintain cluster consistency.
|
||||
*
|
||||
* Shard ID updates must prioritize the primary, then propagate to replicas.
|
||||
* This is critical due to the eventual consistency of shard IDs during cluster
|
||||
* expansion. New replicas might replicate from a primary before fully
|
||||
* synchronizing shard IDs with the rest of the cluster.
|
||||
*
|
||||
* Without this enforcement, a temporary inconsistency can arise where a
|
||||
* replica's shard ID diverges from its primary's. This inconsistency is
|
||||
* persisted in the primary's nodes.conf file. While this divergence will
|
||||
* eventually resolve, if the primary crashes beforehand, it will enter a
|
||||
* crash-restart loop due to the mismatch in its nodes.conf. */
|
||||
if (shard_id && nodeIsReplica(node) &&
|
||||
memcmp(clusterNodeGetPrimary(node)->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
|
||||
serverLog(
|
||||
LL_NOTICE,
|
||||
"Shard id %.40s update request for node id %.40s diverges from existing primary shard id %.40s, rejecting!",
|
||||
shard_id, node->name, clusterNodeGetPrimary(node)->shard_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shard_id && memcmp(node->shard_id, shard_id, CLUSTER_NAMELEN) != 0) {
|
||||
clusterRemoveNodeFromShard(node);
|
||||
memcpy(node->shard_id, shard_id, CLUSTER_NAMELEN);
|
||||
@@ -1493,7 +1515,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 +1956,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 +2019,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 +2219,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 +3061,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 +3231,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;
|
||||
}
|
||||
@@ -3515,14 +3535,17 @@ int clusterProcessPacket(clusterLink *link) {
|
||||
/* Primary turned into a replica! Reconfigure the node. */
|
||||
if (sender_claimed_primary && areInSameShard(sender_claimed_primary, sender)) {
|
||||
/* `sender` was a primary and was in the same shard as its new primary */
|
||||
if (sender->configEpoch > sender_claimed_config_epoch) {
|
||||
if (nodeEpoch(sender_claimed_primary) > sender_claimed_config_epoch) {
|
||||
serverLog(LL_NOTICE,
|
||||
"Ignore stale message from %.40s (%s) in shard %.40s;"
|
||||
" gossip config epoch: %llu, current config epoch: %llu",
|
||||
sender->name, sender->human_nodename, sender->shard_id,
|
||||
(unsigned long long)sender_claimed_config_epoch,
|
||||
(unsigned long long)sender->configEpoch);
|
||||
} else {
|
||||
(unsigned long long)nodeEpoch(sender_claimed_primary));
|
||||
/* This packet is stale so we avoid processing it anymore. Otherwise
|
||||
* this may cause a primary-replica chain issue. */
|
||||
return 1;
|
||||
} else if (nodeIsReplica(sender_claimed_primary)) {
|
||||
/* `primary` is still a `replica` in this observer node's view;
|
||||
* update its role and configEpoch */
|
||||
clusterSetNodeAsPrimary(sender_claimed_primary);
|
||||
@@ -3716,9 +3739,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 +4796,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 +4985,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 +5113,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 +6243,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 +6266,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 +6499,7 @@ clusterNode *getMyClusterNode(void) {
|
||||
return server.cluster->myself;
|
||||
}
|
||||
|
||||
int clusterManualFailoverTimeLimit(void) {
|
||||
mstime_t clusterManualFailoverTimeLimit(void) {
|
||||
return server.cluster->mf_end;
|
||||
}
|
||||
|
||||
@@ -6894,6 +6922,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 +6936,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 +7153,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 +7316,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
|
||||
@@ -149,10 +148,14 @@ static void clusterSlotStatsUpdateNetworkBytesOutForReplication(long long len) {
|
||||
client *c = server.current_client;
|
||||
if (c == NULL || !canAddNetworkBytesOut(c)) return;
|
||||
|
||||
/* We multiply the bytes len by the number of replicas to account for us broadcasting to multiple replicas at once. */
|
||||
len *= (long long)listLength(server.replicas);
|
||||
serverAssert(c->slot >= 0 && c->slot < CLUSTER_SLOTS);
|
||||
serverAssert(nodeIsPrimary(server.cluster->myself));
|
||||
if (len < 0) serverAssert(server.cluster->slot_stats[c->slot].network_bytes_out >= (uint64_t)llabs(len));
|
||||
server.cluster->slot_stats[c->slot].network_bytes_out += (len * listLength(server.replicas));
|
||||
/* We sometimes want to adjust the counter downwards (for example when we want to undo accounting for
|
||||
* SELECT commands that don't belong to any slot) so let's make sure we don't underflow the counter. */
|
||||
serverAssert(len >= 0 || server.cluster->slot_stats[c->slot].network_bytes_out >= (uint64_t)-len);
|
||||
server.cluster->slot_stats[c->slot].network_bytes_out += len;
|
||||
}
|
||||
|
||||
/* Increment network bytes out for replication stream. This method will increment `len` value times the active replica
|
||||
|
||||
+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
|
||||
|
||||
@@ -508,6 +508,9 @@ void debugCommand(client *c) {
|
||||
" Grace period in seconds for replica main channel to establish psync.",
|
||||
"DICT-RESIZING <0|1>",
|
||||
" Enable or disable the main dict and expire dict resizing.",
|
||||
"CLIENT-ENFORCE-REPLY-LIST <0|1>",
|
||||
"When set to 1, it enforces the use of the client reply list directly",
|
||||
" and avoids using the client's static buffer.",
|
||||
NULL};
|
||||
addExtendedReplyHelp(c, help, clusterDebugCommandExtendedHelp());
|
||||
} else if (!strcasecmp(c->argv[1]->ptr, "segfault")) {
|
||||
@@ -1020,6 +1023,9 @@ void debugCommand(client *c) {
|
||||
} else if (!strcasecmp(c->argv[1]->ptr, "dict-resizing") && c->argc == 3) {
|
||||
server.dict_resizing = atoi(c->argv[2]->ptr);
|
||||
addReply(c, shared.ok);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr, "client-enforce-reply-list") && c->argc == 3) {
|
||||
server.debug_client_enforce_reply_list = atoi(c->argv[2]->ptr);
|
||||
addReply(c, shared.ok);
|
||||
} else if (!handleDebugClusterCommand(c)) {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
|
||||
+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.
|
||||
@@ -406,6 +411,7 @@ static int evalRegisterNewScript(client *c, robj *body, char **sha) {
|
||||
scriptingEngineCallCompileCode(engine,
|
||||
VMSE_EVAL,
|
||||
(sds)body->ptr + shebang_len,
|
||||
sdslen(body->ptr) - shebang_len,
|
||||
0,
|
||||
&num_compiled_functions,
|
||||
&_err);
|
||||
|
||||
+8
-4
@@ -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"
|
||||
|
||||
@@ -677,6 +682,9 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
return;
|
||||
} else {
|
||||
obj = setExpire(c, c->db, key, when);
|
||||
signalModifiedKey(c, c->db, key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "expire", key, c->db->id);
|
||||
server.dirty++;
|
||||
addReply(c, shared.cone);
|
||||
/* Propagate as PEXPIREAT millisecond-timestamp
|
||||
* Only rewrite the command arg if not already PEXPIREAT */
|
||||
@@ -690,10 +698,6 @@ void expireGenericCommand(client *c, long long basetime, int unit) {
|
||||
rewriteClientCommandArgument(c, 2, when_obj);
|
||||
decrRefCount(when_obj);
|
||||
}
|
||||
|
||||
signalModifiedKey(c, c->db, key);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "expire", key, c->db->id);
|
||||
server.dirty++;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -1007,6 +1012,7 @@ sds functionsCreateWithLibraryCtx(sds code, int replace, sds *err, functionsLibC
|
||||
scriptingEngineCallCompileCode(engine,
|
||||
VMSE_FUNCTION,
|
||||
md.code,
|
||||
sdslen(md.code),
|
||||
timeout,
|
||||
&num_compiled_functions,
|
||||
&compile_error);
|
||||
|
||||
+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"
|
||||
|
||||
+3
-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"
|
||||
@@ -574,6 +574,7 @@ void trySendPollJobToIOThreads(void) {
|
||||
static void ioThreadAccept(void *data) {
|
||||
client *c = (client *)data;
|
||||
connAccept(c->conn, NULL);
|
||||
atomic_thread_fence(memory_order_release);
|
||||
c->io_read_state = CLIENT_COMPLETED_IO;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright Valkey Contributors.
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
@@ -195,6 +195,7 @@ static compiledFunction **luaEngineCompileCode(ValkeyModuleCtx *module_ctx,
|
||||
engineCtx *engine_ctx,
|
||||
subsystemType type,
|
||||
const char *code,
|
||||
size_t code_len,
|
||||
size_t timeout,
|
||||
size_t *out_num_compiled_functions,
|
||||
robj **err) {
|
||||
@@ -207,7 +208,7 @@ static compiledFunction **luaEngineCompileCode(ValkeyModuleCtx *module_ctx,
|
||||
if (type == VMSE_EVAL) {
|
||||
lua_State *lua = lua_engine_ctx->eval_lua;
|
||||
|
||||
if (luaL_loadbuffer(lua, code, strlen(code), "@user_script")) {
|
||||
if (luaL_loadbuffer(lua, code, code_len, "@user_script")) {
|
||||
sds error = sdscatfmt(sdsempty(), "Error compiling script (new function): %s", lua_tostring(lua, -1));
|
||||
*err = createObject(OBJ_STRING, error);
|
||||
lua_pop(lua, 1);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
+131
-33
@@ -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
|
||||
@@ -193,8 +198,8 @@ typedef struct ValkeyModuleCtx ValkeyModuleCtx;
|
||||
#define VALKEYMODULE_CTX_NEW_CLIENT (1 << 7) /* Free client object when the \
|
||||
context is destroyed */
|
||||
#define VALKEYMODULE_CTX_CHANNELS_POS_REQUEST (1 << 8)
|
||||
#define VALKEYMODULE_CTX_COMMAND (1 << 9) /* Context created to serve a command from call() or AOF (which calls cmd->proc directly) */
|
||||
|
||||
#define VALKEYMODULE_CTX_COMMAND (1 << 9) /* Context created to serve a command from call() or AOF (which calls cmd->proc directly) */
|
||||
#define VALKEYMODULE_CTX_KEYSPACE_NOTIFICATION (1 << 10) /* Context created a keyspace notification event */
|
||||
|
||||
/* This represents a key opened with VM_OpenKey(). */
|
||||
struct ValkeyModuleKey {
|
||||
@@ -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. */
|
||||
@@ -7777,6 +7795,8 @@ void unblockClientFromModule(client *c) {
|
||||
* in that case the privdata argument is disregarded, because we pass the
|
||||
* reply callback the privdata that is set here while blocking.
|
||||
*
|
||||
* For details on return values and error codes, see the comment block for
|
||||
* VM_BlockClient.
|
||||
*/
|
||||
ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx,
|
||||
ValkeyModuleCmdFunc reply_callback,
|
||||
@@ -7789,8 +7809,27 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx,
|
||||
void *privdata,
|
||||
int flags) {
|
||||
client *c = ctx->client;
|
||||
if (c->flag.blocked || getClientType(c) != CLIENT_TYPE_NORMAL || c->flag.deny_blocking) {
|
||||
/* Early return if duplicate block attempt or client is not normal or
|
||||
* client is set to deny blocking. */
|
||||
errno = ENOTSUP;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ctx->flags & (VALKEYMODULE_CTX_TEMP_CLIENT | VALKEYMODULE_CTX_NEW_CLIENT)) {
|
||||
/* Temporary clients can't be blocked */
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
int is_keyspace_notification = ctx->flags & (VALKEYMODULE_CTX_KEYSPACE_NOTIFICATION);
|
||||
int islua = scriptIsRunning();
|
||||
int ismulti = server.in_exec;
|
||||
if ((islua || ismulti) && is_keyspace_notification) {
|
||||
/* Avoid blocking within transactions when context initiated by
|
||||
* keyspace notification. */
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
initClientBlockingState(c);
|
||||
|
||||
c->bstate->module_blocked_handle = zmalloc(sizeof(ValkeyModuleBlockedClient));
|
||||
@@ -7846,6 +7885,11 @@ ValkeyModuleBlockedClient *moduleBlockClient(ValkeyModuleCtx *ctx,
|
||||
c->bstate->timeout = timeout;
|
||||
blockClient(c, BLOCKED_MODULE);
|
||||
}
|
||||
/* Defer response until after being unblocked for a context originated from
|
||||
* keyspace notification events */
|
||||
if (is_keyspace_notification) {
|
||||
initDeferredReplyBuffer(c);
|
||||
}
|
||||
}
|
||||
return bc;
|
||||
}
|
||||
@@ -8073,14 +8117,27 @@ int moduleTryServeClientBlockedOnKey(client *c, robj *key) {
|
||||
* free_privdata: called in order to free the private data that is passed
|
||||
* by ValkeyModule_UnblockClient() call.
|
||||
*
|
||||
* Note: ValkeyModule_UnblockClient should be called for every blocked client,
|
||||
* even if client was killed, timed-out or disconnected. Failing to do so
|
||||
* will result in memory leaks.
|
||||
* Notes:
|
||||
* 1. ValkeyModule_UnblockClient should be called for every blocked client,
|
||||
* even if client was killed, timed-out or disconnected. Failing to do so
|
||||
* will result in memory leaks.
|
||||
* 2. Attempting to block the client on keyspace event notification in versions
|
||||
* prior to 8.1.1 leads to a crash.
|
||||
*
|
||||
* There are some cases where ValkeyModule_BlockClient() cannot be used:
|
||||
*
|
||||
* 1. If the client is a Lua script.
|
||||
* 2. If the client is executing a MULTI block.
|
||||
* 3. If the client is a temporary module client.
|
||||
* 4. If the client is already blocked.
|
||||
*
|
||||
* In cases 1 and 2, a call to ValkeyModule_BlockClient() will **not** block the
|
||||
* client, but instead produce a specific error reply. Note that if the
|
||||
* BlockClient call originated from within a keyspace notification, no error
|
||||
* reply is generated but nullptr is returned while the errno is set to EINVAL.
|
||||
*
|
||||
* In case 3 and 4, a call to ValkeyModule_BlockClient() are no-op, returning
|
||||
* nullptr. errno is set to EINVAL for case 3 while ENOTSUP for case 4.
|
||||
*
|
||||
* In these cases, a call to ValkeyModule_BlockClient() will **not** block the
|
||||
* client, but instead produce a specific error reply.
|
||||
@@ -8272,6 +8329,12 @@ int moduleClientIsBlockedOnKeys(client *c) {
|
||||
* needs to be passed to the client, included but not limited some slow
|
||||
* to compute reply or some reply obtained via networking.
|
||||
*
|
||||
* Returns VALKEYMODULE_OK on success. On failure, VALKEYMODULE_ERR is returned
|
||||
* and `errno` is set as follows:
|
||||
*
|
||||
* - EINVAL if bc is NULL.
|
||||
* - ENOTSUP if bc contains `blocked on keys` but its timeout callback is NULL.
|
||||
*
|
||||
* Note 1: this function can be called from threads spawned by the module.
|
||||
*
|
||||
* Note 2: when we unblock a client that is blocked for keys using the API
|
||||
@@ -8282,10 +8345,17 @@ int moduleClientIsBlockedOnKeys(client *c) {
|
||||
* ValkeyModule_BlockClientOnKeys() is accessible from the timeout
|
||||
* callback via VM_GetBlockedClientPrivateData). */
|
||||
int VM_UnblockClient(ValkeyModuleBlockedClient *bc, void *privdata) {
|
||||
if (!bc) {
|
||||
errno = EINVAL;
|
||||
return VALKEYMODULE_ERR;
|
||||
}
|
||||
if (bc->blocked_on_keys) {
|
||||
/* In theory the user should always pass the timeout handler as an
|
||||
* argument, but better to be safe than sorry. */
|
||||
if (bc->timeout_callback == NULL) return VALKEYMODULE_ERR;
|
||||
if (bc->timeout_callback == NULL) {
|
||||
errno = ENOTSUP;
|
||||
return VALKEYMODULE_ERR;
|
||||
}
|
||||
if (bc->unblocked) return VALKEYMODULE_OK;
|
||||
if (bc->client) moduleBlockedClientTimedOut(bc->client, 1);
|
||||
}
|
||||
@@ -8374,11 +8444,17 @@ void moduleHandleBlockedClients(void) {
|
||||
moduleInvokeFreePrivDataCallback(c, bc);
|
||||
}
|
||||
|
||||
/* It is possible that this blocked client object accumulated
|
||||
* replies to send to the client in a thread safe context.
|
||||
* We need to glue such replies to the client output buffer and
|
||||
* free the temporary client we just used for the replies. */
|
||||
if (c) AddReplyFromClient(c, bc->reply_client);
|
||||
if (c) {
|
||||
/* Replies which were added after the client is blocked by a module
|
||||
* are accumulated separately. We need to transmit those replies
|
||||
* to the client. */
|
||||
commitDeferredReplyBuffer(c, 0);
|
||||
/* It is possible that this blocked client object accumulated
|
||||
* replies to send to the client in a thread safe context.
|
||||
* We need to glue such replies to the client output buffer and
|
||||
* free the temporary client we just used for the replies. */
|
||||
AddReplyFromClient(c, bc->reply_client);
|
||||
}
|
||||
moduleReleaseTempClient(bc->reply_client);
|
||||
moduleReleaseTempClient(bc->thread_safe_ctx_client);
|
||||
|
||||
@@ -8474,9 +8550,10 @@ void moduleBlockedClientTimedOut(client *c, int from_module) {
|
||||
|
||||
moduleFreeContext(&ctx);
|
||||
|
||||
if (!from_module)
|
||||
if (!from_module) {
|
||||
updateStatsOnUnblock(c, bc->background_duration, 0,
|
||||
((server.stat_total_error_replies != prev_error_replies) ? ERROR_COMMAND_FAILED : 0));
|
||||
}
|
||||
|
||||
/* For timeout events, we do not want to call the disconnect callback,
|
||||
* because the blocked client will be automatically disconnected in
|
||||
@@ -8822,12 +8899,16 @@ int VM_NotifyKeyspaceEvent(ValkeyModuleCtx *ctx, int type, const char *event, Va
|
||||
return VALKEYMODULE_OK;
|
||||
}
|
||||
|
||||
unsigned long moduleNotifyKeyspaceSubscribersCnt(void) {
|
||||
return listLength(moduleKeyspaceSubscribers);
|
||||
}
|
||||
|
||||
/* Dispatcher for keyspace notifications to module subscriber functions.
|
||||
* This gets called only if at least one module requested to be notified on
|
||||
* keyspace notifications */
|
||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid) {
|
||||
/* Don't do anything if there aren't any subscribers */
|
||||
if (listLength(moduleKeyspaceSubscribers) == 0) return;
|
||||
if (moduleNotifyKeyspaceSubscribersCnt() == 0) return;
|
||||
|
||||
/* Ugly hack to handle modules which use write commands from within
|
||||
* notify_callback, which they should NOT do!
|
||||
@@ -8862,8 +8943,14 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid)
|
||||
if ((sub->event_mask & type) &&
|
||||
(sub->active == 0 || (sub->module->options & VALKEYMODULE_OPTIONS_ALLOW_NESTED_KEYSPACE_NOTIFICATIONS))) {
|
||||
ValkeyModuleCtx ctx;
|
||||
moduleCreateContext(&ctx, sub->module, VALKEYMODULE_CTX_TEMP_CLIENT);
|
||||
if (server.executing_client == NULL) {
|
||||
moduleCreateContext(&ctx, sub->module, VALKEYMODULE_CTX_TEMP_CLIENT);
|
||||
} else {
|
||||
moduleCreateContext(&ctx, sub->module, VALKEYMODULE_CTX_NONE);
|
||||
ctx.client = server.executing_client;
|
||||
}
|
||||
selectDb(ctx.client, dbid);
|
||||
ctx.flags |= VALKEYMODULE_CTX_KEYSPACE_NOTIFICATION;
|
||||
|
||||
/* mark the handler as active to avoid reentrant loops.
|
||||
* If the subscriber performs an action triggering itself,
|
||||
@@ -8950,7 +9037,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) {
|
||||
@@ -9610,8 +9703,7 @@ void revokeClientAuthentication(client *c) {
|
||||
* is eventually freed we don't rely on the module to still exist. */
|
||||
moduleNotifyUserChanged(c);
|
||||
|
||||
c->user = DefaultUser;
|
||||
c->flag.authenticated = 0;
|
||||
clientSetUser(c, DefaultUser, 0);
|
||||
/* We will write replies to this client later, so we can't close it
|
||||
* directly even if async. */
|
||||
if (c == server.current_client) {
|
||||
@@ -9737,8 +9829,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));
|
||||
}
|
||||
|
||||
@@ -9932,8 +10031,7 @@ static int authenticateClientWithUser(ValkeyModuleCtx *ctx,
|
||||
|
||||
moduleNotifyUserChanged(ctx->client);
|
||||
|
||||
ctx->client->user = user;
|
||||
ctx->client->flag.authenticated = 1;
|
||||
clientSetUser(ctx->client, user, 1);
|
||||
|
||||
if (clientHasModuleAuthInProgress(ctx->client)) {
|
||||
ctx->client->flag.module_auth_has_result = 1;
|
||||
|
||||
@@ -203,6 +203,7 @@ void moduleAcquireGIL(void);
|
||||
int moduleTryAcquireGIL(void);
|
||||
void moduleReleaseGIL(void);
|
||||
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
|
||||
unsigned long moduleNotifyKeyspaceSubscribersCnt(void);
|
||||
void firePostExecutionUnitJobs(void);
|
||||
void moduleCallCommandFilters(client *c);
|
||||
void modulePostExecutionUnitOperations(void);
|
||||
|
||||
+143
-60
@@ -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(). */
|
||||
@@ -132,8 +132,22 @@ void linkClient(client *c) {
|
||||
static void clientSetDefaultAuth(client *c) {
|
||||
/* If the default user does not require authentication, the user is
|
||||
* directly authenticated. */
|
||||
c->user = DefaultUser;
|
||||
c->flag.authenticated = (c->user->flags & USER_FLAG_NOPASS) && !(c->user->flags & USER_FLAG_DISABLED);
|
||||
clientSetUser(c, DefaultUser, (DefaultUser->flags & USER_FLAG_NOPASS) && !(DefaultUser->flags & USER_FLAG_DISABLED));
|
||||
}
|
||||
|
||||
/* Attach the user u to this client.
|
||||
* Also, mark the client authentication state. In case the client is marked as authenticated,
|
||||
* it will also set the ever_authenticated flag on the client in order to avoid low level
|
||||
* limiting of the client output buffer.*/
|
||||
void clientSetUser(client *c, user *u, int authenticated) {
|
||||
c->user = u;
|
||||
c->flag.authenticated = authenticated;
|
||||
if (authenticated)
|
||||
c->flag.ever_authenticated = authenticated;
|
||||
}
|
||||
|
||||
static int clientEverAuthenticated(client *c) {
|
||||
return c->flag.ever_authenticated;
|
||||
}
|
||||
|
||||
int authRequired(client *c) {
|
||||
@@ -157,8 +171,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;
|
||||
@@ -205,8 +217,10 @@ client *createClient(connection *conn) {
|
||||
c->duration = 0;
|
||||
clientSetDefaultAuth(c);
|
||||
c->reply = listCreate();
|
||||
c->deferred_reply = NULL;
|
||||
c->deferred_reply_errors = NULL;
|
||||
c->reply_bytes = 0;
|
||||
c->deferred_reply_bytes = ULLONG_MAX;
|
||||
c->obuf_soft_limit_reached_time = 0;
|
||||
listSetFreeMethod(c->reply, freeClientReplyValue);
|
||||
listSetDupMethod(c->reply, dupClientReplyValue);
|
||||
@@ -279,7 +293,6 @@ void putClientInPendingWriteQueue(client *c) {
|
||||
listLinkNodeHead(server.clients_pending_write, &c->clients_pending_write_node);
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is called every time we are going to transmit new data
|
||||
* to the client. The behavior is the following:
|
||||
*
|
||||
@@ -328,6 +341,7 @@ int prepareClientToWrite(client *c) {
|
||||
* it should already be setup to do so (it has already pending data). */
|
||||
if (!clientHasPendingReplies(c)) putClientInPendingWriteQueue(c);
|
||||
|
||||
if (!isDeferredReplyEnabled(c)) c->flag.buffered_reply = 1;
|
||||
/* Authorize the caller to queue in the output buffer of this client. */
|
||||
return C_OK;
|
||||
}
|
||||
@@ -388,7 +402,8 @@ void deleteCachedResponseClient(client *recording_client) {
|
||||
VALKEY_NO_SANITIZE("bounds")
|
||||
size_t _addReplyToBuffer(client *c, const char *s, size_t len) {
|
||||
size_t available = c->buf_usable_size - c->bufpos;
|
||||
|
||||
/* If the debug enforcing to use the reply list is enabled.*/
|
||||
if (server.debug_client_enforce_reply_list) return 0;
|
||||
/* If there already are entries in the reply list, we cannot
|
||||
* add anything more to the static buffer. */
|
||||
if (listLength(c->reply) > 0) return 0;
|
||||
@@ -426,14 +441,16 @@ void _addReplyProtoToList(client *c, list *reply_list, const char *s, size_t len
|
||||
/* Create a new node, make sure it is allocated to at
|
||||
* least PROTO_REPLY_CHUNK_BYTES */
|
||||
size_t usable_size;
|
||||
size_t size = len < PROTO_REPLY_CHUNK_BYTES ? PROTO_REPLY_CHUNK_BYTES : len;
|
||||
size_t min_reply_size = isDeferredReplyEnabled(c) ? PROTO_REPLY_MIN_BYTES : PROTO_REPLY_CHUNK_BYTES;
|
||||
size_t size = len < min_reply_size ? min_reply_size : len;
|
||||
tail = zmalloc_usable(size + sizeof(clientReplyBlock), &usable_size);
|
||||
/* take over the allocation's internal fragmentation */
|
||||
tail->size = usable_size - sizeof(clientReplyBlock);
|
||||
tail->used = len;
|
||||
memcpy(tail->buf, s, len);
|
||||
listAddNodeTail(reply_list, tail);
|
||||
c->reply_bytes += tail->size;
|
||||
unsigned long long *reply_bytes = (isDeferredReplyEnabled(c)) ? &c->deferred_reply_bytes : &c->reply_bytes;
|
||||
*reply_bytes += tail->size;
|
||||
|
||||
closeClientOnOutputBufferLimitReached(c, 1);
|
||||
}
|
||||
@@ -463,7 +480,6 @@ void _addReplyToBufferOrList(client *c, const char *s, size_t len) {
|
||||
}
|
||||
|
||||
c->net_output_bytes_curr_cmd += len;
|
||||
|
||||
/* We call it here because this function may affect the reply
|
||||
* buffer offset (see function comment) */
|
||||
reqresSaveClientReplyOffset(c);
|
||||
@@ -474,12 +490,17 @@ void _addReplyToBufferOrList(client *c, const char *s, size_t len) {
|
||||
* the SUBSCRIBE command family, which (currently) have a push message instead of a proper reply.
|
||||
* The check for executing_client also avoids affecting push messages that are part of eviction.
|
||||
* Check CLIENT_PUSHING first to avoid race conditions, as it's absent in module's fake client. */
|
||||
if (c->flag.pushing && c == server.current_client && server.executing_client &&
|
||||
!cmdHasPushAsReply(server.executing_client->cmd)) {
|
||||
_addReplyProtoToList(c, server.pending_push_messages, s, len);
|
||||
int defer_push_message = c->flag.pushing && c == server.current_client && server.executing_client &&
|
||||
!cmdHasPushAsReply(server.executing_client->cmd);
|
||||
if (defer_push_message == 0 && isDeferredReplyEnabled(c)) {
|
||||
_addReplyProtoToList(c, c->deferred_reply, s, len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (defer_push_message) {
|
||||
_addReplyProtoToList(c, server.pending_push_messages, s, len);
|
||||
return;
|
||||
}
|
||||
size_t reply_len = _addReplyToBuffer(c, s, len);
|
||||
if (len > reply_len) _addReplyProtoToList(c, c->reply, s + reply_len, len - reply_len);
|
||||
}
|
||||
@@ -566,6 +587,7 @@ void afterErrorReply(client *c, const char *s, size_t len, int flags) {
|
||||
return;
|
||||
}
|
||||
|
||||
commitDeferredReplyBuffer(c, 1);
|
||||
if (!(flags & ERR_REPLY_FLAG_NO_STATS_UPDATE)) {
|
||||
/* Increment the global error counter */
|
||||
server.stat_total_error_replies++;
|
||||
@@ -842,6 +864,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 +878,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 +889,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;
|
||||
|
||||
@@ -1282,6 +1307,49 @@ void addReplySubcommandSyntaxError(client *c) {
|
||||
sdsfree(cmd);
|
||||
}
|
||||
|
||||
inline int isDeferredReplyEnabled(client *c) {
|
||||
return c->deferred_reply_bytes != ULLONG_MAX;
|
||||
}
|
||||
|
||||
/* Commands that generate replies before triggering keyspace notifications must
|
||||
* use a deferred reply buffer. This allows postponing the actual transmission
|
||||
* of the reply until after the client is unblocked, in case it was blocked by
|
||||
* a keyspace notification. This is necessary because modules subscribed to
|
||||
* keyspace notifications can block the client from within the notification
|
||||
* callback. */
|
||||
void initDeferredReplyBuffer(client *c) {
|
||||
if (moduleNotifyKeyspaceSubscribersCnt() == 0) return;
|
||||
if (c->deferred_reply == NULL) c->deferred_reply = listCreate();
|
||||
if (!isDeferredReplyEnabled(c)) c->deferred_reply_bytes = 0;
|
||||
}
|
||||
|
||||
static void resetDeferredReplyBuffer(client *c) {
|
||||
listEmpty(c->deferred_reply);
|
||||
c->deferred_reply_bytes = ULLONG_MAX;
|
||||
}
|
||||
|
||||
/* Move the client deferred reply buffer into the client reply buffer and put the client
|
||||
* in the pending write queue. */
|
||||
void commitDeferredReplyBuffer(client *c, int skip_if_blocked) {
|
||||
if (skip_if_blocked && c->flag.blocked) return;
|
||||
|
||||
if (!isDeferredReplyEnabled(c) || (c->deferred_reply && listLength(c->deferred_reply) == 0)) {
|
||||
resetDeferredReplyBuffer(c);
|
||||
return;
|
||||
}
|
||||
|
||||
listJoin(c->reply, c->deferred_reply);
|
||||
c->reply_bytes += c->deferred_reply_bytes;
|
||||
|
||||
resetDeferredReplyBuffer(c);
|
||||
if (prepareClientToWrite(c) != C_OK) {
|
||||
return;
|
||||
}
|
||||
/* We call it here because this function may affect the reply
|
||||
* buffer offset (see function comment) */
|
||||
reqresSaveClientReplyOffset(c);
|
||||
}
|
||||
|
||||
/* Append 'src' client output buffers into 'dst' client output buffers.
|
||||
* This function clears the output buffers of 'src' */
|
||||
void AddReplyFromClient(client *dst, client *src) {
|
||||
@@ -1582,7 +1650,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 {
|
||||
@@ -1736,6 +1803,7 @@ void freeClient(client *c) {
|
||||
c->reply = NULL;
|
||||
zfree_with_size(c->buf, c->buf_usable_size);
|
||||
c->buf = NULL;
|
||||
listRelease(c->deferred_reply);
|
||||
|
||||
freeClientArgv(c);
|
||||
freeClientOriginalArgv(c);
|
||||
@@ -2578,6 +2646,8 @@ void resetClient(client *c) {
|
||||
c->slot = -1;
|
||||
c->flag.executing_command = 0;
|
||||
c->flag.replication_done = 0;
|
||||
c->flag.buffered_reply = 0;
|
||||
c->flag.keyspace_notified = 0;
|
||||
c->net_output_bytes_curr_cmd = 0;
|
||||
|
||||
/* Make sure the duration has been recorded to some command. */
|
||||
@@ -2588,6 +2658,7 @@ void resetClient(client *c) {
|
||||
|
||||
if (c->deferred_reply_errors) listRelease(c->deferred_reply_errors);
|
||||
c->deferred_reply_errors = NULL;
|
||||
commitDeferredReplyBuffer(c, 1);
|
||||
|
||||
/* We clear the ASKING flag as well if we are not inside a MULTI, and
|
||||
* if what we just executed is not the ASKING command itself. */
|
||||
@@ -2764,24 +2835,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 +2928,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 +3016,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 +3055,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 +3556,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 +3759,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 +3960,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) {
|
||||
@@ -4477,10 +4549,10 @@ void rewriteClientCommandVector(client *c, int argc, ...) {
|
||||
|
||||
/* Completely replace the client command vector with the provided one. */
|
||||
void replaceClientCommandVector(client *c, int argc, robj **argv) {
|
||||
int j;
|
||||
backupAndUpdateClientArgv(c, argc, argv);
|
||||
c->argv_len_sum = 0;
|
||||
for (j = 0; j < c->argc; j++)
|
||||
c->flag.buffered_reply = 0;
|
||||
for (int j = 0; j < c->argc; j++)
|
||||
if (c->argv[j]) c->argv_len_sum += getStringObjectLen(c->argv[j]);
|
||||
c->cmd = lookupCommandOrOriginal(c->argv, c->argc);
|
||||
serverAssertWithInfo(c, NULL, c->cmd != NULL);
|
||||
@@ -4511,6 +4583,7 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
|
||||
|
||||
/* If this is the command name make sure to fix c->cmd. */
|
||||
if (i == 0) {
|
||||
c->flag.buffered_reply = 0;
|
||||
c->cmd = lookupCommandOrOriginal(c->argv, c->argc);
|
||||
serverAssertWithInfo(c, NULL, c->cmd != NULL);
|
||||
}
|
||||
@@ -4534,10 +4607,15 @@ size_t getClientOutputBufferMemoryUsage(client *c) {
|
||||
repl_node_num = last->id - cur->id + 1;
|
||||
}
|
||||
return repl_buf_size + (repl_node_size * repl_node_num);
|
||||
} else {
|
||||
size_t list_item_size = sizeof(listNode) + sizeof(clientReplyBlock);
|
||||
return c->reply_bytes + (list_item_size * listLength(c->reply));
|
||||
}
|
||||
|
||||
size_t list_item_size = sizeof(listNode) + sizeof(clientReplyBlock);
|
||||
size_t usage = c->reply_bytes + (list_item_size * listLength(c->reply));
|
||||
if (isDeferredReplyEnabled(c)) {
|
||||
usage += c->deferred_reply_bytes +
|
||||
(list_item_size * listLength(c->deferred_reply));
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
/* Returns the total client's memory usage.
|
||||
@@ -4621,6 +4699,11 @@ int checkClientOutputBufferLimits(client *c) {
|
||||
int soft = 0, hard = 0, class;
|
||||
unsigned long used_mem = getClientOutputBufferMemoryUsage(c);
|
||||
|
||||
/* For unauthenticated clients which were also never authenticated before the output buffer is limited to prevent
|
||||
* them from abusing it by not reading the replies */
|
||||
if (used_mem > REPLY_BUFFER_SIZE_UNAUTHENTICATED_CLIENT && authRequired(c) && !clientEverAuthenticated(c))
|
||||
return 1;
|
||||
|
||||
class = getClientType(c);
|
||||
/* For the purpose of output buffer limiting, primaries are handled
|
||||
* like normal clients. */
|
||||
|
||||
+14
-1
@@ -107,12 +107,25 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
|
||||
robj *chanobj, *eventobj;
|
||||
int len = -1;
|
||||
char buf[24];
|
||||
|
||||
client *c = server.executing_client;
|
||||
debugServerAssert(moduleNotifyKeyspaceSubscribersCnt() == 0 ||
|
||||
(type & (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_STREAM)) == 0 ||
|
||||
c == NULL ||
|
||||
c->cmd == NULL ||
|
||||
(c->cmd->flags & CMD_WRITE) == 0 ||
|
||||
c->flag.buffered_reply == 0 ||
|
||||
c->flag.keyspace_notified == 1 ||
|
||||
c->id == UINT64_MAX || // AOF client
|
||||
getClientType(c) != CLIENT_TYPE_NORMAL);
|
||||
/* If any modules are interested in events, notify the module system now.
|
||||
* This bypasses the notifications configuration, but the module engine
|
||||
* will only call event subscribers if the event type matches the types
|
||||
* they are interested in. */
|
||||
moduleNotifyKeyspaceEvent(type, event, key, dbid);
|
||||
if (c) {
|
||||
c->flag.keyspace_notified = 1;
|
||||
commitDeferredReplyBuffer(c, 1);
|
||||
}
|
||||
|
||||
/* If notifications for this class of events are off, return ASAP. */
|
||||
if (!(server.notify_keyspace_events & type)) return;
|
||||
|
||||
+17
-9
@@ -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);
|
||||
@@ -1242,28 +1242,36 @@ size_t objectComputeSize(robj *key, robj *o, size_t sample_size, int dbid) {
|
||||
if (s->cgroups) {
|
||||
raxStart(&ri, s->cgroups);
|
||||
raxSeek(&ri, "^", NULL, 0);
|
||||
while (raxNext(&ri)) {
|
||||
samples = 0;
|
||||
elesize = 0;
|
||||
while (samples < sample_size && raxNext(&ri)) {
|
||||
streamCG *cg = ri.data;
|
||||
asize += sizeof(*cg);
|
||||
asize += raxAllocSize(cg->pel);
|
||||
asize += sizeof(streamNACK) * raxSize(cg->pel);
|
||||
elesize += sizeof(*cg);
|
||||
elesize += raxAllocSize(cg->pel);
|
||||
elesize += sizeof(streamNACK) * raxSize(cg->pel);
|
||||
|
||||
/* For each consumer we also need to add the basic data
|
||||
* structures and the PEL memory usage. */
|
||||
raxIterator cri;
|
||||
raxStart(&cri, cg->consumers);
|
||||
raxSeek(&cri, "^", NULL, 0);
|
||||
while (raxNext(&cri)) {
|
||||
size_t inner_samples = 0;
|
||||
size_t inner_elesize = 0;
|
||||
while (inner_samples < sample_size && raxNext(&cri)) {
|
||||
streamConsumer *consumer = cri.data;
|
||||
asize += sizeof(*consumer);
|
||||
asize += sdslen(consumer->name);
|
||||
asize += raxAllocSize(consumer->pel);
|
||||
inner_elesize += sizeof(*consumer);
|
||||
inner_elesize += sdslen(consumer->name);
|
||||
inner_elesize += raxAllocSize(consumer->pel);
|
||||
/* Don't count NACKs again, they are shared with the
|
||||
* consumer group PEL. */
|
||||
inner_samples++;
|
||||
}
|
||||
raxStop(&cri);
|
||||
if (inner_samples) elesize += (double)inner_elesize / inner_samples * raxSize(cg->consumers);
|
||||
samples++;
|
||||
}
|
||||
raxStop(&ri);
|
||||
if (samples) asize += (double)elesize / samples * raxSize(s->cgroups);
|
||||
}
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
asize = moduleGetMemUsage(key, o, sample_size, dbid);
|
||||
|
||||
@@ -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"
|
||||
|
||||
+38
-30
@@ -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);
|
||||
@@ -1985,7 +1991,7 @@ void replicationCreatePrimaryClientWithHandler(connection *conn, int dbid, Conne
|
||||
* execution is done. This is the reason why we allow blocking the replication
|
||||
* connection. */
|
||||
server.primary->flag.primary = 1;
|
||||
server.primary->flag.authenticated = 1;
|
||||
clientSetUser(server.primary, NULL, 1);
|
||||
|
||||
/* Allocate a private query buffer for the primary client instead of using the shared query buffer.
|
||||
* This is done because the primary's query buffer data needs to be preserved for my sub-replicas to use. */
|
||||
@@ -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
|
||||
@@ -4329,7 +4337,7 @@ void establishPrimaryConnection(void) {
|
||||
connSetPrivateData(server.primary->conn, server.primary);
|
||||
server.primary->flag.close_after_reply = 0;
|
||||
server.primary->flag.close_asap = 0;
|
||||
server.primary->flag.authenticated = 1;
|
||||
clientSetUser(server.primary, NULL, 1);
|
||||
server.primary->last_interaction = server.unixtime;
|
||||
server.repl_state = REPL_STATE_CONNECTED;
|
||||
server.repl_down_since = 0;
|
||||
|
||||
+24
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright Valkey Contributors.
|
||||
* Copyright (c) Valkey Contributors
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
@@ -217,21 +217,35 @@ static void engineTeardownModuleCtx(scriptingEngine *e) {
|
||||
compiledFunction **scriptingEngineCallCompileCode(scriptingEngine *engine,
|
||||
subsystemType type,
|
||||
const char *code,
|
||||
size_t code_len,
|
||||
size_t timeout,
|
||||
size_t *out_num_compiled_functions,
|
||||
robj **err) {
|
||||
serverAssert(type == VMSE_EVAL || type == VMSE_FUNCTION);
|
||||
|
||||
compiledFunction **functions = NULL;
|
||||
engineSetupModuleCtx(engine, NULL);
|
||||
if (engine->impl.methods.version == 1) {
|
||||
functions = engine->impl.methods.compile_code_v1(
|
||||
engine->module_ctx,
|
||||
engine->impl.ctx,
|
||||
type,
|
||||
code,
|
||||
timeout,
|
||||
out_num_compiled_functions,
|
||||
err);
|
||||
} else {
|
||||
/* Assume versions greater than 1 use updated interface. */
|
||||
functions = engine->impl.methods.compile_code(
|
||||
engine->module_ctx,
|
||||
engine->impl.ctx,
|
||||
type,
|
||||
code,
|
||||
code_len,
|
||||
timeout,
|
||||
out_num_compiled_functions,
|
||||
err);
|
||||
}
|
||||
|
||||
compiledFunction **functions = engine->impl.methods.compile_code(
|
||||
engine->module_ctx,
|
||||
engine->impl.ctx,
|
||||
type,
|
||||
code,
|
||||
timeout,
|
||||
out_num_compiled_functions,
|
||||
err);
|
||||
|
||||
engineTeardownModuleCtx(engine);
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ ValkeyModule *scriptingEngineGetModule(scriptingEngine *engine);
|
||||
compiledFunction **scriptingEngineCallCompileCode(scriptingEngine *engine,
|
||||
subsystemType type,
|
||||
const char *code,
|
||||
size_t code_len,
|
||||
size_t timeout,
|
||||
size_t *out_num_compiled_functions,
|
||||
robj **err);
|
||||
|
||||
@@ -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]);
|
||||
|
||||
+9
-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"
|
||||
@@ -2802,6 +2806,7 @@ void initServer(void) {
|
||||
server.reply_buffer_peak_reset_time = REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME;
|
||||
server.reply_buffer_resizing_enabled = 1;
|
||||
server.client_mem_usage_buckets = NULL;
|
||||
server.debug_client_enforce_reply_list = 0;
|
||||
resetReplicationBuffer();
|
||||
|
||||
/* Make sure the locale is set on startup based on the config file. */
|
||||
@@ -3724,6 +3729,8 @@ void call(client *c, int flags) {
|
||||
* re-processing and unblock the client.*/
|
||||
c->flag.executing_command = 1;
|
||||
|
||||
c->flag.buffered_reply = 0;
|
||||
c->flag.keyspace_notified = 0;
|
||||
/* Setting the CLIENT_REPROCESSING_COMMAND flag so that during the actual
|
||||
* processing of the command proc, the client is aware that it is being
|
||||
* re-processed. */
|
||||
@@ -4063,6 +4070,7 @@ int processCommand(client *c) {
|
||||
}
|
||||
}
|
||||
c->cmd = c->lastcmd = c->realcmd = cmd;
|
||||
c->flag.buffered_reply = 0;
|
||||
sds err;
|
||||
if (!commandCheckExistence(c, &err)) {
|
||||
rejectCommandSds(c, err);
|
||||
|
||||
+28
-15
@@ -199,7 +199,8 @@ struct hdr_histogram;
|
||||
#define PROTO_REPLY_MIN_BYTES (1024) /* the lower limit on reply buffer size */
|
||||
#define REDIS_AUTOSYNC_BYTES (1024 * 1024 * 4) /* Sync file every 4MB. */
|
||||
|
||||
#define REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME 5000 /* 5 seconds */
|
||||
#define REPLY_BUFFER_DEFAULT_PEAK_RESET_TIME 5000 /* 5 seconds */
|
||||
#define REPLY_BUFFER_SIZE_UNAUTHENTICATED_CLIENT 1024 /* 1024 bytes */
|
||||
|
||||
/* When configuring the server eventloop, we setup it so that the total number
|
||||
* of file descriptors we can handle are server.maxclients + RESERVED_FDS +
|
||||
@@ -1088,6 +1089,7 @@ typedef struct ClientFlags {
|
||||
uint64_t reprocessing_command : 1; /* The client is re-processing the command. */
|
||||
uint64_t replication_done : 1; /* Indicate that replication has been done on the client */
|
||||
uint64_t authenticated : 1; /* Indicate a client has successfully authenticated */
|
||||
uint64_t ever_authenticated : 1; /* Indicate a client was ever successfully authenticated during it's lifetime */
|
||||
uint64_t protected_rdb_channel : 1; /* Dual channel replication sync: Protects the RDB client from premature \
|
||||
* release during full sync. This flag is used to ensure that the RDB client, which \
|
||||
* references the first replication data block required by the replica, is not \
|
||||
@@ -1108,7 +1110,11 @@ typedef struct ClientFlags {
|
||||
* flag, we won't cache the primary in freeClient. */
|
||||
uint64_t fake : 1; /* This is a fake client without a real connection. */
|
||||
uint64_t import_source : 1; /* This client is importing data to server and can visit expired key. */
|
||||
uint64_t reserved : 4; /* Reserved for future use */
|
||||
uint64_t buffered_reply : 1; /* Indicates the reply for the current command was buffered, either in client::reply
|
||||
or client::buf. */
|
||||
uint64_t keyspace_notified : 1; /* Indicates that a keyspace notification was triggered during the execution of the
|
||||
current command. */
|
||||
uint64_t reserved : 1; /* Reserved for future use */
|
||||
} ClientFlags;
|
||||
|
||||
typedef struct ClientPubSubData {
|
||||
@@ -1259,13 +1265,16 @@ typedef struct client {
|
||||
dictEntry *cur_script; /* Cached pointer to the dictEntry of the script being executed. */
|
||||
user *user; /* User associated with this connection */
|
||||
time_t obuf_soft_limit_reached_time;
|
||||
list *deferred_reply_errors; /* Used for module thread safe contexts. */
|
||||
robj *name; /* As set by CLIENT SETNAME. */
|
||||
robj *lib_name; /* The client library name as set by CLIENT SETINFO. */
|
||||
robj *lib_ver; /* The client library version as set by CLIENT SETINFO. */
|
||||
sds peerid; /* Cached peer ID. */
|
||||
sds sockname; /* Cached connection target address. */
|
||||
time_t ctime; /* Client creation time. */
|
||||
list *deferred_reply_errors; /* Used for module thread safe contexts. */
|
||||
robj *name; /* As set by CLIENT SETNAME. */
|
||||
robj *lib_name; /* The client library name as set by CLIENT SETINFO. */
|
||||
robj *lib_ver; /* The client library version as set by CLIENT SETINFO. */
|
||||
sds peerid; /* Cached peer ID. */
|
||||
sds sockname; /* Cached connection target address. */
|
||||
time_t ctime; /* Client creation time. */
|
||||
list *deferred_reply; /* List of reply objects to be sent to the client, typically after
|
||||
the client has been unblocked. */
|
||||
unsigned long long deferred_reply_bytes; /* Total bytes of objects in the blocked client pending list.*/
|
||||
#ifdef LOG_REQ_RES
|
||||
clientReqResInfo reqres;
|
||||
#endif
|
||||
@@ -1663,7 +1672,7 @@ struct valkeyServer {
|
||||
int enable_debug_cmd; /* Enable DEBUG commands, see PROTECTED_ACTION_ALLOWED_* */
|
||||
int enable_module_cmd; /* Enable MODULE commands, see PROTECTED_ACTION_ALLOWED_* */
|
||||
int enable_debug_assert; /* Enable debug asserts */
|
||||
|
||||
int debug_client_enforce_reply_list; /* Force client to always use the reply list */
|
||||
/* RDB / AOF loading information */
|
||||
volatile sig_atomic_t loading; /* We are loading data from disk if true */
|
||||
volatile sig_atomic_t async_loading; /* We are loading data without blocking the db being served */
|
||||
@@ -2084,6 +2093,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 +2594,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);
|
||||
@@ -2645,6 +2653,8 @@ void resetClientIOState(client *c);
|
||||
void freeClientOriginalArgv(client *c);
|
||||
void freeClientArgv(client *c);
|
||||
void sendReplyToClient(connection *conn);
|
||||
int isDeferredReplyEnabled(client *c);
|
||||
void initDeferredReplyBuffer(client *c);
|
||||
void *addReplyDeferredLen(client *c);
|
||||
void setDeferredArrayLen(client *c, void *node, long length);
|
||||
void setDeferredMapLen(client *c, void *node, long length);
|
||||
@@ -2662,6 +2672,7 @@ void addReplyBool(client *c, int b);
|
||||
void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext);
|
||||
void addReplyProto(client *c, const char *s, size_t len);
|
||||
void AddReplyFromClient(client *c, client *src);
|
||||
void commitDeferredReplyBuffer(client *c, int skip_if_blocked);
|
||||
void addReplyBulk(client *c, robj *obj);
|
||||
void addReplyBulkCString(client *c, const char *s);
|
||||
void addReplyBulkCBuffer(client *c, const void *p, size_t len);
|
||||
@@ -2755,6 +2766,7 @@ void initSharedQueryBuf(void);
|
||||
void freeSharedQueryBuf(void);
|
||||
client *lookupClientByID(uint64_t id);
|
||||
int authRequired(client *c);
|
||||
void clientSetUser(client *c, user *u, int authenticated);
|
||||
void putClientInPendingWriteQueue(client *c);
|
||||
client *createCachedResponseClient(int resp);
|
||||
void deleteCachedResponseClient(client *recording_client);
|
||||
@@ -2816,7 +2828,7 @@ void listTypeDelete(listTypeIterator *iter, listTypeEntry *entry);
|
||||
robj *listTypeDup(robj *o);
|
||||
void listTypeDelRange(robj *o, long start, long stop);
|
||||
void popGenericCommand(client *c, int where);
|
||||
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, int signal, int *deleted);
|
||||
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, int *deleted);
|
||||
typedef enum {
|
||||
LIST_CONV_AUTO,
|
||||
LIST_CONV_GROWING,
|
||||
@@ -3250,11 +3262,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);
|
||||
@@ -3526,6 +3538,7 @@ int isInsideYieldingLongCommand(void);
|
||||
void processUnblockedClients(void);
|
||||
void initClientBlockingState(client *c);
|
||||
void freeClientBlockingState(client *c);
|
||||
void resetBlockedClientPendingReply(client *c);
|
||||
void blockClient(client *c, int btype);
|
||||
void unblockClient(client *c, int queue_for_reprocessing);
|
||||
void unblockClientOnTimeout(client *c);
|
||||
|
||||
+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);
|
||||
|
||||
+204
-43
@@ -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 {
|
||||
@@ -645,10 +805,10 @@ void hsetnxCommand(client *c) {
|
||||
} else {
|
||||
hashTypeTryConversion(o, c->argv, 2, 3);
|
||||
hashTypeSet(o, c->argv[2]->ptr, c->argv[3]->ptr, HASH_SET_COPY);
|
||||
addReply(c, shared.cone);
|
||||
signalModifiedKey(c, c->db, c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH, "hset", c->argv[1], c->db->id);
|
||||
server.dirty++;
|
||||
addReply(c, shared.cone);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,6 +826,10 @@ void hsetCommand(client *c) {
|
||||
|
||||
for (i = 2; i < c->argc; i += 2) created += !hashTypeSet(o, c->argv[i]->ptr, c->argv[i + 1]->ptr, HASH_SET_COPY);
|
||||
|
||||
signalModifiedKey(c, c->db, c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH, "hset", c->argv[1], c->db->id);
|
||||
server.dirty += (c->argc - 2) / 2;
|
||||
|
||||
/* HMSET (deprecated) and HSET return value is different. */
|
||||
char *cmdname = c->argv[0]->ptr;
|
||||
if (cmdname[1] == 's' || cmdname[1] == 'S') {
|
||||
@@ -675,9 +839,6 @@ void hsetCommand(client *c) {
|
||||
/* HMSET */
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
signalModifiedKey(c, c->db, c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH, "hset", c->argv[1], c->db->id);
|
||||
server.dirty += (c->argc - 2) / 2;
|
||||
}
|
||||
|
||||
void hincrbyCommand(client *c) {
|
||||
@@ -709,10 +870,10 @@ void hincrbyCommand(client *c) {
|
||||
value += incr;
|
||||
new = sdsfromlonglong(value);
|
||||
hashTypeSet(o, c->argv[2]->ptr, new, HASH_SET_TAKE_VALUE);
|
||||
addReplyLongLong(c, value);
|
||||
signalModifiedKey(c, c->db, c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH, "hincrby", c->argv[1], c->db->id);
|
||||
server.dirty++;
|
||||
addReplyLongLong(c, value);
|
||||
}
|
||||
|
||||
void hincrbyfloatCommand(client *c) {
|
||||
@@ -752,10 +913,10 @@ void hincrbyfloatCommand(client *c) {
|
||||
int len = ld2string(buf, sizeof(buf), value, LD_STR_HUMAN);
|
||||
new = sdsnewlen(buf, len);
|
||||
hashTypeSet(o, c->argv[2]->ptr, new, HASH_SET_TAKE_VALUE);
|
||||
addReplyBulkCBuffer(c, buf, len);
|
||||
signalModifiedKey(c, c->db, c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_HASH, "hincrbyfloat", c->argv[1], c->db->id);
|
||||
server.dirty++;
|
||||
addReplyBulkCBuffer(c, buf, len);
|
||||
|
||||
/* Always replicate HINCRBYFLOAT as an HSET command with the final value
|
||||
* in order to make sure that differences in float precision or formatting
|
||||
|
||||
+18
-17
@@ -480,11 +480,11 @@ void pushGenericCommand(client *c, int where, int xx) {
|
||||
server.dirty++;
|
||||
}
|
||||
|
||||
addReplyLongLong(c, listTypeLength(lobj));
|
||||
|
||||
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
|
||||
signalModifiedKey(c, c->db, c->argv[1]);
|
||||
char *event = (where == LIST_HEAD) ? "lpush" : "rpush";
|
||||
notifyKeyspaceEvent(NOTIFY_LIST, event, c->argv[1], c->db->id);
|
||||
|
||||
addReplyLongLong(c, listTypeLength(lobj));
|
||||
}
|
||||
|
||||
/* LPUSH <key> <element> [<element> ...] */
|
||||
@@ -608,10 +608,10 @@ void lsetCommand(client *c) {
|
||||
* already handled the growing case in listTypeTryConversionAppend()
|
||||
* above, so here we just need to try the conversion for shrinking. */
|
||||
listTypeTryConversion(o, LIST_CONV_SHRINKING, NULL, NULL);
|
||||
addReply(c, shared.ok);
|
||||
signalModifiedKey(c, c->db, c->argv[1]);
|
||||
notifyKeyspaceEvent(NOTIFY_LIST, "lset", c->argv[1], c->db->id);
|
||||
server.dirty++;
|
||||
addReply(c, shared.ok);
|
||||
} else {
|
||||
addReplyErrorObject(c, shared.outofrangeerr);
|
||||
}
|
||||
@@ -628,13 +628,14 @@ void lsetCommand(client *c) {
|
||||
*
|
||||
* 'deleted' is an optional output argument to get an indication
|
||||
* if the key got deleted by this function. */
|
||||
void listPopRangeAndReplyWithKey(client *c, robj *o, robj *key, int where, long count, int signal, int *deleted) {
|
||||
void listPopRangeAndReplyWithKey(client *c, robj *o, robj *key, int where, long count, int *deleted) {
|
||||
long llen = listTypeLength(o);
|
||||
long rangelen = (count > llen) ? llen : count;
|
||||
long rangestart = (where == LIST_HEAD) ? 0 : -rangelen;
|
||||
long rangeend = (where == LIST_HEAD) ? rangelen - 1 : -1;
|
||||
int reverse = (where == LIST_HEAD) ? 0 : 1;
|
||||
|
||||
initDeferredReplyBuffer(c);
|
||||
/* We return key-name just once, and an array of elements */
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyBulk(c, key);
|
||||
@@ -643,7 +644,8 @@ void listPopRangeAndReplyWithKey(client *c, robj *o, robj *key, int where, long
|
||||
/* Pop these elements. */
|
||||
listTypeDelRange(o, rangestart, rangelen);
|
||||
/* Maintain the notifications and dirty. */
|
||||
listElementsRemoved(c, key, where, o, rangelen, signal, deleted);
|
||||
listElementsRemoved(c, key, where, o, rangelen, deleted);
|
||||
commitDeferredReplyBuffer(c, 1);
|
||||
}
|
||||
|
||||
/* Extracted from `addListRangeReply()` to reply with a quicklist list.
|
||||
@@ -726,12 +728,10 @@ void addListRangeReply(client *c, robj *o, long start, long end, int reverse) {
|
||||
}
|
||||
|
||||
/* A housekeeping helper for list elements popping tasks.
|
||||
*
|
||||
* If 'signal' is 0, skip calling signalModifiedKey().
|
||||
*
|
||||
* 'deleted' is an optional output argument to get an indication
|
||||
* if the key got deleted by this function. */
|
||||
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, int signal, int *deleted) {
|
||||
void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, int *deleted) {
|
||||
char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
|
||||
|
||||
notifyKeyspaceEvent(NOTIFY_LIST, event, key, c->db->id);
|
||||
@@ -744,7 +744,7 @@ void listElementsRemoved(client *c, robj *key, int where, robj *o, long count, i
|
||||
listTypeTryConversion(o, LIST_CONV_SHRINKING, NULL, NULL);
|
||||
if (deleted) *deleted = 0;
|
||||
}
|
||||
if (signal) signalModifiedKey(c, c->db, key);
|
||||
signalModifiedKey(c, c->db, key);
|
||||
server.dirty += count;
|
||||
}
|
||||
|
||||
@@ -778,10 +778,10 @@ void popGenericCommand(client *c, int where) {
|
||||
/* Pop a single element. This is POP's original behavior that replies
|
||||
* with a bulk string. */
|
||||
value = listTypePop(o, where);
|
||||
listElementsRemoved(c, c->argv[1], where, o, 1, NULL);
|
||||
serverAssert(value != NULL);
|
||||
addReplyBulk(c, value);
|
||||
decrRefCount(value);
|
||||
listElementsRemoved(c, c->argv[1], where, o, 1, 1, NULL);
|
||||
} else {
|
||||
/* Pop a range of elements. An addition to the original POP command,
|
||||
* which replies with a multi-bulk. */
|
||||
@@ -791,9 +791,11 @@ void popGenericCommand(client *c, int where) {
|
||||
long rangeend = (where == LIST_HEAD) ? rangelen - 1 : -1;
|
||||
int reverse = (where == LIST_HEAD) ? 0 : 1;
|
||||
|
||||
initDeferredReplyBuffer(c);
|
||||
addListRangeReply(c, o, rangestart, rangeend, reverse);
|
||||
listTypeDelRange(o, rangestart, rangelen);
|
||||
listElementsRemoved(c, c->argv[1], where, o, rangelen, 1, NULL);
|
||||
listElementsRemoved(c, c->argv[1], where, o, rangelen, NULL);
|
||||
commitDeferredReplyBuffer(c, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -823,7 +825,7 @@ void mpopGenericCommand(client *c, robj **keys, int numkeys, int where, long cou
|
||||
if (llen == 0) continue;
|
||||
|
||||
/* Pop a range of elements in a nested arrays way. */
|
||||
listPopRangeAndReplyWithKey(c, o, key, where, count, 1, NULL);
|
||||
listPopRangeAndReplyWithKey(c, o, key, where, count, NULL);
|
||||
|
||||
/* Replicate it as [LR]POP COUNT. */
|
||||
robj *count_obj = createStringObjectFromLongLong((count > llen) ? llen : count);
|
||||
@@ -1116,7 +1118,7 @@ void lmoveGenericCommand(client *c, int wherefrom, int whereto) {
|
||||
value = listTypePop(sobj, wherefrom);
|
||||
serverAssert(value); /* assertion for valgrind (avoid NPD) */
|
||||
lmoveHandlePush(c, c->argv[2], dobj, value, whereto);
|
||||
listElementsRemoved(c, touchedkey, wherefrom, sobj, 1, 1, NULL);
|
||||
listElementsRemoved(c, touchedkey, wherefrom, sobj, 1, NULL);
|
||||
|
||||
/* listTypePop returns an object with its refcount incremented */
|
||||
decrRefCount(value);
|
||||
@@ -1190,7 +1192,7 @@ void blockingPopGenericCommand(client *c, robj **keys, int numkeys, int where, i
|
||||
if (count != -1) {
|
||||
/* BLMPOP, non empty list, like a normal [LR]POP with count option.
|
||||
* The difference here we pop a range of elements in a nested arrays way. */
|
||||
listPopRangeAndReplyWithKey(c, o, key, where, count, 1, NULL);
|
||||
listPopRangeAndReplyWithKey(c, o, key, where, count, NULL);
|
||||
|
||||
/* Replicate it as [LR]POP COUNT. */
|
||||
robj *count_obj = createStringObjectFromLongLong((count > llen) ? llen : count);
|
||||
@@ -1203,12 +1205,11 @@ void blockingPopGenericCommand(client *c, robj **keys, int numkeys, int where, i
|
||||
robj *value = listTypePop(o, where);
|
||||
serverAssert(value != NULL);
|
||||
|
||||
listElementsRemoved(c, key, where, o, 1, NULL);
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyBulk(c, key);
|
||||
addReplyBulk(c, value);
|
||||
decrRefCount(value);
|
||||
listElementsRemoved(c, key, where, o, 1, 1, NULL);
|
||||
|
||||
/* Replicate it as an [LR]POP instead of B[LR]POP. */
|
||||
rewriteClientCommandVector(c, 2, (where == LIST_HEAD) ? shared.lpop : shared.rpop, key);
|
||||
return;
|
||||
|
||||
+9
-5
@@ -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"
|
||||
@@ -898,7 +903,6 @@ void spopWithCountCommand(client *c) {
|
||||
setTypeRemoveAux(set, str, len, llele, encoding == OBJ_ENCODING_HASHTABLE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Transfer the old set to the client. */
|
||||
setTypeIterator *si;
|
||||
si = setTypeInitIterator(set);
|
||||
@@ -1382,16 +1386,16 @@ void sinterGenericCommand(client *c,
|
||||
dstset->ptr = lpShrinkToFit(dstset->ptr);
|
||||
}
|
||||
setKey(c, c->db, dstkey, &dstset, 0);
|
||||
addReplyLongLong(c, setTypeSize(dstset));
|
||||
notifyKeyspaceEvent(NOTIFY_SET, "sinterstore", dstkey, c->db->id);
|
||||
server.dirty++;
|
||||
addReplyLongLong(c, setTypeSize(dstset));
|
||||
} else {
|
||||
addReply(c, shared.czero);
|
||||
if (dbDelete(c->db, dstkey)) {
|
||||
server.dirty++;
|
||||
signalModifiedKey(c, c->db, dstkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", dstkey, c->db->id);
|
||||
}
|
||||
addReply(c, shared.czero);
|
||||
decrRefCount(dstset);
|
||||
}
|
||||
} else {
|
||||
@@ -1606,16 +1610,16 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum, robj *dstke
|
||||
* create this key with the result set inside */
|
||||
if (setTypeSize(dstset) > 0) {
|
||||
setKey(c, c->db, dstkey, &dstset, 0);
|
||||
addReplyLongLong(c, setTypeSize(dstset));
|
||||
notifyKeyspaceEvent(NOTIFY_SET, op == SET_OP_UNION ? "sunionstore" : "sdiffstore", dstkey, c->db->id);
|
||||
server.dirty++;
|
||||
addReplyLongLong(c, setTypeSize(dstset));
|
||||
} else {
|
||||
addReply(c, shared.czero);
|
||||
if (dbDelete(c->db, dstkey)) {
|
||||
server.dirty++;
|
||||
signalModifiedKey(c, c->db, dstkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", dstkey, c->db->id);
|
||||
}
|
||||
addReply(c, shared.czero);
|
||||
decrRefCount(dstset);
|
||||
}
|
||||
}
|
||||
|
||||
+15
-14
@@ -1394,11 +1394,6 @@ int streamRangeHasTombstones(stream *s, streamID *start, streamID *end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (streamCompareID(&s->first_id, &s->max_deleted_entry_id) > 0) {
|
||||
/* The latest tombstone is before the first entry. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (start) {
|
||||
start_id = *start;
|
||||
} else {
|
||||
@@ -1490,6 +1485,11 @@ long long streamEstimateDistanceFromFirstEverEntry(stream *s, streamID *id) {
|
||||
return s->entries_added;
|
||||
}
|
||||
|
||||
if (!streamIDEqZero(id) && streamCompareID(id, &s->max_deleted_entry_id) < 0) {
|
||||
/* The ID is before the last tombstone, so the counter is unknown. */
|
||||
return SCG_INVALID_ENTRIES_READ;
|
||||
}
|
||||
|
||||
int cmp_last = streamCompareID(id, &s->last_id);
|
||||
if (cmp_last == 0) {
|
||||
/* Return the exact counter of the last entry in the stream. */
|
||||
@@ -1678,10 +1678,11 @@ size_t streamReplyWithRange(client *c,
|
||||
while (streamIteratorGetID(&si, &id, &numfields)) {
|
||||
/* Update the group last_id if needed. */
|
||||
if (group && streamCompareID(&id, &group->last_id) > 0) {
|
||||
if (group->entries_read != SCG_INVALID_ENTRIES_READ && !streamRangeHasTombstones(s, &id, NULL)) {
|
||||
/* A valid counter and no future tombstones mean we can
|
||||
* increment the read counter to keep tracking the group's
|
||||
* progress. */
|
||||
if (group->entries_read != SCG_INVALID_ENTRIES_READ &&
|
||||
streamCompareID(&group->last_id, &s->first_id) >= 0 &&
|
||||
!streamRangeHasTombstones(s, &group->last_id, NULL)) {
|
||||
/* A valid counter and no tombstones in the group's last-delivered-id and the stream's last-generated-id,
|
||||
* we can increment the read counter to keep tracking the group's progress. */
|
||||
group->entries_read++;
|
||||
} else if (s->entries_added) {
|
||||
/* The group's counter may be invalid, so we try to obtain it. */
|
||||
@@ -2027,10 +2028,10 @@ void xaddCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
sds replyid = createStreamIDString(&id);
|
||||
addReplyBulkCBuffer(c, replyid, sdslen(replyid));
|
||||
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM, "xadd", c->argv[1], c->db->id);
|
||||
server.dirty++;
|
||||
addReplyBulkCBuffer(c, replyid, sdslen(replyid));
|
||||
|
||||
/* Trim if needed. */
|
||||
if (parsed_args.trim_strategy != TRIM_STRATEGY_NONE) {
|
||||
@@ -2663,9 +2664,9 @@ void xgroupCommand(client *c) {
|
||||
|
||||
streamCG *cg = streamCreateCG(s, grpname, sdslen(grpname), &id, entries_read);
|
||||
if (cg) {
|
||||
addReply(c, shared.ok);
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM, "xgroup-create", c->argv[2], c->db->id);
|
||||
addReply(c, shared.ok);
|
||||
} else {
|
||||
addReplyError(c, "-BUSYGROUP Consumer Group name already exists");
|
||||
}
|
||||
@@ -2678,16 +2679,16 @@ void xgroupCommand(client *c) {
|
||||
}
|
||||
cg->last_id = id;
|
||||
cg->entries_read = entries_read;
|
||||
addReply(c, shared.ok);
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM, "xgroup-setid", c->argv[2], c->db->id);
|
||||
addReply(c, shared.ok);
|
||||
} else if (!strcasecmp(opt, "DESTROY") && c->argc == 4) {
|
||||
if (cg) {
|
||||
raxRemove(s->cgroups, (unsigned char *)grpname, sdslen(grpname), NULL);
|
||||
streamFreeCG(cg);
|
||||
addReply(c, shared.cone);
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM, "xgroup-destroy", c->argv[2], c->db->id);
|
||||
addReply(c, shared.cone);
|
||||
/* We want to unblock any XREADGROUP consumers with -NOGROUP. */
|
||||
signalKeyAsReady(c->db, c->argv[2], OBJ_STREAM);
|
||||
} else {
|
||||
@@ -2780,9 +2781,9 @@ void xsetidCommand(client *c) {
|
||||
s->last_id = id;
|
||||
if (entries_added != -1) s->entries_added = entries_added;
|
||||
if (!streamIDEqZero(&max_xdel_id)) s->max_deleted_entry_id = max_xdel_id;
|
||||
addReply(c, shared.ok);
|
||||
server.dirty++;
|
||||
notifyKeyspaceEvent(NOTIFY_STREAM, "xsetid", c->argv[1], c->db->id);
|
||||
addReply(c, shared.ok);
|
||||
}
|
||||
|
||||
/* XACK <key> <group> <id> <id> ... <id>
|
||||
|
||||
+21
-6
@@ -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() */
|
||||
@@ -101,7 +106,8 @@ void setGenericCommand(client *c,
|
||||
}
|
||||
|
||||
if (flags & OBJ_SET_GET) {
|
||||
if (getGenericCommand(c) == C_ERR) return;
|
||||
initDeferredReplyBuffer(c);
|
||||
if (getGenericCommand(c) == C_ERR) goto cleanup;
|
||||
}
|
||||
|
||||
robj *existing_value = lookupKeyWrite(c->db, key);
|
||||
@@ -110,27 +116,27 @@ void setGenericCommand(client *c,
|
||||
/* Handle the IFEQ conditional check */
|
||||
if (flags & OBJ_SET_IFEQ && found) {
|
||||
if (!(flags & OBJ_SET_GET) && checkType(c, existing_value, OBJ_STRING)) {
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (compareStringObjects(existing_value, comparison) != 0) {
|
||||
if (!(flags & OBJ_SET_GET)) {
|
||||
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
|
||||
}
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
} else if (flags & OBJ_SET_IFEQ && !found) {
|
||||
if (!(flags & OBJ_SET_GET)) {
|
||||
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
|
||||
}
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if ((flags & OBJ_SET_NX && found) || (flags & OBJ_SET_XX && !found)) {
|
||||
if (!(flags & OBJ_SET_GET)) {
|
||||
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
|
||||
}
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* If the `milliseconds` have expired, then we don't need to set it into the
|
||||
@@ -139,7 +145,7 @@ void setGenericCommand(client *c,
|
||||
if (expire && checkAlreadyExpired(milliseconds)) {
|
||||
if (found) deleteExpiredKeyFromOverwriteAndPropagate(c, key);
|
||||
if (!(flags & OBJ_SET_GET)) addReply(c, shared.ok);
|
||||
return;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* When expire is not NULL, we avoid deleting the TTL so it can be updated later instead of being deleted and then
|
||||
@@ -190,6 +196,9 @@ void setGenericCommand(client *c,
|
||||
}
|
||||
replaceClientCommandVector(c, argc, argv);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
commitDeferredReplyBuffer(c, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -437,6 +446,7 @@ void getexCommand(client *c) {
|
||||
return;
|
||||
}
|
||||
|
||||
initDeferredReplyBuffer(c);
|
||||
/* We need to do this before we expire the key or delete it */
|
||||
addReplyBulk(c, o);
|
||||
|
||||
@@ -464,9 +474,11 @@ void getexCommand(client *c) {
|
||||
server.dirty++;
|
||||
}
|
||||
}
|
||||
commitDeferredReplyBuffer(c, 1);
|
||||
}
|
||||
|
||||
void getdelCommand(client *c) {
|
||||
initDeferredReplyBuffer(c);
|
||||
if (getGenericCommand(c) == C_ERR) return;
|
||||
if (dbSyncDelete(c->db, c->argv[1])) {
|
||||
/* Propagate as DEL command */
|
||||
@@ -475,9 +487,11 @@ void getdelCommand(client *c) {
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", c->argv[1], c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
commitDeferredReplyBuffer(c, 1);
|
||||
}
|
||||
|
||||
void getsetCommand(client *c) {
|
||||
initDeferredReplyBuffer(c);
|
||||
if (getGenericCommand(c) == C_ERR) return;
|
||||
c->argv[2] = tryObjectEncoding(c->argv[2]);
|
||||
setKey(c, c->db, c->argv[1], &c->argv[2], 0);
|
||||
@@ -485,6 +499,7 @@ void getsetCommand(client *c) {
|
||||
notifyKeyspaceEvent(NOTIFY_STRING, "set", c->argv[1], c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
commitDeferredReplyBuffer(c, 1);
|
||||
/* Propagate as SET command */
|
||||
rewriteClientCommandArgument(c, 0, shared.set);
|
||||
}
|
||||
|
||||
+43
-33
@@ -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
|
||||
*----------------------------------------------------------------------------*/
|
||||
@@ -1754,6 +1758,8 @@ static void zaddGenericCommand(client *c, int flags) {
|
||||
int j, elements, ch = 0;
|
||||
size_t maxelelen = 0;
|
||||
int scoreidx = 0;
|
||||
int reply_err = 0;
|
||||
|
||||
/* The following vars are used in order to track what the command actually
|
||||
* did during the execution, to reply to the client and to trigger the
|
||||
* notification of keyspace change. */
|
||||
@@ -1847,18 +1853,26 @@ static void zaddGenericCommand(client *c, int flags) {
|
||||
ele = c->argv[scoreidx + 1 + j * 2]->ptr;
|
||||
int retval = zsetAdd(zobj, score, ele, flags, &retflags, &newscore);
|
||||
if (retval == 0) {
|
||||
addReplyError(c, nanerr);
|
||||
goto cleanup;
|
||||
reply_err = 1;
|
||||
break;
|
||||
}
|
||||
if (retflags & ZADD_OUT_ADDED) added++;
|
||||
if (retflags & ZADD_OUT_UPDATED) updated++;
|
||||
if (!(retflags & ZADD_OUT_NOP)) processed++;
|
||||
score = newscore;
|
||||
}
|
||||
server.dirty += (added + updated);
|
||||
if (!reply_err) {
|
||||
server.dirty += (added + updated);
|
||||
}
|
||||
if (added || updated) {
|
||||
signalModifiedKey(c, c->db, key);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET, incr ? "zincr" : "zadd", key, c->db->id);
|
||||
}
|
||||
|
||||
reply_to_client:
|
||||
if (incr) { /* ZINCRBY or INCR option. */
|
||||
if (reply_err) {
|
||||
addReplyError(c, nanerr);
|
||||
} else if (incr) { /* ZINCRBY or INCR option. */
|
||||
if (processed)
|
||||
addReplyDouble(c, score);
|
||||
else
|
||||
@@ -1866,13 +1880,8 @@ reply_to_client:
|
||||
} else { /* ZADD. */
|
||||
addReplyLongLong(c, ch ? added + updated : added);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
zfree(scores);
|
||||
if (added || updated) {
|
||||
signalModifiedKey(c, c->db, key);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET, incr ? "zincr" : "zadd", key, c->db->id);
|
||||
}
|
||||
}
|
||||
|
||||
void zaddCommand(client *c) {
|
||||
@@ -2767,18 +2776,17 @@ static void zunionInterDiffGenericCommand(client *c, robj *dstkey, int numkeysIn
|
||||
if (dstzset->zsl->length) {
|
||||
zsetConvertToListpackIfNeeded(dstobj, maxelelen, totelelen);
|
||||
setKey(c, c->db, dstkey, &dstobj, 0);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET, (op == SET_OP_UNION) ? "zunionstore" : (op == SET_OP_INTER ? "zinterstore" : "zdiffstore"),
|
||||
dstkey, c->db->id);
|
||||
addReplyLongLong(c, zsetLength(dstobj));
|
||||
notifyKeyspaceEvent(
|
||||
NOTIFY_ZSET, (op == SET_OP_UNION) ? "zunionstore" : (op == SET_OP_INTER ? "zinterstore" : "zdiffstore"),
|
||||
dstkey, c->db->id);
|
||||
server.dirty++;
|
||||
} else {
|
||||
addReply(c, shared.czero);
|
||||
if (dbDelete(c->db, dstkey)) {
|
||||
signalModifiedKey(c, c->db, dstkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", dstkey, c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
addReply(c, shared.czero);
|
||||
decrRefCount(dstobj);
|
||||
}
|
||||
} else if (cardinality_only) {
|
||||
@@ -2970,16 +2978,16 @@ static void zrangeResultEmitLongLongForStore(zrange_result_handler *handler, lon
|
||||
static void zrangeResultFinalizeStore(zrange_result_handler *handler, size_t result_count) {
|
||||
if (result_count) {
|
||||
setKey(handler->client, handler->client->db, handler->dstkey, &handler->dstobj, 0);
|
||||
addReplyLongLong(handler->client, result_count);
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET, "zrangestore", handler->dstkey, handler->client->db->id);
|
||||
server.dirty++;
|
||||
addReplyLongLong(handler->client, result_count);
|
||||
} else {
|
||||
addReply(handler->client, shared.czero);
|
||||
if (dbDelete(handler->client->db, handler->dstkey)) {
|
||||
signalModifiedKey(handler->client, handler->client->db, handler->dstkey);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC, "del", handler->dstkey, handler->client->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
addReply(handler->client, shared.czero);
|
||||
decrRefCount(handler->dstobj);
|
||||
}
|
||||
}
|
||||
@@ -3772,6 +3780,24 @@ void zscanCommand(client *c) {
|
||||
scanGenericCommand(c, o, cursor);
|
||||
}
|
||||
|
||||
void addZpopInitialReply(client *c, int emitkey, int use_nested_array, long rangelen, robj *key) {
|
||||
if (!use_nested_array && !emitkey) {
|
||||
/* ZPOPMIN/ZPOPMAX with or without COUNT option in RESP2. */
|
||||
addReplyArrayLen(c, rangelen * 2);
|
||||
} else if (use_nested_array && !emitkey) {
|
||||
/* ZPOPMIN/ZPOPMAX with COUNT option in RESP3. */
|
||||
addReplyArrayLen(c, rangelen);
|
||||
} else if (!use_nested_array && emitkey) {
|
||||
/* BZPOPMIN/BZPOPMAX in RESP2 and RESP3. */
|
||||
addReplyArrayLen(c, rangelen * 2 + 1);
|
||||
addReplyBulk(c, key);
|
||||
} else if (use_nested_array && emitkey) {
|
||||
/* ZMPOP/BZMPOP in RESP2 and RESP3. */
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyBulk(c, key);
|
||||
addReplyArrayLen(c, rangelen);
|
||||
}
|
||||
}
|
||||
/* This command implements the generic zpop operation, used by:
|
||||
* ZPOPMIN, ZPOPMAX, BZPOPMIN, BZPOPMAX and ZMPOP. This function is also used
|
||||
* inside blocked.c in the unblocking stage of BZPOPMIN, BZPOPMAX and BZMPOP.
|
||||
@@ -3844,23 +3870,6 @@ void genericZpopCommand(client *c,
|
||||
long llen = zsetLength(zobj);
|
||||
long rangelen = (count > llen) ? llen : count;
|
||||
|
||||
if (!use_nested_array && !emitkey) {
|
||||
/* ZPOPMIN/ZPOPMAX with or without COUNT option in RESP2. */
|
||||
addReplyArrayLen(c, rangelen * 2);
|
||||
} else if (use_nested_array && !emitkey) {
|
||||
/* ZPOPMIN/ZPOPMAX with COUNT option in RESP3. */
|
||||
addReplyArrayLen(c, rangelen);
|
||||
} else if (!use_nested_array && emitkey) {
|
||||
/* BZPOPMIN/BZPOPMAX in RESP2 and RESP3. */
|
||||
addReplyArrayLen(c, rangelen * 2 + 1);
|
||||
addReplyBulk(c, key);
|
||||
} else if (use_nested_array && emitkey) {
|
||||
/* ZMPOP/BZMPOP in RESP2 and RESP3. */
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyBulk(c, key);
|
||||
addReplyArrayLen(c, rangelen);
|
||||
}
|
||||
|
||||
/* Remove the element. */
|
||||
do {
|
||||
if (zobj->encoding == OBJ_ENCODING_LISTPACK) {
|
||||
@@ -3905,6 +3914,7 @@ void genericZpopCommand(client *c,
|
||||
if (result_count == 0) { /* Do this only for the first iteration. */
|
||||
char *events[2] = {"zpopmin", "zpopmax"};
|
||||
notifyKeyspaceEvent(NOTIFY_ZSET, events[where], key, c->db->id);
|
||||
addZpopInitialReply(c, emitkey, use_nested_array, rangelen, key);
|
||||
}
|
||||
|
||||
if (use_nested_array) {
|
||||
|
||||
+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);
|
||||
|
||||
+25
-4
@@ -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. */
|
||||
|
||||
@@ -873,6 +878,8 @@ typedef struct ValkeyModuleScriptingEngineCallableLazyEvalReset {
|
||||
*
|
||||
* - `code`: string pointer to the source code.
|
||||
*
|
||||
* - `code_len`: The length of the code string.
|
||||
*
|
||||
* - `timeout`: timeout for the library creation (0 for no timeout).
|
||||
*
|
||||
* - `out_num_compiled_functions`: out param with the number of objects
|
||||
@@ -884,6 +891,18 @@ typedef struct ValkeyModuleScriptingEngineCallableLazyEvalReset {
|
||||
* occurred.
|
||||
*/
|
||||
typedef ValkeyModuleScriptingEngineCompiledFunction **(*ValkeyModuleScriptingEngineCompileCodeFunc)(
|
||||
ValkeyModuleCtx *module_ctx,
|
||||
ValkeyModuleScriptingEngineCtx *engine_ctx,
|
||||
ValkeyModuleScriptingEngineSubsystemType type,
|
||||
const char *code,
|
||||
size_t code_len,
|
||||
size_t timeout,
|
||||
size_t *out_num_compiled_functions,
|
||||
ValkeyModuleString **err);
|
||||
|
||||
/* Version one of source code compilation interface. This API does not allow the compiler to
|
||||
* safely handle binary data. You should use a newer version of the API if possible. */
|
||||
typedef ValkeyModuleScriptingEngineCompiledFunctionV1 **(*ValkeyModuleScriptingEngineCompileCodeFuncV1)(
|
||||
ValkeyModuleCtx *module_ctx,
|
||||
ValkeyModuleScriptingEngineCtx *engine_ctx,
|
||||
ValkeyModuleScriptingEngineSubsystemType type,
|
||||
@@ -980,7 +999,7 @@ typedef ValkeyModuleScriptingEngineMemoryInfo (*ValkeyModuleScriptingEngineGetMe
|
||||
ValkeyModuleScriptingEngineSubsystemType type);
|
||||
|
||||
/* Current ABI version for scripting engine modules. */
|
||||
#define VALKEYMODULE_SCRIPTING_ENGINE_ABI_VERSION 1UL
|
||||
#define VALKEYMODULE_SCRIPTING_ENGINE_ABI_VERSION 2UL
|
||||
|
||||
typedef struct ValkeyModuleScriptingEngineMethods {
|
||||
uint64_t version; /* Version of this structure for ABI compat. */
|
||||
@@ -988,8 +1007,10 @@ typedef struct ValkeyModuleScriptingEngineMethods {
|
||||
/* Compile code function callback. When a new script is loaded, this
|
||||
* callback will be called with the script code, compiles it, and returns a
|
||||
* list of `ValkeyModuleScriptingEngineCompiledFunc` objects. */
|
||||
ValkeyModuleScriptingEngineCompileCodeFunc compile_code;
|
||||
|
||||
union {
|
||||
ValkeyModuleScriptingEngineCompileCodeFuncV1 compile_code_v1;
|
||||
ValkeyModuleScriptingEngineCompileCodeFunc compile_code;
|
||||
};
|
||||
/* Function callback to free the memory of a registered engine function. */
|
||||
ValkeyModuleScriptingEngineFreeFunctionFunc free_function;
|
||||
|
||||
|
||||
+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.1"
|
||||
#define VALKEY_VERSION_NUM 0x00080101
|
||||
/* 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. */
|
||||
|
||||
+2
-1
@@ -101,10 +101,11 @@ static thread_local int thread_index = -1;
|
||||
#if defined(__i386__) || defined(__x86_64__) || defined(__amd64__) || defined(__POWERPC__) || defined(__arm__) || \
|
||||
defined(__arm64__)
|
||||
static __attribute__((aligned(CACHE_LINE_SIZE))) size_t used_memory_thread_padded[MAX_THREADS_NUM + PADDING_ELEMENT_NUM];
|
||||
static size_t *used_memory_thread = &used_memory_thread_padded[PADDING_ELEMENT_NUM];
|
||||
#else
|
||||
static __attribute__((aligned(CACHE_LINE_SIZE))) _Atomic size_t used_memory_thread_padded[MAX_THREADS_NUM + PADDING_ELEMENT_NUM];
|
||||
static _Atomic size_t *used_memory_thread = &used_memory_thread_padded[PADDING_ELEMENT_NUM];
|
||||
#endif
|
||||
static size_t *used_memory_thread = &used_memory_thread_padded[PADDING_ELEMENT_NUM];
|
||||
static atomic_int total_active_threads = 0;
|
||||
/* This is a simple protection. It's used only if some modules create a lot of threads. */
|
||||
static atomic_size_t used_memory_for_additional_threads = 0;
|
||||
|
||||
@@ -215,6 +215,9 @@ proc wait_for_cluster_propagation {} {
|
||||
wait_for_condition 1000 50 {
|
||||
[cluster_config_consistent] eq 1
|
||||
} else {
|
||||
for {set j 0} {$j < [llength $::servers]} {incr j} {
|
||||
puts "R $j cluster slots output: [R $j cluster slots]"
|
||||
}
|
||||
fail "cluster config did not reach a consistent state"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -244,3 +244,44 @@ test "Shutting down master waits for replica then aborted" {
|
||||
}
|
||||
}
|
||||
} {} {repl external:skip}
|
||||
|
||||
test "Shutting down primary wait for replica after previous block" {
|
||||
start_server {overrides {save "" repl-backlog-size 1MB}} {
|
||||
start_server {overrides {save "" repl-backlog-size 1MB}} {
|
||||
set primary [srv -1 client]
|
||||
set primary_deferring [valkey_deferring_client -1]
|
||||
set primary_host [srv -1 host]
|
||||
set primary_port [srv -1 port]
|
||||
set primary_pid [srv -1 pid]
|
||||
set replica [srv 0 client]
|
||||
set replica_pid [srv 0 pid]
|
||||
|
||||
$primary_deferring BLPOP key 0.1
|
||||
|
||||
# Config primary and replica.
|
||||
$replica replicaof $primary_host $primary_port
|
||||
wait_for_sync $replica
|
||||
|
||||
# Pause the replica and write a key on primary.
|
||||
pause_process $replica_pid
|
||||
$primary incr k
|
||||
|
||||
# Call shutdown.
|
||||
$primary_deferring shutdown
|
||||
|
||||
wait_for_condition 100 100 {
|
||||
[string match "*connected_clients:2*" [$primary info clients]] && [string match "*blocked_clients:1*" [$primary info clients]]
|
||||
} else {
|
||||
set client_info [$primary info clients]
|
||||
fail "Shutdown did not trigger block. Current INFO CLIENTS output: $client_info"
|
||||
}
|
||||
|
||||
# Wake up replica, causing primary to continue shutting down.
|
||||
resume_process $replica_pid
|
||||
$primary_deferring close
|
||||
|
||||
# Check for crash.
|
||||
assert_equal [count_log_message -1 "*BUG REPORT START*"] 0
|
||||
}
|
||||
}
|
||||
} {} {repl external:skip}
|
||||
|
||||
@@ -39,6 +39,7 @@ TEST_MODULES = \
|
||||
datatype2.so \
|
||||
auth.so \
|
||||
keyspace_events.so \
|
||||
block_keyspace_notification.so \
|
||||
blockedclient.so \
|
||||
getkeys.so \
|
||||
getchannels.so \
|
||||
|
||||
@@ -187,15 +187,11 @@ void AuthBlock_FreeData(ValkeyModuleCtx *ctx, void *privdata) {
|
||||
* of blocking module auth.
|
||||
*/
|
||||
int blocking_auth_cb(ValkeyModuleCtx *ctx, ValkeyModuleString *username, ValkeyModuleString *password, ValkeyModuleString **err) {
|
||||
VALKEYMODULE_NOT_USED(username);
|
||||
VALKEYMODULE_NOT_USED(password);
|
||||
VALKEYMODULE_NOT_USED(err);
|
||||
/* Block the client from the Module. */
|
||||
ValkeyModuleBlockedClient *bc = ValkeyModule_BlockClientOnAuth(ctx, AuthBlock_Reply, AuthBlock_FreeData);
|
||||
int ctx_flags = ValkeyModule_GetContextFlags(ctx);
|
||||
if (ctx_flags & VALKEYMODULE_CTX_FLAGS_MULTI || ctx_flags & VALKEYMODULE_CTX_FLAGS_LUA) {
|
||||
/* Clean up by using ValkeyModule_UnblockClient since we attempted blocking the client. */
|
||||
ValkeyModule_UnblockClient(bc, NULL);
|
||||
if (bc == NULL) {
|
||||
ValkeyModule_ReplyWithError(ctx, "ERR Blocking module command called from transaction");
|
||||
return VALKEYMODULE_AUTH_HANDLED;
|
||||
}
|
||||
ValkeyModule_BlockedClientMeasureTimeStart(bc);
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/* This module is used to test blocking the client during a keyspace event. */
|
||||
#define _BSD_SOURCE
|
||||
#define _DEFAULT_SOURCE /* For usleep */
|
||||
|
||||
#include "valkeymodule.h"
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define EVENT_LOG_MAX_SIZE 1024
|
||||
|
||||
static pthread_mutex_t event_log_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
typedef struct KeyspaceEventData {
|
||||
ValkeyModuleString *key;
|
||||
ValkeyModuleString *event;
|
||||
} KeyspaceEventData;
|
||||
|
||||
typedef struct KeyspaceEventLog {
|
||||
KeyspaceEventData *log[EVENT_LOG_MAX_SIZE];
|
||||
size_t next_index;
|
||||
} KeyspaceEventLog;
|
||||
|
||||
KeyspaceEventLog *event_log = NULL;
|
||||
int unloaded = 0;
|
||||
|
||||
typedef struct BackgroundThreadData {
|
||||
KeyspaceEventData *event;
|
||||
ValkeyModuleBlockedClient *bc;
|
||||
} BackgroundThreadData;
|
||||
|
||||
static void *GenericEvent_BackgroundWork(void *arg) {
|
||||
BackgroundThreadData *data = (BackgroundThreadData *)arg;
|
||||
// Sleep for 1 second
|
||||
sleep(1);
|
||||
pthread_mutex_lock(&event_log_mutex);
|
||||
if (!unloaded && event_log->next_index < EVENT_LOG_MAX_SIZE) {
|
||||
event_log->log[event_log->next_index] = data->event;
|
||||
event_log->next_index++;
|
||||
}
|
||||
pthread_mutex_unlock(&event_log_mutex);
|
||||
if (data->bc) {
|
||||
ValkeyModule_UnblockClient(data->bc, NULL);
|
||||
}
|
||||
ValkeyModule_Free(data);
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
static int KeySpace_NotificationGeneric(ValkeyModuleCtx *ctx, int type,
|
||||
const char *event,
|
||||
ValkeyModuleString *key) {
|
||||
VALKEYMODULE_NOT_USED(ctx);
|
||||
VALKEYMODULE_NOT_USED(type);
|
||||
ValkeyModuleString *retained_key = ValkeyModule_HoldString(ctx, key);
|
||||
ValkeyModuleBlockedClient *bc =
|
||||
ValkeyModule_BlockClient(ctx, NULL, NULL, NULL, 0);
|
||||
if (bc == NULL) {
|
||||
ValkeyModule_Log(ctx, VALKEYMODULE_LOGLEVEL_NOTICE,
|
||||
"Failed to block for event %s on %s!", event,
|
||||
ValkeyModule_StringPtrLen(key, NULL));
|
||||
}
|
||||
BackgroundThreadData *data =
|
||||
ValkeyModule_Alloc(sizeof(BackgroundThreadData));
|
||||
data->bc = bc;
|
||||
KeyspaceEventData *event_data =
|
||||
ValkeyModule_Alloc(sizeof(KeyspaceEventData));
|
||||
event_data->key = retained_key;
|
||||
event_data->event = ValkeyModule_CreateString(ctx, event, strlen(event));
|
||||
data->event = event_data;
|
||||
pthread_t tid;
|
||||
pthread_create(&tid, NULL, GenericEvent_BackgroundWork, (void *)data);
|
||||
return VALKEYMODULE_OK;
|
||||
}
|
||||
|
||||
static int cmdGetEvents(ValkeyModuleCtx *ctx, ValkeyModuleString **argv,
|
||||
int argc) {
|
||||
VALKEYMODULE_NOT_USED(argv);
|
||||
VALKEYMODULE_NOT_USED(argc);
|
||||
pthread_mutex_lock(&event_log_mutex);
|
||||
ValkeyModule_ReplyWithArray(ctx, event_log->next_index);
|
||||
for (size_t i = 0; i < event_log->next_index; i++) {
|
||||
ValkeyModule_ReplyWithArray(ctx, 4);
|
||||
ValkeyModule_ReplyWithStringBuffer(ctx, "event", 5);
|
||||
ValkeyModule_ReplyWithString(ctx, event_log->log[i]->event);
|
||||
ValkeyModule_ReplyWithStringBuffer(ctx, "key", 3);
|
||||
ValkeyModule_ReplyWithString(ctx, event_log->log[i]->key);
|
||||
}
|
||||
pthread_mutex_unlock(&event_log_mutex);
|
||||
return VALKEYMODULE_OK;
|
||||
}
|
||||
|
||||
static int cmdClearEvents(ValkeyModuleCtx *ctx, ValkeyModuleString **argv,
|
||||
int argc) {
|
||||
VALKEYMODULE_NOT_USED(argv);
|
||||
VALKEYMODULE_NOT_USED(argc);
|
||||
pthread_mutex_lock(&event_log_mutex);
|
||||
for (size_t i = 0; i < event_log->next_index; i++) {
|
||||
KeyspaceEventData *data = event_log->log[i];
|
||||
ValkeyModule_FreeString(ctx, data->event);
|
||||
ValkeyModule_FreeString(ctx, data->key);
|
||||
ValkeyModule_Free(data);
|
||||
}
|
||||
event_log->next_index = 0;
|
||||
ValkeyModule_ReplyWithSimpleString(ctx, "OK");
|
||||
pthread_mutex_unlock(&event_log_mutex);
|
||||
return VALKEYMODULE_OK;
|
||||
}
|
||||
|
||||
/* This function must be present on each Valkey module. It is used in order to
|
||||
* register the commands into the Valkey server. */
|
||||
int ValkeyModule_OnLoad(ValkeyModuleCtx *ctx, ValkeyModuleString **argv,
|
||||
int argc) {
|
||||
VALKEYMODULE_NOT_USED(argv);
|
||||
VALKEYMODULE_NOT_USED(argc);
|
||||
if (ValkeyModule_Init(ctx, "testblockingkeyspacenotif", 1,
|
||||
VALKEYMODULE_APIVER_1) == VALKEYMODULE_ERR) {
|
||||
return VALKEYMODULE_ERR;
|
||||
}
|
||||
event_log = ValkeyModule_Alloc(sizeof(KeyspaceEventLog));
|
||||
event_log->next_index = 0;
|
||||
int keySpaceAll = ValkeyModule_GetKeyspaceNotificationFlagsAll();
|
||||
if (!(keySpaceAll & VALKEYMODULE_NOTIFY_LOADED)) {
|
||||
// VALKEYMODULE_NOTIFY_LOADED event are not supported we can not start
|
||||
return VALKEYMODULE_ERR;
|
||||
}
|
||||
if (ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_LOADED,
|
||||
KeySpace_NotificationGeneric) !=
|
||||
VALKEYMODULE_OK ||
|
||||
ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_GENERIC,
|
||||
KeySpace_NotificationGeneric) !=
|
||||
VALKEYMODULE_OK ||
|
||||
ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_EXPIRED,
|
||||
KeySpace_NotificationGeneric) !=
|
||||
VALKEYMODULE_OK ||
|
||||
ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_MODULE,
|
||||
KeySpace_NotificationGeneric) !=
|
||||
VALKEYMODULE_OK ||
|
||||
ValkeyModule_SubscribeToKeyspaceEvents(
|
||||
ctx, VALKEYMODULE_NOTIFY_KEY_MISS, KeySpace_NotificationGeneric) !=
|
||||
VALKEYMODULE_OK ||
|
||||
ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_STRING,
|
||||
KeySpace_NotificationGeneric) !=
|
||||
VALKEYMODULE_OK ||
|
||||
ValkeyModule_SubscribeToKeyspaceEvents(ctx, VALKEYMODULE_NOTIFY_HASH,
|
||||
KeySpace_NotificationGeneric) !=
|
||||
VALKEYMODULE_OK ||
|
||||
ValkeyModule_CreateCommand(ctx, "b_keyspace.events", cmdGetEvents, "",
|
||||
0, 0, 0) == VALKEYMODULE_ERR ||
|
||||
ValkeyModule_CreateCommand(ctx, "b_keyspace.clear", cmdClearEvents, "",
|
||||
0, 0, 0) == VALKEYMODULE_ERR) {
|
||||
return VALKEYMODULE_ERR;
|
||||
}
|
||||
return VALKEYMODULE_OK;
|
||||
}
|
||||
|
||||
int ValkeyModule_OnUnload(ValkeyModuleCtx *ctx) {
|
||||
pthread_mutex_lock(&event_log_mutex);
|
||||
unloaded = 1;
|
||||
for (size_t i = 0; i < event_log->next_index; i++) {
|
||||
KeyspaceEventData *data = event_log->log[i];
|
||||
ValkeyModule_FreeString(ctx, data->event);
|
||||
ValkeyModule_FreeString(ctx, data->key);
|
||||
ValkeyModule_Free(data);
|
||||
}
|
||||
ValkeyModule_Free(event_log);
|
||||
pthread_mutex_unlock(&event_log_mutex);
|
||||
return VALKEYMODULE_OK;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -349,10 +349,12 @@ static ValkeyModuleScriptingEngineCompiledFunction **createHelloLangEngine(Valke
|
||||
ValkeyModuleScriptingEngineCtx *engine_ctx,
|
||||
ValkeyModuleScriptingEngineSubsystemType type,
|
||||
const char *code,
|
||||
size_t code_len,
|
||||
size_t timeout,
|
||||
size_t *out_num_compiled_functions,
|
||||
ValkeyModuleString **err) {
|
||||
VALKEYMODULE_NOT_USED(module_ctx);
|
||||
VALKEYMODULE_NOT_USED(code_len);
|
||||
VALKEYMODULE_NOT_USED(type);
|
||||
VALKEYMODULE_NOT_USED(timeout);
|
||||
VALKEYMODULE_NOT_USED(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:
|
||||
|
||||
@@ -365,3 +365,9 @@ proc ::valkey_cluster::get_slot_from_keys {keys} {
|
||||
}
|
||||
return $slot
|
||||
}
|
||||
|
||||
# Check if the server responds with "PONG"
|
||||
proc check_server_response {server_id} {
|
||||
# Send a PING command and check if the response is "PONG"
|
||||
return [expr {[catch {R $server_id PING} result] == 0 && $result eq "PONG"}]
|
||||
}
|
||||
|
||||
@@ -129,6 +129,9 @@ proc wait_for_cluster_propagation {} {
|
||||
wait_for_condition 1000 50 {
|
||||
[cluster_config_consistent] eq 1
|
||||
} else {
|
||||
for {set j 0} {$j < [llength $::servers]} {incr j} {
|
||||
puts "R $j cluster slots output: [R $j cluster slots]"
|
||||
}
|
||||
fail "cluster config did not reach a consistent state"
|
||||
}
|
||||
}
|
||||
@@ -253,7 +256,7 @@ proc start_cluster {masters replicas options code {slot_allocator continuous_slo
|
||||
|
||||
# Configure the starting of multiple servers. Set cluster node timeout
|
||||
# aggressively since many tests depend on ping/pong messages.
|
||||
set cluster_options [list overrides [list cluster-enabled yes cluster-ping-interval 100 cluster-node-timeout 3000]]
|
||||
set cluster_options [list overrides [list cluster-enabled yes cluster-ping-interval 100 cluster-node-timeout 3000 cluster-slot-stats-enabled yes]]
|
||||
set options [concat $cluster_options $options]
|
||||
|
||||
# Cluster mode only supports a single database, so before executing the tests
|
||||
|
||||
@@ -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} {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user