Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a6c872ae3 | ||
|
|
c4d7b32653 | ||
|
|
1d6888b20d | ||
|
|
c0a83262a3 | ||
|
|
b46813a86f | ||
|
|
2df35f9fb0 | ||
|
|
1b3721eb22 | ||
|
|
148de48334 | ||
|
|
09b5c95b98 | ||
|
|
9cc798fa10 | ||
|
|
230c651c89 | ||
|
|
7f532eacdf | ||
|
|
8fcf3dc866 | ||
|
|
789b1b7b95 | ||
|
|
65401042dc | ||
|
|
a36cfc0829 | ||
|
|
30f57f32bd | ||
|
|
138263a1b4 | ||
|
|
f023cf026b | ||
|
|
fa08257fcb | ||
|
|
13d9553673 | ||
|
|
8757b125b2 | ||
|
|
1587755a22 | ||
|
|
368786ead5 | ||
|
|
f9b7fa0a96 | ||
|
|
ba1a4b2c8f | ||
|
|
457a1a542d | ||
|
|
95040d61d5 | ||
|
|
31896140d1 | ||
|
|
842a28f4a1 | ||
|
|
0d06e515af | ||
|
|
b1a53ea21f | ||
|
|
0c1c747062 | ||
|
|
6c3a8eccef | ||
|
|
2e46d2e735 | ||
|
|
c3db5254b7 | ||
|
|
2ad98a4e3c | ||
|
|
5faaed6e6b | ||
|
|
dbe5d1e605 | ||
|
|
54ea50c029 | ||
|
|
c8f1ec959a | ||
|
|
bc904dab04 | ||
|
|
22f1ab6e27 | ||
|
|
5c355b68ec | ||
|
|
837ca7f8f4 | ||
|
|
8cdf9391da | ||
|
|
c4e3405704 | ||
|
|
80621b1d0e | ||
|
|
fab099cdcf | ||
|
|
0d9576435f | ||
|
|
3ab7fe0812 | ||
|
|
5e155593d9 | ||
|
|
a38493a70e | ||
|
|
b7d6ef6b5a | ||
|
|
cf27a1abe4 | ||
|
|
9c1ecd044e | ||
|
|
9302d27889 | ||
|
|
62551a7b12 | ||
|
|
74cfe81543 | ||
|
|
8bba074586 | ||
|
|
bf432c98fd | ||
|
|
7bdab45ff1 | ||
|
|
7cf63635f0 | ||
|
|
d9b03bdb98 | ||
|
|
3b7d32069f | ||
|
|
7ecc04f59d | ||
|
|
05859cdd7e | ||
|
|
2408a15ee6 | ||
|
|
3e1afec688 | ||
|
|
2432f55278 | ||
|
|
417cc6e4fc | ||
|
|
0bbb196c46 | ||
|
|
48eaa75257 | ||
|
|
247307de96 | ||
|
|
5a05863e97 | ||
|
|
625b6f58f6 | ||
|
|
861917603e | ||
|
|
77628f370a | ||
|
|
7a80aade96 | ||
|
|
c61099eaa0 | ||
|
|
05c26399ec | ||
|
|
80a5137ee9 | ||
|
|
47c51369ee | ||
|
|
fafc47251a | ||
|
|
303667a40c | ||
|
|
63f02e7876 | ||
|
|
0fa78fd8fd | ||
|
|
58dc4f3c85 | ||
|
|
8677971360 | ||
|
|
8aeea8c210 | ||
|
|
15cb40dac2 | ||
|
|
4757561861 | ||
|
|
ca6e471a3f | ||
|
|
6339fd739e | ||
|
|
fa6d4c3d63 | ||
|
|
eb74450fca | ||
|
|
3bcfbbe92a | ||
|
|
b89bc044a3 | ||
|
|
2f1a8b2bad | ||
|
|
670993a89d | ||
|
|
3cd464263b | ||
|
|
3f810d35bf | ||
|
|
2049c7fe32 | ||
|
|
80f1ebda88 | ||
|
|
e1d35aca01 | ||
|
|
e8da0e5b47 | ||
|
|
0d85627bf0 | ||
|
|
ae9552663d | ||
|
|
e97fe246aa | ||
|
|
0be39e5032 | ||
|
|
c77d60d6b8 | ||
|
|
747dfe578e | ||
|
|
153b79a290 | ||
|
|
d22c68f904 | ||
|
|
8e65ce37df | ||
|
|
fe3ae0ccac | ||
|
|
5aa6440cbc | ||
|
|
8d2b548f84 | ||
|
|
c5287c0b0d | ||
|
|
a0bad9a048 | ||
|
|
effcb5a03c | ||
|
|
8f3b6990dd | ||
|
|
ef536f48fd | ||
|
|
b22ce4abb5 | ||
|
|
a6a27f56f2 | ||
|
|
5f5ddfd1a1 | ||
|
|
f2adcdedbc | ||
|
|
3dc246cd53 | ||
|
|
5f45bdf542 | ||
|
|
2ba0194fbe | ||
|
|
8e89e0b89f | ||
|
|
f12001e0cc | ||
|
|
f887464840 | ||
|
|
ef741a95a2 | ||
|
|
40c140bf16 | ||
|
|
bbc0dcbb9a | ||
|
|
78e21daafe | ||
|
|
1f062ee4a9 | ||
|
|
f9b9140f10 | ||
|
|
50ed2b4cb5 | ||
|
|
1abd489d07 | ||
|
|
63d841c3ae | ||
|
|
0f5190f103 | ||
|
|
f4d176b3b7 | ||
|
|
9accf8bd24 | ||
|
|
7d5708180e | ||
|
|
36a1f02835 | ||
|
|
462e603a1f | ||
|
|
1b615c774d | ||
|
|
8a6ae0e4dd | ||
|
|
bd1b241faa | ||
|
|
c4d74587b5 | ||
|
|
4ecc07fcff | ||
|
|
31a4356ac0 | ||
|
|
44157ee35e | ||
|
|
220a577d3c | ||
|
|
7e866b47e3 | ||
|
|
8b9fd4b752 | ||
|
|
4f0d3118e1 | ||
|
|
a441d3db44 | ||
|
|
ee376cdc3c | ||
|
|
708bfd5de5 | ||
|
|
28bebc2a51 | ||
|
|
3f588a31af | ||
|
|
fb5230dd3c | ||
|
|
753c9f616f | ||
|
|
1ba5799f7b | ||
|
|
20f163eb3d | ||
|
|
e86882efe9 | ||
|
|
b3ce4c28ca | ||
|
|
62059a2438 | ||
|
|
fe16003e66 | ||
|
|
b7aa8b5219 | ||
|
|
8a65b65d63 | ||
|
|
6088387b6f | ||
|
|
67187687b1 | ||
|
|
3de7fa257f | ||
|
|
707757e478 | ||
|
|
33319b80a7 | ||
|
|
50f1469961 | ||
|
|
9152df61da | ||
|
|
dd81afa2c8 | ||
|
|
b6ea0dd2a3 | ||
|
|
c6f190a7ab | ||
|
|
280dab0b8a | ||
|
|
1f7bcecc94 | ||
|
|
53e4631e79 | ||
|
|
c7c278c6c1 | ||
|
|
6ec7b16cc1 | ||
|
|
b90a1df833 | ||
|
|
95a46111ff | ||
|
|
b89e644ec3 | ||
|
|
fb208c0792 | ||
|
|
832c723f9c | ||
|
|
5072fe0c2d | ||
|
|
b5f728f6a0 | ||
|
|
6ef4b00925 | ||
|
|
ee64353a7b | ||
|
|
07c5e5640b | ||
|
|
7842df622f | ||
|
|
099203cb2a | ||
|
|
95314a93e5 | ||
|
|
98328ae2ec | ||
|
|
a9838e6a4b | ||
|
|
3cc7e9956f | ||
|
|
8e8483aac4 | ||
|
|
e8887dc44c | ||
|
|
9601c31125 | ||
|
|
ded623d8ea | ||
|
|
21b0f4ed4e | ||
|
|
23e58f5107 | ||
|
|
8387f9e164 | ||
|
|
bf6a5688ad | ||
|
|
d30b7255cb | ||
|
|
3339b753b5 | ||
|
|
b5a37c0e42 | ||
|
|
02700f11cd | ||
|
|
bafaec5b6a | ||
|
|
bf6287d087 | ||
|
|
0024d5dfde | ||
|
|
ca681f997e | ||
|
|
591fc90263 | ||
|
|
319153fe46 | ||
|
|
beb75e40bf | ||
|
|
cecdc99873 | ||
|
|
37f685908e | ||
|
|
48aa1ce524 | ||
|
|
b209e8afde | ||
|
|
a765ee8238 | ||
|
|
6e2cbd51c3 | ||
|
|
18538461d1 | ||
|
|
3ff37ea815 | ||
|
|
d099c10581 | ||
|
|
5dec7d3675 | ||
|
|
262ed50201 | ||
|
|
c4baa64ea8 | ||
|
|
e3c38aab66 | ||
|
|
b9c00b27f8 | ||
|
|
5656e99c7c | ||
|
|
a2e901c93d | ||
|
|
25f780b662 | ||
|
|
5c5c7c5a2c | ||
|
|
818046d031 | ||
|
|
e8240017fd | ||
|
|
e76e3af5b7 | ||
|
|
d2da5cca37 | ||
|
|
8816bcd973 | ||
|
|
1ab0cd228f | ||
|
|
cfa6129040 | ||
|
|
2761a2bd7b | ||
|
|
c42d07a76e | ||
|
|
c118f91b25 | ||
|
|
39881fa6f2 | ||
|
|
609acaad02 | ||
|
|
7f541b9607 | ||
|
|
c93e4a62c6 | ||
|
|
9bbeaa312d | ||
|
|
11e73c66a8 | ||
|
|
221409788a | ||
|
|
8da9c04bb8 | ||
|
|
7e7c7b0558 | ||
|
|
23a947ee3d | ||
|
|
cb842d6cc2 | ||
|
|
b23122d002 | ||
|
|
3c96680cfb | ||
|
|
60a4fa2e4b | ||
|
|
cc1660abdd | ||
|
|
391530cd15 | ||
|
|
e396dd3385 | ||
|
|
858a8800e2 | ||
|
|
29f733484a | ||
|
|
5aa47347e7 | ||
|
|
73249497d4 | ||
|
|
85ab4cab58 | ||
|
|
154fdcee01 | ||
|
|
da4c5eec82 | ||
|
|
0cb1ee0dc1 | ||
|
|
9ca860be9e | ||
|
|
4eda670de9 | ||
|
|
7511a1919b | ||
|
|
16068d6b63 | ||
|
|
ea72406275 | ||
|
|
eb2661a46d | ||
|
|
0ef4a4e7e3 | ||
|
|
29346eb7dd | ||
|
|
174307530b | ||
|
|
e4b69f9a13 | ||
|
|
860b8c772a | ||
|
|
877c09f662 | ||
|
|
238a626859 | ||
|
|
fde3576f88 | ||
|
|
c5f3d3e11c | ||
|
|
0d5d75e04d | ||
|
|
1e974e6311 | ||
|
|
a9f0f07b7c | ||
|
|
dd67275033 | ||
|
|
2e69130ea3 | ||
|
|
081693f32e | ||
|
|
e6e0cf5764 | ||
|
|
33391a7b61 | ||
|
|
ddbd96d8ae | ||
|
|
f3316c3a1a | ||
|
|
23aca15c8c | ||
|
|
9b7254c810 | ||
|
|
ec84bd6143 | ||
|
|
679e009b73 | ||
|
|
4499d68748 | ||
|
|
3bcacd8a21 | ||
|
|
5299ccf2a9 | ||
|
|
dd57b141b9 | ||
|
|
cb71dec0c3 | ||
|
|
08b63b6ceb | ||
|
|
3c57a8fc92 | ||
|
|
86c63588b0 | ||
|
|
f56e0115fb | ||
|
|
82fbf213eb | ||
|
|
8d542434bd | ||
|
|
c8e1e833cd | ||
|
|
39200596f4 | ||
|
|
0288d70820 | ||
|
|
5ee05d8de4 | ||
|
|
bb6389e823 | ||
|
|
1102415f46 | ||
|
|
b632e9df6a | ||
|
|
7a3cb3b4b3 | ||
|
|
837b14c89a | ||
|
|
42f36755ed | ||
|
|
6d3e1fd4ac | ||
|
|
0a6eacff1f | ||
|
|
fafbf53e98 | ||
|
|
a760d1769a | ||
|
|
52942eebeb | ||
|
|
90ba7ba4dc | ||
|
|
d25e582a17 | ||
|
|
1fddc44c61 | ||
|
|
189b7609f5 | ||
|
|
a64e725034 | ||
|
|
dc94d36295 | ||
|
|
b9ad4f6782 | ||
|
|
4f9bab773c | ||
|
|
7f1bafc922 | ||
|
|
3e2003ee0f | ||
|
|
32497c0a5f | ||
|
|
d4307af631 | ||
|
|
e436a0e548 | ||
|
|
82a296893f | ||
|
|
bf0ba1dcc4 | ||
|
|
b5f106110a | ||
|
|
88cbd3e088 | ||
|
|
3bb9fc7308 | ||
|
|
671953d021 | ||
|
|
a3ae6cb0d6 | ||
|
|
e304356f59 | ||
|
|
060c6901a3 | ||
|
|
3b74c5ce02 | ||
|
|
6ea4e2c949 | ||
|
|
91b5808fd6 | ||
|
|
0cbdc8eb37 | ||
|
|
f74b04f563 |
+16
-13
@@ -2,12 +2,16 @@ name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
||||
test-ubuntu-latest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: make
|
||||
# Fail build if there are warnings
|
||||
# build with TLS just for compilation coverage
|
||||
@@ -26,10 +30,10 @@ jobs:
|
||||
test-sanitizer-address:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: make
|
||||
# build with TLS module just for compilation coverage
|
||||
run: make SANITIZER=address REDIS_CFLAGS='-Werror -DDEBUG_ASSERTIONS' BUILD_TLS=module
|
||||
run: make SANITIZER=address REDIS_CFLAGS='-Werror -DDEBUG_ASSERTIONS -DREDIS_TEST' BUILD_TLS=module
|
||||
- name: testprep
|
||||
run: sudo apt-get install tcl8.6 tclx -y
|
||||
- name: test
|
||||
@@ -39,7 +43,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container: debian:buster
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: make
|
||||
run: |
|
||||
sed -i 's|http://deb.debian.org/debian|http://archive.debian.org/debian|g' /etc/apt/sources.list
|
||||
@@ -50,7 +54,7 @@ jobs:
|
||||
build-macos-latest:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: make
|
||||
# Fail build if there are warnings
|
||||
# build with TLS just for compilation coverage
|
||||
@@ -59,16 +63,16 @@ jobs:
|
||||
build-32bit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386 gcc-multilib g++-multilib
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386 gcc-multilib
|
||||
make REDIS_CFLAGS='-Werror' 32bit
|
||||
|
||||
build-libc-malloc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: make
|
||||
run: make REDIS_CFLAGS='-Werror' MALLOC=libc
|
||||
|
||||
@@ -76,17 +80,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container: quay.io/centos/centos:stream9
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: make
|
||||
run: |
|
||||
dnf -y install which gcc gcc-c++ make
|
||||
dnf -y install which gcc make
|
||||
make REDIS_CFLAGS='-Werror'
|
||||
|
||||
build-old-chain-jemalloc:
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: make
|
||||
run: |
|
||||
apt-get update
|
||||
@@ -96,7 +100,6 @@ jobs:
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
apt-get install -y make gcc-4.8 g++-4.8
|
||||
apt-get install -y make gcc-4.8
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
|
||||
make CC=gcc REDIS_CFLAGS='-Werror'
|
||||
|
||||
@@ -4,13 +4,21 @@ name: "Codecov"
|
||||
# where each PR needs to be compared against the coverage of the head commit
|
||||
on: [push, pull_request]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
code-coverage:
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.repository == 'redis/redis' }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install lcov and run test
|
||||
run: |
|
||||
@@ -18,7 +26,11 @@ jobs:
|
||||
make lcov
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ./src/redis.info
|
||||
files: ./src/redis.info
|
||||
disable_search: true
|
||||
fail_ci_if_error: true
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ on:
|
||||
# run weekly new vulnerability was added to the database
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
@@ -19,15 +23,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
|
||||
@@ -6,17 +6,24 @@ on:
|
||||
- cron: '0 0 * * *'
|
||||
# Support manual execution
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
coverity:
|
||||
if: github.repository == 'redis/redis'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
- uses: actions/checkout@v6
|
||||
- name: Download and extract the Coverity Build Tool
|
||||
run: |
|
||||
wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=${{ secrets.COVERITY_SCAN_TOKEN }}&project=redis-unstable" -O cov-analysis-linux64.tar.gz
|
||||
wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=${COVERITY_SCAN_TOKEN}&project=redis-unstable" -O cov-analysis-linux64.tar.gz
|
||||
mkdir cov-analysis-linux64
|
||||
tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64
|
||||
env:
|
||||
COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
|
||||
- name: Install Redis dependencies
|
||||
run: sudo apt install -y gcc tcl8.6 tclx procps libssl-dev
|
||||
- name: Build with cov-build
|
||||
@@ -26,7 +33,10 @@ jobs:
|
||||
tar czvf cov-int.tgz cov-int
|
||||
curl \
|
||||
--form project=redis-unstable \
|
||||
--form email=${{ secrets.COVERITY_SCAN_EMAIL }} \
|
||||
--form token=${{ secrets.COVERITY_SCAN_TOKEN }} \
|
||||
--form email="${COVERITY_SCAN_EMAIL}" \
|
||||
--form token="${COVERITY_SCAN_TOKEN}" \
|
||||
--form file=@cov-int.tgz \
|
||||
https://scan.coverity.com/builds
|
||||
env:
|
||||
COVERITY_SCAN_EMAIL: ${{ secrets.COVERITY_SCAN_EMAIL }}
|
||||
COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
|
||||
|
||||
+279
-96
@@ -11,7 +11,7 @@ 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,centos,malloc,specific,fortify,reply-schema,oldTC,defrag'
|
||||
default: 'valgrind,sanitizer,tls,freebsd,macos,alpine,32bit,iothreads,ubuntu,centos,malloc,specific,fortify,reply-schema,oldTC,defrag,vectorset,assert-keyspace,arm'
|
||||
skiptests:
|
||||
description: 'tests to skip (delete the ones you wanna keep, do not leave empty)'
|
||||
default: 'redis,modules,sentinel,cluster,unittest'
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'ubuntu')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -47,12 +47,12 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST -DDEBUG_ASSERTIONS'
|
||||
- name: testprep
|
||||
run: sudo apt-get install tcl8.6 tclx
|
||||
- name: test
|
||||
@@ -68,12 +68,129 @@ jobs:
|
||||
if: true && !contains(github.event.inputs.skiptests, 'unittest')
|
||||
run: ./src/redis-server test all --accurate
|
||||
|
||||
test-ubuntu-jemalloc-fortify:
|
||||
test-ubuntu-arm:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'arm')
|
||||
timeout-minutes: 360
|
||||
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@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y tcl8.6 tclx
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: ./runtest --accurate --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/redis-server test all --accurate
|
||||
|
||||
test-ubuntu-arm-libc-malloc:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
(!contains(github.event.inputs.skipjobs, 'arm') || !contains(github.event.inputs.skipjobs, 'malloc'))
|
||||
timeout-minutes: 360
|
||||
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@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: make MALLOC=libc REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y tcl8.6 tclx
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: ./runtest --accurate --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}}
|
||||
|
||||
test-ubuntu-arm-tls:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
(!contains(github.event.inputs.skipjobs, 'arm') || !contains(github.event.inputs.skipjobs, 'tls'))
|
||||
timeout-minutes: 360
|
||||
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@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: make BUILD_TLS=yes REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y tcl8.6 tclx tcl-tls
|
||||
./utils/gen-test-certs.sh
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: ./runtest --accurate --verbose --tls --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel --tls ${{github.event.inputs.cluster_test_args}}
|
||||
- name: cluster tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'cluster')
|
||||
run: ./runtest-cluster --tls ${{github.event.inputs.cluster_test_args}}
|
||||
|
||||
# Test with DEBUG_ASSERT_KEYSPACE enabled to verify keyspace consistency.
|
||||
# This enables additional runtime checks after each command for:
|
||||
# - Info keysizes histogram
|
||||
# - Cluster slot stats
|
||||
# Skips slow and defrag tests to avoid timeouts while maintaining good coverage.
|
||||
test-debug-assert-keyspace:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'fortify')
|
||||
timeout-minutes: 14400
|
||||
!contains(github.event.inputs.skipjobs, 'assert-keyspace')
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -84,14 +201,48 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST -DDEBUG_ASSERT_KEYSPACE -DDEBUG_ASSERTIONS'
|
||||
- name: testprep
|
||||
run: sudo apt-get install tcl8.6 tclx
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: |
|
||||
./runtest --verbose --tags "-slow -defrag" \
|
||||
--dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: cluster tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'cluster')
|
||||
run: ./runtest-cluster ${{github.event.inputs.cluster_test_args}}
|
||||
|
||||
test-ubuntu-jemalloc-fortify:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'fortify')
|
||||
timeout-minutes: 360
|
||||
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@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: |
|
||||
apt-get update && apt-get install -y make gcc g++
|
||||
make CC=gcc REDIS_CFLAGS='-Werror -DREDIS_TEST -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3'
|
||||
apt-get update && apt-get install -y make gcc
|
||||
# Also enables jemalloc's sized deallocation checks to catch sdallocx()/zfree_with_size() misuse.
|
||||
make CC=gcc REDIS_CFLAGS='-Werror -DREDIS_TEST -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3' JEMALLOC_CONFIGURE_OPTS='--enable-opt-size-checks'
|
||||
- name: testprep
|
||||
run: sudo apt-get install -y tcl8.6 tclx procps
|
||||
- name: test
|
||||
@@ -112,7 +263,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'malloc')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -123,7 +274,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -146,7 +297,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'malloc')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -157,7 +308,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -180,7 +331,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, '32bit')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -191,13 +342,13 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386 g++ gcc-multilib g++-multilib
|
||||
sudo apt-get update && sudo apt-get install libc6-dev-i386 gcc-multilib
|
||||
make 32bit REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
make -C tests/modules 32bit # the script below doesn't have an argument, we must build manually ahead of time
|
||||
- name: testprep
|
||||
@@ -220,7 +371,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'tls')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -231,7 +382,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -260,7 +411,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'tls')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -271,7 +422,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -300,7 +451,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'iothreads')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -311,7 +462,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -322,7 +473,7 @@ jobs:
|
||||
run: sudo apt-get install tcl8.6 tclx
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: ./runtest --config io-threads 4 --accurate --verbose --tags network --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest --config io-threads 4 --accurate --verbose --tags "network iothreads psync2 repl failover" --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: cluster tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'cluster')
|
||||
run: ./runtest-cluster --config io-threads 4 ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -332,7 +483,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'specific')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -343,7 +494,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -410,7 +561,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'valgrind') && !contains(github.event.inputs.skiptests, 'redis')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -421,7 +572,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -430,17 +581,20 @@ jobs:
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install tcl8.6 tclx valgrind g++ -y
|
||||
sudo apt-get install tcl8.6 tclx valgrind -y
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: ./runtest --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
|
||||
# Note that valgrind's overhead doesn't pair well with io-threads so we
|
||||
# explicitly disable tests tagged with 'iothreads' - these are tests that
|
||||
# always run with io-threads enabled.
|
||||
run: ./runtest --valgrind --no-latency --verbose --clients 1 --timeout 2400 --tags -iothreads --dump-logs ${{github.event.inputs.test_args}}
|
||||
|
||||
test-valgrind-misc:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'valgrind') && !(contains(github.event.inputs.skiptests, 'modules') && contains(github.event.inputs.skiptests, 'unittest'))
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -451,7 +605,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -472,7 +626,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'valgrind') && !contains(github.event.inputs.skiptests, 'redis')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -483,7 +637,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -492,17 +646,17 @@ jobs:
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install tcl8.6 tclx valgrind g++ -y
|
||||
sudo apt-get install tcl8.6 tclx valgrind -y
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: ./runtest --valgrind --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
|
||||
run: ./runtest --valgrind --tags -iothreads --no-latency --verbose --clients 1 --timeout 2400 --dump-logs ${{github.event.inputs.test_args}}
|
||||
|
||||
test-valgrind-no-malloc-usable-size-misc:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'valgrind') && !(contains(github.event.inputs.skiptests, 'modules') && contains(github.event.inputs.skiptests, 'unittest'))
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -513,7 +667,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -534,7 +688,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'sanitizer')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: [ gcc, clang ]
|
||||
@@ -550,7 +704,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -578,9 +732,9 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'sanitizer')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
CC: clang # MSan work only with clang
|
||||
CC: clang # MSan works only with clang
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -591,7 +745,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -619,7 +773,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'sanitizer')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: [ gcc, clang ]
|
||||
@@ -635,7 +789,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -663,8 +817,9 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'sanitizer')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
strategy:
|
||||
fail-fast: false # let gcc and clang both run until the end even if one of them fails
|
||||
matrix:
|
||||
compiler: [ gcc, clang ]
|
||||
env:
|
||||
@@ -679,7 +834,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -696,7 +851,7 @@ jobs:
|
||||
run: ./runtest --tsan --clients 1 --config io-threads 4 --accurate --verbose --dump-logs ${{github.event.inputs.test_args}}
|
||||
- name: sentinel tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
run: ./runtest-sentinel --config io-threads 2 ${{github.event.inputs.cluster_test_args}}
|
||||
run: ./runtest-sentinel --tsan ${{github.event.inputs.cluster_test_args}}
|
||||
- name: cluster tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'cluster')
|
||||
run: ./runtest-cluster --config io-threads 2 ${{github.event.inputs.cluster_test_args}}
|
||||
@@ -707,7 +862,7 @@ jobs:
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'centos')
|
||||
container: quay.io/centos/centos:stream9
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -718,13 +873,13 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: |
|
||||
dnf -y install which gcc make g++
|
||||
dnf -y install which gcc make
|
||||
make REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
@@ -746,7 +901,7 @@ jobs:
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'tls')
|
||||
container: quay.io/centos/centos:stream9
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -757,13 +912,13 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: |
|
||||
dnf -y install which gcc make openssl-devel openssl g++
|
||||
dnf -y install which gcc make openssl-devel openssl
|
||||
make BUILD_TLS=module REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
@@ -789,7 +944,7 @@ jobs:
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'tls')
|
||||
container: quay.io/centos/centos:stream9
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -800,13 +955,13 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: |
|
||||
dnf -y install which gcc make openssl-devel openssl g++
|
||||
dnf -y install which gcc make openssl-devel openssl
|
||||
make BUILD_TLS=module REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
@@ -831,7 +986,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'macos') && !(contains(github.event.inputs.skiptests, 'redis') && contains(github.event.inputs.skiptests, 'modules'))
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -842,7 +997,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -857,7 +1012,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'macos') && !contains(github.event.inputs.skiptests, 'sentinel')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -868,7 +1023,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -883,7 +1038,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'macos') && !contains(github.event.inputs.skiptests, 'cluster')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -894,7 +1049,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -907,12 +1062,12 @@ jobs:
|
||||
build-macos:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-13, macos-15]
|
||||
os: [macos-14, macos-26]
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'macos')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
@@ -926,7 +1081,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -934,33 +1089,30 @@ jobs:
|
||||
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
|
||||
test-freebsd:
|
||||
runs-on: macos-13
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'freebsd')
|
||||
timeout-minutes: 14400
|
||||
env:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
timeout-minutes: 360
|
||||
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
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: test
|
||||
uses: cross-platform-actions/action@v0.22.0
|
||||
uses: cross-platform-actions/action@v1.0.0
|
||||
with:
|
||||
operating_system: freebsd
|
||||
environment_variables: MAKE
|
||||
version: 13.2
|
||||
shell: bash
|
||||
run: |
|
||||
sudo pkg install -y bash gmake lang/tcl86 lang/tclx gcc
|
||||
sudo pkg install -y bash gmake lang/tcl86 lang/tclX gcc
|
||||
gmake
|
||||
./runtest --single unit/keyspace --single unit/auth --single unit/networking --single unit/protocol
|
||||
|
||||
@@ -980,7 +1132,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -1016,7 +1168,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -1038,7 +1190,7 @@ jobs:
|
||||
|
||||
reply-schemas-validator:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'reply-schema')
|
||||
@@ -1052,7 +1204,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -1069,10 +1221,14 @@ jobs:
|
||||
- name: cluster tests
|
||||
if: true && !contains(github.event.inputs.skiptests, 'cluster')
|
||||
run: ./runtest-cluster --log-req-res --dont-clean --force-resp3 ${{github.event.inputs.cluster_test_args}}
|
||||
- name: Install Python dependencies
|
||||
uses: py-actions/py-dependency-install@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
path: "./utils/req-res-validator/requirements.txt"
|
||||
python-version: "3.x"
|
||||
cache: "pip"
|
||||
cache-dependency-path: "./utils/req-res-validator/requirements.txt"
|
||||
- name: Install Python dependencies
|
||||
run: python -m pip install -r ./utils/req-res-validator/requirements.txt
|
||||
- name: validator
|
||||
run: ./utils/req-res-log-validator.py --verbose --fail-missing-reply-schemas ${{ (!contains(github.event.inputs.skiptests, 'redis') && !contains(github.event.inputs.skiptests, 'module') && !contains(github.event.inputs.sentinel, 'redis') && !contains(github.event.inputs.skiptests, 'cluster')) && github.event.inputs.test_args == '' && github.event.inputs.cluster_test_args == '' && '--fail-commands-not-all-hit' || '' }}
|
||||
|
||||
@@ -1082,7 +1238,7 @@ jobs:
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'oldTC')
|
||||
container: ubuntu:20.04
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -1093,7 +1249,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -1106,9 +1262,8 @@ jobs:
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
apt-get install -y make gcc-4.8 g++-4.8
|
||||
apt-get install -y make gcc-4.8
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
|
||||
make CC=gcc REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: apt-get install -y tcl tcltls tclx
|
||||
@@ -1128,7 +1283,7 @@ jobs:
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'tls') && !contains(github.event.inputs.skipjobs, 'oldTC')
|
||||
container: ubuntu:20.04
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -1139,7 +1294,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -1152,10 +1307,9 @@ jobs:
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
apt-get install -y make gcc-4.8 g++-4.8 openssl libssl-dev
|
||||
apt-get install -y make gcc-4.8 openssl libssl-dev
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
|
||||
make CC=gcc CXX=g++ BUILD_TLS=module REDIS_CFLAGS='-Werror'
|
||||
make CC=gcc BUILD_TLS=module REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
apt-get install -y tcl tcltls tclx
|
||||
@@ -1179,7 +1333,7 @@ jobs:
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'tls') && !contains(github.event.inputs.skipjobs, 'oldTC')
|
||||
container: ubuntu:20.04
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -1190,7 +1344,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -1203,9 +1357,8 @@ jobs:
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32
|
||||
apt-get update
|
||||
apt-get install -y make gcc-4.8 g++-4.8 openssl libssl-dev
|
||||
apt-get install -y make gcc-4.8 openssl libssl-dev
|
||||
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100
|
||||
update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100
|
||||
make BUILD_TLS=module CC=gcc REDIS_CFLAGS='-Werror'
|
||||
- name: testprep
|
||||
run: |
|
||||
@@ -1229,7 +1382,7 @@ jobs:
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'defrag')
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- name: prep
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
@@ -1240,7 +1393,7 @@ jobs:
|
||||
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@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
@@ -1251,3 +1404,33 @@ jobs:
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: ./runtest --debug-defrag --verbose --clients 1 ${{github.event.inputs.test_args}}
|
||||
|
||||
test-vectorset:
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
(github.event_name == 'workflow_dispatch' || (github.event_name != 'workflow_dispatch' && github.repository == 'redis/redis')) &&
|
||||
!contains(github.event.inputs.skipjobs, 'vectorset')
|
||||
timeout-minutes: 360
|
||||
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@v6
|
||||
with:
|
||||
repository: ${{ env.GITHUB_REPOSITORY }}
|
||||
ref: ${{ env.GITHUB_HEAD_REF }}
|
||||
- name: make
|
||||
run: make REDIS_CFLAGS='-Werror -DREDIS_TEST'
|
||||
- name: testprep
|
||||
run: |
|
||||
sudo apt-get install tcl8.6 tclx
|
||||
sudo pip install redis
|
||||
- name: test
|
||||
if: true && !contains(github.event.inputs.skiptests, 'redis')
|
||||
run: ./runtest --accurate --verbose --dump-logs --single vectorset/vectorset ${{github.event.inputs.test_args}}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
name: Docker
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main, dev, test, unstable]
|
||||
tags: ['v*']
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
jobs:
|
||||
docker:
|
||||
uses: hanzoai/.github/.github/workflows/docker-build.yml@main
|
||||
with:
|
||||
image: ghcr.io/hanzoai/memory
|
||||
secrets: inherit
|
||||
@@ -6,13 +6,17 @@ on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test-external-standalone:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'schedule' || github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build
|
||||
run: make REDIS_CFLAGS=-Werror
|
||||
- name: Start redis-server
|
||||
@@ -27,7 +31,7 @@ jobs:
|
||||
--tags -slow
|
||||
- name: Archive redis log
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: test-external-redis-log
|
||||
path: external-redis.log
|
||||
@@ -35,9 +39,9 @@ jobs:
|
||||
test-external-cluster:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'schedule' || github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build
|
||||
run: make REDIS_CFLAGS=-Werror
|
||||
- name: Start redis-server
|
||||
@@ -55,7 +59,7 @@ jobs:
|
||||
--tags -slow
|
||||
- name: Archive redis log
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: test-external-cluster-log
|
||||
path: external-redis-cluster.log
|
||||
@@ -63,9 +67,9 @@ jobs:
|
||||
test-external-nodebug:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name != 'schedule' || github.repository == 'redis/redis'
|
||||
timeout-minutes: 14400
|
||||
timeout-minutes: 360
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build
|
||||
run: make REDIS_CFLAGS=-Werror
|
||||
- name: Start redis-server
|
||||
@@ -79,7 +83,7 @@ jobs:
|
||||
--tags "-slow -needs:debug"
|
||||
- name: Archive redis log
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: test-external-redis-nodebug-log
|
||||
path: external-redis-nodebug.log
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
- name: Generate a token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@v1
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.DOCS_APP_ID }}
|
||||
private-key: ${{ secrets.DOCS_APP_PRIVATE_KEY }}
|
||||
|
||||
@@ -8,13 +8,17 @@ on:
|
||||
paths:
|
||||
- 'src/commands/*.json'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
reply-schemas-linter:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup nodejs
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
- name: Install packages
|
||||
run: npm install ajv
|
||||
- name: linter
|
||||
|
||||
@@ -9,6 +9,10 @@ on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Spellcheck
|
||||
@@ -16,10 +20,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: pip cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
name: Workflow Sanity
|
||||
on:
|
||||
pull_request:
|
||||
paths: ['.github/workflows/**']
|
||||
jobs:
|
||||
sanity:
|
||||
uses: hanzoai/.github/.github/workflows/workflow-sanity.yml@main
|
||||
+1
-1
@@ -30,7 +30,7 @@ deps/lua/src/luac
|
||||
deps/lua/src/liblua.a
|
||||
deps/hdr_histogram/libhdrhistogram.a
|
||||
deps/fpconv/libfpconv.a
|
||||
deps/fast_float/libfast_float.a
|
||||
deps/tre/libtre.a
|
||||
tests/tls/*
|
||||
.make-*
|
||||
.prerequisites
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
FROM --platform=$TARGETPLATFORM alpine:latest AS builder
|
||||
|
||||
RUN apk add --no-cache gcc g++ make musl-dev linux-headers
|
||||
|
||||
WORKDIR /src
|
||||
COPY . /src
|
||||
|
||||
RUN make -j$(nproc) BUILD_TLS=no MALLOC=libc
|
||||
|
||||
FROM --platform=$TARGETPLATFORM alpine:latest
|
||||
|
||||
LABEL maintainer="dev@hanzo.ai"
|
||||
LABEL org.opencontainers.image.title="Hanzo Memory"
|
||||
LABEL org.opencontainers.image.description="Redis-compatible in-memory store — Hanzo Memory"
|
||||
LABEL org.opencontainers.image.vendor="Hanzo AI"
|
||||
LABEL org.opencontainers.image.source="https://github.com/hanzoai/redis"
|
||||
|
||||
RUN addgroup -S memory && adduser -S memory -G memory
|
||||
|
||||
COPY --from=builder /src/src/redis-server /usr/local/bin/redis-server
|
||||
COPY --from=builder /src/src/redis-cli /usr/local/bin/redis-cli
|
||||
COPY --from=builder /src/src/redis-sentinel /usr/local/bin/redis-sentinel
|
||||
COPY --from=builder /src/src/redis-benchmark /usr/local/bin/redis-benchmark
|
||||
|
||||
RUN mkdir -p /data && chown memory:memory /data
|
||||
VOLUME /data
|
||||
WORKDIR /data
|
||||
|
||||
EXPOSE 6379
|
||||
|
||||
USER memory
|
||||
|
||||
HEALTHCHECK --interval=15s --timeout=3s --start-period=10s --retries=3 \
|
||||
CMD redis-cli ping | grep -q PONG || exit 1
|
||||
|
||||
ENTRYPOINT ["redis-server"]
|
||||
CMD ["--bind", "0.0.0.0", "--dir", "/data", "--maxmemory-policy", "allkeys-lru", "--protected-mode", "no"]
|
||||
@@ -0,0 +1,7 @@
|
||||
# redis
|
||||
|
||||
[](https://codecov.io/github/redis/redis)
|
||||
|
||||
This document serves as both a quick start guide to Redis and a detailed resource for building it from source.
|
||||
|
||||
- New to Redis? Start with [What is Redis](#what-is-redis) and [Getting Started](#getting-started)
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
SUBDIRS = src
|
||||
ifeq ($(BUILD_WITH_MODULES), yes)
|
||||
ifeq ($(MAKECMDGOALS),32bit)
|
||||
$(error BUILD_WITH_MODULES=yes is not supported on 32 bit systems)
|
||||
endif
|
||||
SUBDIRS += modules
|
||||
endif
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ This document serves as both a quick start guide to Redis and a detailed resourc
|
||||
- [Using Redis with redis-cli](#using-redis-with-redis-cli)
|
||||
- [Using Redis with Redis Insight](#using-redis-with-redis-insight)
|
||||
- [Redis data types, processing engines, and capabilities](#redis-data-types-processing-engines-and-capabilities)
|
||||
- [Cloud hosted Redis](#cloud-hosted-redis)
|
||||
- [Community](#community)
|
||||
- [Build Redis from source](#build-redis-from-source)
|
||||
- [Build and run Redis with all data structures - Ubuntu 20.04 (Focal)](#build-and-run-redis-with-all-data-structures---ubuntu-2004-focal)
|
||||
@@ -52,7 +53,7 @@ Redis excels in various applications, including:
|
||||
- **Distributed Session Store:** Offers flexible session data modeling (string, JSON, hash).
|
||||
- **Data Structure Server:** Provides low-level data structures (strings, lists, sets, hashes, sorted sets, JSON, etc.) with high-level semantics (counters, queues, leaderboards, rate limiters) and supports transactions & scripting.
|
||||
- **NoSQL Data Store:** Key-value, document, and time series data storage.
|
||||
- **Search and Query Engine:** Indexing for hash/JSON documents, supporting vector search, full-text search, geospatial queries, ranking, and aggregations via Redis Query Engine.
|
||||
- **Search and Query Engine:** Indexing for hash/JSON documents, supporting vector search, full-text search, geospatial queries, ranking, and aggregations via Redis Search.
|
||||
- **Event Store & Message Broker:** Implements queues (lists), priority queues (sorted sets), event deduplication (sets), streams, and pub/sub with probabilistic stream processing capabilities.
|
||||
- **Vector Store for GenAI:** Integrates with AI applications (e.g. LangGraph, mem0) for short-term memory, long-term memory, LLM response caching (semantic caching), and retrieval augmented generation (RAG).
|
||||
- **Real-Time Analytics:** Powers personalization, recommendations, fraud detection, and risk assessment.
|
||||
@@ -171,9 +172,10 @@ Redis provides a variety of data types, processing engines, and capabilities to
|
||||
**Important:** Features marked with an asterisk (\*) require Redis to be compiled with the `BUILD_WITH_MODULES=yes` flag when [building Redis from source](#build-redis-from-source)
|
||||
|
||||
- [**String:**](https://redis.io/docs/latest/develop/data-types/strings) Sequences of bytes, including text, serialized objects, and binary arrays used for caching, counters, and bitwise operations.
|
||||
- [**JSON:**](https://redis.io/docs/latest/develop/data-types/json/) Nested JSON documents that are indexed and searchable using JSONPath expressions and with [Redis Query Engine](https://redis.io/docs/latest/develop/interact/search-and-query/)
|
||||
- [**JSON:**](https://redis.io/docs/latest/develop/data-types/json/) Nested JSON documents that are indexed and searchable using JSONPath expressions and with [Redis Search](https://redis.io/docs/latest/develop/ai/search-and-query/)
|
||||
- [**Array:**](https://redis.io/docs/latest/develop/data-types/arrays/) Sparse, index-addressable collection of string values
|
||||
- [**Hash:**](https://redis.io/docs/latest/develop/data-types/hashes/) Field-value maps used to represent basic objects and store groupings of key-value pairs with support for [hash field expiration (TTL)](https://redis.io/docs/latest/develop/data-types/hashes/#field-expiration)
|
||||
- [**Redis Query Engine:**](https://redis.io/docs/latest/develop/interact/search-and-query/) Use Redis as a document database, a vector database, a secondary index, and a search engine. Define indexes for hash and JSON documents and then use a rich query language for vector search, full-text search, geospatial queries, and aggregations.
|
||||
- [**Redis Search:**](https://redis.io/docs/latest/develop/ai/search-and-query/) Use Redis as a document database, a vector database, a secondary index, and a search engine. Define indexes for hash and JSON documents and then use a rich query language for vector search, full-text search, geospatial queries, and aggregations.
|
||||
- [**List:**](https://redis.io/docs/latest/develop/data-types/lists/) Linked lists of string values used as stacks, queues, and for queue management.
|
||||
- [**Set:**](https://redis.io/docs/latest/develop/data-types/sets/) Unordered collection of unique strings used for tracking unique items, relations, and common set operations (intersections, unions, differences).
|
||||
- [**Sorted set:**](https://redis.io/docs/latest/develop/data-types/sorted-sets/) Collection of unique strings ordered by an associated score used for leaderboards and rate limiters.
|
||||
@@ -194,6 +196,12 @@ Redis provides a variety of data types, processing engines, and capabilities to
|
||||
- [**Transaction:**](https://redis.io/docs/latest/develop/interact/transactions/) Allows the execution of a group of commands in a single step. A request sent by another client will never be served in the middle of the execution of a transaction. This guarantees that the commands are executed as a single isolated operation.
|
||||
- [**Programmability:**](https://redis.io/docs/latest/develop/interact/programmability/eval-intro/) Upload and execute Lua scripts on the server. Scripts can employ programmatic control structures and use most of the commands while executing to access the database. Because scripts are executed on the server, reading and writing data from scripts is very efficient.
|
||||
|
||||
## Cloud hosted Redis
|
||||
|
||||
Fully-managed Redis with real-time performance at scale.
|
||||
|
||||
[**Redis Cloud**](https://redis.io/cloud/)
|
||||
|
||||
## Community
|
||||
|
||||
[**Redis Community Resources**](https://redis.io/community/)
|
||||
@@ -265,7 +273,7 @@ Tested with the following Docker image:
|
||||
|
||||
```sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
@@ -331,7 +339,7 @@ Tested with the following Docker image:
|
||||
|
||||
```sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
@@ -385,7 +393,7 @@ Tested with the following Docker image:
|
||||
|
||||
```sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
@@ -442,7 +450,7 @@ Tested with the following Docker images:
|
||||
|
||||
```sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
@@ -561,7 +569,7 @@ Tested with the following Docker images:
|
||||
```sh
|
||||
source /etc/profile.d/gcc-toolset-13.sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
@@ -678,7 +686,7 @@ Tested with the following Docker images:
|
||||
```sh
|
||||
source /etc/profile.d/gcc-toolset-13.sh
|
||||
cd /usr/src/redis-<version>
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes DISABLE_WERRORS=yes
|
||||
export BUILD_TLS=yes BUILD_WITH_MODULES=yes INSTALL_RUST_TOOLCHAIN=yes
|
||||
make -j "$(nproc)" all
|
||||
```
|
||||
|
||||
@@ -750,7 +758,6 @@ Tested with the following Docker images:
|
||||
export HOMEBREW_PREFIX="$(brew --prefix)"
|
||||
export BUILD_WITH_MODULES=yes
|
||||
export BUILD_TLS=yes
|
||||
export DISABLE_WERRORS=yes
|
||||
PATH="$HOMEBREW_PREFIX/opt/libtool/libexec/gnubin:$HOMEBREW_PREFIX/opt/llvm@18/bin:$HOMEBREW_PREFIX/opt/make/libexec/gnubin:$HOMEBREW_PREFIX/opt/gnu-sed/libexec/gnubin:$HOMEBREW_PREFIX/opt/coreutils/libexec/gnubin:$PATH"
|
||||
export LDFLAGS="-L$HOMEBREW_PREFIX/opt/llvm@18/lib"
|
||||
export CPPFLAGS="-I$HOMEBREW_PREFIX/opt/llvm@18/include"
|
||||
@@ -784,6 +791,8 @@ To build Redis with all the data structures (including JSON, time series, Bloom
|
||||
make BUILD_WITH_MODULES=yes
|
||||
```
|
||||
|
||||
Note: `BUILD_WITH_MODULES=yes` is not supported on 32 bit systems.
|
||||
|
||||
To build Redis with just the core data structures, use:
|
||||
|
||||
```sh
|
||||
@@ -873,7 +882,9 @@ make MALLOC=jemalloc
|
||||
|
||||
By default, Redis will build using the POSIX clock_gettime function as the monotonic clock source. On most modern systems, the internal processor clock can be used to improve performance. Cautions can be found here: http://oliveryang.net/2015/09/pitfalls-of-TSC-usage/
|
||||
|
||||
To build with support for the processor's internal instruction clock, use:
|
||||
On ARM aarch64 systems, the hardware clock is enabled by default because the ARM Generic Timer is architecturally guaranteed to be available and monotonic on all ARMv8-A processors (see the *“The Generic Timer in AArch64 state”* section of the *Arm Architecture Reference Manual for Armv8-A*).
|
||||
|
||||
To build with support for the processor's internal instruction clock on other architectures, use:
|
||||
|
||||
```sh
|
||||
make CFLAGS="-DUSE_PROCESSOR_CLOCK"
|
||||
|
||||
+28
-6
@@ -11,20 +11,32 @@ unless this is not possible or feasible with a reasonable effort.
|
||||
|
||||
| Version | Supported |
|
||||
|---------|------------------------------------------------------------------------|
|
||||
| 8.6.x | :white_check_mark: |
|
||||
| 8.4.x | :white_check_mark: |
|
||||
| 8.2.x | :white_check_mark: |
|
||||
| 8.0.x | :white_check_mark: |
|
||||
| 8.0.x | :x: |
|
||||
| 7.4.x | :white_check_mark: |
|
||||
| 7.2.x | :white_check_mark: support extended till 7.4 end of support |
|
||||
| < 7.2.x | :x: |
|
||||
| 6.2.x | :white_check_mark: support extended - may be removed after end of 2025 |
|
||||
| 6.2.x | :white_check_mark: support extended |
|
||||
| < 6.2.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe you've discovered a serious vulnerability, please contact the
|
||||
Redis core team at redis@redis.io. We will evaluate your report and if
|
||||
necessary issue a fix and an advisory. If the issue was previously undisclosed,
|
||||
we'll also mention your name in the credits.
|
||||
If you believe you have found a security vulnerability, to ensure proper review
|
||||
and assessment, we kindly ask vulnerability reports be submitted through
|
||||
our [Redis Vulnerability Disclosure Program.](https://redis.io/redis-responsible-vulnerability-disclosure/)
|
||||
|
||||
We have found this path to be beneficial for both researchers and us for
|
||||
a number of reasons. Including, offering fast response times to researchers and
|
||||
opportunities for us to invite those with exceptional reports into closed paid
|
||||
engagements.
|
||||
|
||||
For those averse to using our chosen platform, we will also accept reports directly
|
||||
via GitHub's "Report a Vulnerability".
|
||||
|
||||
To contact the security team directly with questions use: [security@redis.com](mailto:security@redis.com)
|
||||
|
||||
|
||||
## Responsible Disclosure
|
||||
|
||||
@@ -40,6 +52,16 @@ embargo on public disclosure.
|
||||
If you believe you should be on the list, please contact us and we will
|
||||
consider your request based on the above criteria.
|
||||
|
||||
## Support across Operating Systems, Architectures, and Compilers
|
||||
|
||||
Redis is primarily tested on modern Linux distributions, using contemporary
|
||||
Intel and AMD x86_64 CPUs, as well as ARM-based CPUs, and recent versions of
|
||||
the GCC compiler.
|
||||
Vulnerability reports that rely on unsupported or uncommon environments
|
||||
(for example, 32-bit architectures, non-Linux operating systems, or outdated
|
||||
toolchains) may be considered out of scope, even if the issue is technically
|
||||
valid. Such reports will be evaluated on a case-by-case basis at our discretion.
|
||||
|
||||
## License Compatibility
|
||||
|
||||
For security vulnerability patches released under Redis Open Source 7.4 and
|
||||
|
||||
Vendored
+7
-6
@@ -59,7 +59,7 @@ distclean:
|
||||
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
|
||||
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
|
||||
-(cd fpconv && $(MAKE) clean) > /dev/null || true
|
||||
-(cd fast_float && $(MAKE) clean) > /dev/null || true
|
||||
-(cd tre && $(MAKE) clean) > /dev/null || true
|
||||
-(cd xxhash && $(MAKE) clean) > /dev/null || true
|
||||
-(rm -f .make-*)
|
||||
|
||||
@@ -95,11 +95,12 @@ fpconv: .make-prerequisites
|
||||
|
||||
.PHONY: fpconv
|
||||
|
||||
fast_float: .make-prerequisites
|
||||
tre: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
cd fast_float && $(MAKE) libfast_float CFLAGS="$(DEPS_CFLAGS)" LDFLAGS="$(DEPS_LDFLAGS)"
|
||||
cd tre && $(MAKE) CFLAGS="$(DEPS_CFLAGS)" LDFLAGS="$(DEPS_LDFLAGS)"
|
||||
|
||||
.PHONY: tre
|
||||
|
||||
.PHONY: fast_float
|
||||
|
||||
XXHASH_CFLAGS = -fPIC $(DEPS_CFLAGS)
|
||||
xxhash: .make-prerequisites
|
||||
@@ -136,8 +137,8 @@ lua: .make-prerequisites
|
||||
|
||||
.PHONY: lua
|
||||
|
||||
JEMALLOC_CFLAGS=$(CFLAGS)
|
||||
JEMALLOC_LDFLAGS=$(LDFLAGS)
|
||||
JEMALLOC_CFLAGS=$(ENABLE_LTO) $(CFLAGS)
|
||||
JEMALLOC_LDFLAGS=$(ENABLE_LTO) $(LDFLAGS)
|
||||
|
||||
ifneq ($(DEB_HOST_GNU_TYPE),)
|
||||
JEMALLOC_CONFIGURE_OPTS += --host=$(DEB_HOST_GNU_TYPE)
|
||||
|
||||
Vendored
-27
@@ -1,27 +0,0 @@
|
||||
# Fallback to gcc/g++ when $CC or $CXX is not in $PATH.
|
||||
CC ?= gcc
|
||||
CXX ?= g++
|
||||
|
||||
WARN=-Wall
|
||||
OPT=-O3
|
||||
STD=-std=c++11
|
||||
DEFS=-DFASTFLOAT_ALLOWS_LEADING_PLUS
|
||||
|
||||
FASTFLOAT_CFLAGS=$(WARN) $(OPT) $(STD) $(DEFS) $(CFLAGS)
|
||||
FASTFLOAT_LDFLAGS=$(LDFLAGS)
|
||||
|
||||
libfast_float: fast_float_strtod.o
|
||||
$(AR) -r libfast_float.a fast_float_strtod.o
|
||||
|
||||
32bit: FASTFLOAT_CFLAGS += -m32
|
||||
32bit: FASTFLOAT_LDFLAGS += -m32
|
||||
32bit: libfast_float
|
||||
|
||||
fast_float_strtod.o: fast_float_strtod.cpp
|
||||
$(CXX) $(FASTFLOAT_CFLAGS) -c fast_float_strtod.cpp $(FASTFLOAT_LDFLAGS)
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f *.a
|
||||
rm -f *.h.gch
|
||||
rm -rf *.dSYM
|
||||
Vendored
-21
@@ -1,21 +0,0 @@
|
||||
README for fast_float v6.1.4
|
||||
|
||||
----------------------------------------------
|
||||
|
||||
We're using the fast_float library[1] in our (compiled-in)
|
||||
floating-point fast_float_strtod implementation for faster and more
|
||||
portable parsing of 64 decimal strings.
|
||||
|
||||
The single file fast_float.h is an amalgamation of the entire library,
|
||||
which can be (re)generated with the amalgamate.py script (from the
|
||||
fast_float repository) via the command
|
||||
|
||||
```
|
||||
git clone https://github.com/fastfloat/fast_float
|
||||
cd fast_float
|
||||
git checkout v6.1.4
|
||||
python3 ./script/amalgamate.py --license=MIT \
|
||||
> $REDIS_SRC/deps/fast_float/fast_float.h
|
||||
```
|
||||
|
||||
[1]: https://github.com/fastfloat/fast_float
|
||||
Vendored
-3838
File diff suppressed because it is too large
Load Diff
-32
@@ -1,32 +0,0 @@
|
||||
#include "fast_float.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <cerrno>
|
||||
|
||||
/* Convert NPTR to a double using the fast_float library.
|
||||
*
|
||||
* This function behaves similarly to the standard strtod function, converting
|
||||
* the initial portion of the string pointed to by `nptr` to a `double` value,
|
||||
* using the fast_float library for high performance. If the conversion fails,
|
||||
* errno is set to EINVAL error code.
|
||||
*
|
||||
* @param nptr A pointer to the null-terminated byte string to be interpreted.
|
||||
* @param endptr A pointer to a pointer to character. If `endptr` is not NULL,
|
||||
* it will point to the character after the last character used
|
||||
* in the conversion.
|
||||
* @return The converted value as a double. If no valid conversion could
|
||||
* be performed, returns 0.0.
|
||||
* If ENDPTR is not NULL, a pointer to the character after the last one used
|
||||
* in the number is put in *ENDPTR. */
|
||||
extern "C" double fast_float_strtod(const char *nptr, char **endptr) {
|
||||
double result = 0.0;
|
||||
auto answer = fast_float::from_chars(nptr, nptr + strlen(nptr), result);
|
||||
if (answer.ec != std::errc()) {
|
||||
errno = EINVAL; // Fallback to for other errors
|
||||
}
|
||||
if (endptr != NULL) {
|
||||
*endptr = (char *)answer.ptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Vendored
-15
@@ -1,15 +0,0 @@
|
||||
|
||||
#ifndef __FAST_FLOAT_STRTOD_H__
|
||||
#define __FAST_FLOAT_STRTOD_H__
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
double fast_float_strtod(const char *in, char **out);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __FAST_FLOAT_STRTOD_H__ */
|
||||
Vendored
+81
-8
@@ -120,6 +120,7 @@
|
||||
#include <assert.h>
|
||||
#include "linenoise.h"
|
||||
|
||||
#define SEQ_BUFFER_MAX_LENGTH 8
|
||||
#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
|
||||
#define LINENOISE_MAX_LINE 4096
|
||||
static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
|
||||
@@ -793,6 +794,30 @@ void linenoiseEditMoveRight(struct linenoiseState *l) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Consider letters/digits/underscore as “word”; others as delimiters. */
|
||||
static int isWordChar(char c) {
|
||||
return (c == '_' || (c >= '0' && c <= '9') ||
|
||||
(c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'));
|
||||
}
|
||||
|
||||
static void linenoiseEditMoveWordLeft(struct linenoiseState *l) {
|
||||
if (l->pos == 0) return;
|
||||
/* Skip any delimiters, then move left over the previous word */
|
||||
while (l->pos > 0 && !isWordChar(l->buf[l->pos - 1])) l->pos--;
|
||||
/* Then move to the start of that word */
|
||||
while (l->pos > 0 && isWordChar(l->buf[l->pos - 1])) l->pos--;
|
||||
refreshLine(l);
|
||||
}
|
||||
|
||||
static void linenoiseEditMoveWordRight(struct linenoiseState *l) {
|
||||
if (l->pos == l->len) return;
|
||||
/* Skip the current word to the right */
|
||||
while (l->pos < l->len && isWordChar(l->buf[l->pos])) l->pos++;
|
||||
/* Then skip any delimiters to reach the next word */
|
||||
while (l->pos < l->len && !isWordChar(l->buf[l->pos])) l->pos++;
|
||||
refreshLine(l);
|
||||
}
|
||||
|
||||
/* Move cursor to the start of the line. */
|
||||
void linenoiseEditMoveHome(struct linenoiseState *l) {
|
||||
if (l->pos != 0) {
|
||||
@@ -1012,6 +1037,18 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
* Use two calls to handle slow terminals returning the two
|
||||
* chars at different times. */
|
||||
if (read(l.ifd,seq,1) == -1) break;
|
||||
|
||||
/* Handle Meta-b / Meta-f directly because it's a 2-byte sequence */
|
||||
if (seq[0] == 'b' || seq[0] == 'f') {
|
||||
if (reverse_search_mode_enabled) {
|
||||
disableReverseSearchMode(&l, buf, buflen, 1);
|
||||
break;
|
||||
}
|
||||
if (seq[0] == 'b') linenoiseEditMoveWordLeft(&l); /* ESC b → word left */
|
||||
else linenoiseEditMoveWordRight(&l); /* ESC f → word right */
|
||||
break;
|
||||
}
|
||||
|
||||
if (read(l.ifd,seq+1,1) == -1) break;
|
||||
|
||||
if (reverse_search_mode_enabled) {
|
||||
@@ -1022,14 +1059,50 @@ static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen,
|
||||
/* ESC [ sequences. */
|
||||
if (seq[0] == '[') {
|
||||
if (seq[1] >= '0' && seq[1] <= '9') {
|
||||
/* Extended escape, read additional byte. */
|
||||
if (read(l.ifd,seq+2,1) == -1) break;
|
||||
if (seq[2] == '~') {
|
||||
switch(seq[1]) {
|
||||
case '3': /* Delete key. */
|
||||
linenoiseEditDelete(&l);
|
||||
break;
|
||||
}
|
||||
/* Extended escape, read additional bytes.
|
||||
* Examples: ESC [1;5C ESC [3~ */
|
||||
char seq_buffer[SEQ_BUFFER_MAX_LENGTH];
|
||||
int i = 0;
|
||||
seq_buffer[i++] = seq[1];
|
||||
|
||||
/* Read more bytes until we see a CSI final byte (range @..~).
|
||||
* Use SEQ_BUFFER_MAX_LENGTH-1 to reserve one position for '\0'. */
|
||||
char seq_char;
|
||||
while (i < SEQ_BUFFER_MAX_LENGTH - 1 && read(l.ifd, &seq_char, 1) == 1) {
|
||||
seq_buffer[i++] = seq_char;
|
||||
if (seq_char >= '@' && seq_char <= '~') break; /* CSI final byte */
|
||||
}
|
||||
seq_buffer[i] = '\0';
|
||||
|
||||
/* The exact key mapping behavior depends on your keyboard/terminal setup.
|
||||
* For example, in MacOS terminal you can go to the profile keyboard setting
|
||||
* to see or configure the current mapping.
|
||||
*
|
||||
* Take action `[1;5C` (Ctrl + →) or `[1;3D` (Alt + ←) as examples:
|
||||
* [ indicates a CSI (Control Sequence Introducer), telling the terminal
|
||||
* "What follows is a control command, not text"
|
||||
* 1 is how many units to move (default is 1 if omitted)
|
||||
* ; is the separator between parameters
|
||||
* 5 is the modifier mask for Ctrl. Other possible values include 1 (no modifier),
|
||||
* 2 (Shift), 3 (Alt), 4 (Shift + Alt), 6 (Shift + Ctrl), 7 (Alt + Ctrl), etc.
|
||||
* C is the cursor right command. Other commands include A (cursor up), B (cursor
|
||||
* down), D (cursor left).
|
||||
*/
|
||||
|
||||
/* Word left: Ctrl + ← (modifier 5) or Alt + ← (modifier 3) */
|
||||
if (strcmp(seq_buffer, "1;5D") == 0 || strcmp(seq_buffer, "1;3D") == 0) {
|
||||
linenoiseEditMoveWordLeft(&l);
|
||||
break;
|
||||
}
|
||||
/* Word right: Ctrl + → (modifier 5) or Alt + → (modifier 3) */
|
||||
if (strcmp(seq_buffer, "1;5C") == 0 || strcmp(seq_buffer, "1;3C") == 0) {
|
||||
linenoiseEditMoveWordRight(&l);
|
||||
break;
|
||||
}
|
||||
/* Delete key */
|
||||
if (strcmp(seq_buffer, "3~") == 0) {
|
||||
linenoiseEditDelete(&l);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch(seq[1]) {
|
||||
|
||||
Vendored
+4
-3
@@ -340,13 +340,14 @@ static int luaB_assert (lua_State *L) {
|
||||
|
||||
|
||||
static int luaB_unpack (lua_State *L) {
|
||||
int i, e, n;
|
||||
int i, e;
|
||||
unsigned int n;
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
i = luaL_optint(L, 2, 1);
|
||||
e = luaL_opt(L, luaL_checkint, 3, luaL_getn(L, 1));
|
||||
if (i > e) return 0; /* empty range */
|
||||
n = e - i + 1; /* number of elements */
|
||||
if (n <= 0 || !lua_checkstack(L, n)) /* n <= 0 means arith. overflow */
|
||||
n = (unsigned int)e - (unsigned int)i; /* number of elements minus 1 */
|
||||
if (n >= INT_MAX || !lua_checkstack(L, ++n))
|
||||
return luaL_error(L, "too many results to unpack");
|
||||
lua_rawgeti(L, 1, i); /* push arg[i] (avoiding overflow problems) */
|
||||
while (i++ < e) /* push arg[i + 1...e] */
|
||||
|
||||
Vendored
+21
-13
@@ -138,6 +138,7 @@ static void inclinenumber (LexState *ls) {
|
||||
|
||||
|
||||
void luaX_setinput (lua_State *L, LexState *ls, ZIO *z, TString *source) {
|
||||
ls->t.token = 0;
|
||||
ls->decpoint = '.';
|
||||
ls->L = L;
|
||||
ls->lookahead.token = TK_EOS; /* no look-ahead token */
|
||||
@@ -206,9 +207,13 @@ static void read_numeral (LexState *ls, SemInfo *seminfo) {
|
||||
trydecpoint(ls, seminfo); /* try to update decimal point separator */
|
||||
}
|
||||
|
||||
|
||||
static int skip_sep (LexState *ls) {
|
||||
int count = 0;
|
||||
/*
|
||||
** reads a sequence '[=*[' or ']=*]', leaving the last bracket.
|
||||
** If a sequence is well-formed, return its number of '='s + 2; otherwise,
|
||||
** return 1 if there is no '='s or 0 otherwise (an unfinished '[==...').
|
||||
*/
|
||||
static size_t skip_sep (LexState *ls) {
|
||||
size_t count = 0;
|
||||
int s = ls->current;
|
||||
lua_assert(s == '[' || s == ']');
|
||||
save_and_next(ls);
|
||||
@@ -216,11 +221,13 @@ static int skip_sep (LexState *ls) {
|
||||
save_and_next(ls);
|
||||
count++;
|
||||
}
|
||||
return (ls->current == s) ? count : (-count) - 1;
|
||||
return (ls->current == s) ? count + 2
|
||||
: (count == 0) ? 1
|
||||
: 0;
|
||||
}
|
||||
|
||||
|
||||
static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) {
|
||||
static void read_long_string (LexState *ls, SemInfo *seminfo, size_t sep) {
|
||||
int cont = 0;
|
||||
(void)(cont); /* avoid warnings when `cont' is not used */
|
||||
save_and_next(ls); /* skip 2nd `[' */
|
||||
@@ -270,8 +277,8 @@ static void read_long_string (LexState *ls, SemInfo *seminfo, int sep) {
|
||||
}
|
||||
} endloop:
|
||||
if (seminfo)
|
||||
seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + (2 + sep),
|
||||
luaZ_bufflen(ls->buff) - 2*(2 + sep));
|
||||
seminfo->ts = luaX_newstring(ls, luaZ_buffer(ls->buff) + sep,
|
||||
luaZ_bufflen(ls->buff) - 2 * sep);
|
||||
}
|
||||
|
||||
|
||||
@@ -346,9 +353,9 @@ static int llex (LexState *ls, SemInfo *seminfo) {
|
||||
/* else is a comment */
|
||||
next(ls);
|
||||
if (ls->current == '[') {
|
||||
int sep = skip_sep(ls);
|
||||
size_t sep = skip_sep(ls);
|
||||
luaZ_resetbuffer(ls->buff); /* `skip_sep' may dirty the buffer */
|
||||
if (sep >= 0) {
|
||||
if (sep >= 2) {
|
||||
read_long_string(ls, NULL, sep); /* long comment */
|
||||
luaZ_resetbuffer(ls->buff);
|
||||
continue;
|
||||
@@ -360,13 +367,14 @@ static int llex (LexState *ls, SemInfo *seminfo) {
|
||||
continue;
|
||||
}
|
||||
case '[': {
|
||||
int sep = skip_sep(ls);
|
||||
if (sep >= 0) {
|
||||
size_t sep = skip_sep(ls);
|
||||
if (sep >= 2) {
|
||||
read_long_string(ls, seminfo, sep);
|
||||
return TK_STRING;
|
||||
}
|
||||
else if (sep == -1) return '[';
|
||||
else luaX_lexerror(ls, "invalid long string delimiter", TK_STRING);
|
||||
else if (sep == 0) /* '[=...' missing second bracket */
|
||||
luaX_lexerror(ls, "invalid long string delimiter", TK_STRING);
|
||||
return '[';
|
||||
}
|
||||
case '=': {
|
||||
next(ls);
|
||||
|
||||
Vendored
+5
-1
@@ -384,13 +384,17 @@ Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name) {
|
||||
struct LexState lexstate;
|
||||
struct FuncState funcstate;
|
||||
lexstate.buff = buff;
|
||||
luaX_setinput(L, &lexstate, z, luaS_new(L, name));
|
||||
TString *tname = luaS_new(L, name);
|
||||
setsvalue2s(L, L->top, tname);
|
||||
incr_top(L);
|
||||
luaX_setinput(L, &lexstate, z, tname);
|
||||
open_func(&lexstate, &funcstate);
|
||||
funcstate.f->is_vararg = VARARG_ISVARARG; /* main func. is always vararg */
|
||||
luaX_next(&lexstate); /* read first token */
|
||||
chunk(&lexstate);
|
||||
check(&lexstate, TK_EOS);
|
||||
close_func(&lexstate);
|
||||
--L->top;
|
||||
lua_assert(funcstate.prev == NULL);
|
||||
lua_assert(funcstate.f->nups == 0);
|
||||
lua_assert(lexstate.fs == NULL);
|
||||
|
||||
Vendored
+1
-2
@@ -434,8 +434,7 @@ static TValue *newkey (lua_State *L, Table *t, const TValue *key) {
|
||||
** search function for integers
|
||||
*/
|
||||
const TValue *luaH_getnum (Table *t, int key) {
|
||||
/* (1 <= key && key <= t->sizearray) */
|
||||
if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray))
|
||||
if (1 <= key && key <= t->sizearray)
|
||||
return &t->array[key-1];
|
||||
else {
|
||||
lua_Number nk = cast_num(key);
|
||||
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
This is the license, copyright notice, and disclaimer for TRE, a regex
|
||||
matching package (library and tools) with support for approximate
|
||||
matching.
|
||||
|
||||
Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN 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.
|
||||
Vendored
+79
@@ -0,0 +1,79 @@
|
||||
STD= -std=c99
|
||||
WARN= -Wall
|
||||
OPT= -Os
|
||||
|
||||
ifeq ($(SANITIZER),address)
|
||||
CFLAGS+=-fsanitize=address -fno-sanitize-recover=all -fno-omit-frame-pointer
|
||||
LDFLAGS+=-fsanitize=address
|
||||
else
|
||||
ifeq ($(SANITIZER),undefined)
|
||||
CFLAGS+=-fsanitize=undefined -fno-sanitize-recover=all -fno-omit-frame-pointer
|
||||
LDFLAGS+=-fsanitize=undefined
|
||||
else
|
||||
ifeq ($(SANITIZER),thread)
|
||||
CFLAGS+=-fsanitize=thread -fno-sanitize-recover=all -fno-omit-frame-pointer
|
||||
LDFLAGS+=-fsanitize=thread
|
||||
else
|
||||
ifeq ($(SANITIZER),memory)
|
||||
CFLAGS+=-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-sanitize-recover=all -fno-omit-frame-pointer
|
||||
LDFLAGS+=-fsanitize=memory
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) -DTRE_REGEX_T_FIELD=value -Ilocal_includes -Ilib
|
||||
R_LDFLAGS= $(LDFLAGS)
|
||||
DEBUG= -g
|
||||
|
||||
R_CC=$(CC) $(R_CFLAGS)
|
||||
R_LD=$(CC) $(R_LDFLAGS)
|
||||
|
||||
AR= ar
|
||||
ARFLAGS= rcs
|
||||
|
||||
TRE_OBJ=lib/regcomp.o lib/regerror.o lib/regexec.o lib/tre-ast.o lib/tre-compile.o \
|
||||
lib/tre-filter.o lib/tre-match-backtrack.o lib/tre-match-parallel.o \
|
||||
lib/tre-mem.o lib/tre-parse.o lib/tre-stack.o lib/xmalloc.o
|
||||
TRE_TESTS=tests/retest tests/test-str-source tests/test-literal-opt tests/test-malformed-regn
|
||||
|
||||
libtre.a: $(TRE_OBJ)
|
||||
$(AR) $(ARFLAGS) $@ $+
|
||||
|
||||
check: $(TRE_TESTS)
|
||||
@set -e; \
|
||||
for test in $(TRE_TESTS); do \
|
||||
echo "TEST $$test"; \
|
||||
./$$test; \
|
||||
done
|
||||
|
||||
tests/retest: tests/retest.c libtre.a
|
||||
$(R_LD) $(R_CFLAGS) -DHAVE_REGNEXEC -DHAVE_REGNCOMP -o $@ $< libtre.a
|
||||
|
||||
tests/test-str-source: tests/test-str-source.c libtre.a
|
||||
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
|
||||
|
||||
tests/test-literal-opt: tests/test-literal-opt.c libtre.a
|
||||
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
|
||||
|
||||
tests/test-malformed-regn: tests/test-malformed-regn.c libtre.a
|
||||
$(R_LD) $(R_CFLAGS) -o $@ $< libtre.a
|
||||
|
||||
lib/regcomp.o: lib/regcomp.c local_includes/tre.h local_includes/tre-config.h lib/tre-internal.h lib/xmalloc.h
|
||||
lib/regerror.o: lib/regerror.c local_includes/tre.h
|
||||
lib/regexec.o: lib/regexec.c local_includes/tre.h lib/tre-internal.h lib/xmalloc.h
|
||||
lib/tre-ast.o: lib/tre-ast.c lib/tre-ast.h lib/tre-internal.h
|
||||
lib/tre-compile.o: lib/tre-compile.c lib/tre-compile.h lib/tre-internal.h lib/tre-mem.h lib/tre-parse.h lib/tre-stack.h lib/xmalloc.h
|
||||
lib/tre-filter.o: lib/tre-filter.c lib/tre-filter.h lib/tre-internal.h
|
||||
lib/tre-match-backtrack.o: lib/tre-match-backtrack.c lib/tre-internal.h lib/tre-match-utils.h lib/tre-mem.h lib/tre-stack.h
|
||||
lib/tre-match-parallel.o: lib/tre-match-parallel.c lib/tre-internal.h lib/tre-match-utils.h lib/tre-mem.h
|
||||
lib/tre-mem.o: lib/tre-mem.c lib/tre-mem.h
|
||||
lib/tre-parse.o: lib/tre-parse.c lib/tre-ast.h lib/tre-compile.h lib/tre-filter.h lib/tre-internal.h lib/tre-mem.h lib/tre-parse.h lib/tre-stack.h lib/xmalloc.h
|
||||
lib/tre-stack.o: lib/tre-stack.c lib/tre-internal.h lib/tre-stack.h
|
||||
lib/xmalloc.o: lib/xmalloc.c lib/xmalloc.h
|
||||
|
||||
.c.o:
|
||||
$(R_CC) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f $(TRE_OBJ) libtre.a $(TRE_TESTS)
|
||||
Vendored
+276
@@ -0,0 +1,276 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
TRE is a lightweight, robust, and efficient POSIX compliant regexp
|
||||
matching library with some exciting features such as approximate
|
||||
(fuzzy) matching.
|
||||
|
||||
The matching algorithm used in TRE uses linear worst-case time in
|
||||
the length of the text being searched, and quadratic worst-case
|
||||
time in the length of the used regular expression.
|
||||
|
||||
In other words, the time complexity of the algorithm is O(M^2N), where
|
||||
M is the length of the regular expression and N is the length of the
|
||||
text. The used space is also quadratic on the length of the regex,
|
||||
but does not depend on the searched string. This quadratic behaviour
|
||||
occurs only on pathological cases which are probably very rare in
|
||||
practice.
|
||||
|
||||
|
||||
Hacking
|
||||
=======
|
||||
|
||||
Here's how to work with this code.
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
You will need the following tools installed on your system:
|
||||
|
||||
- autoconf
|
||||
- automake
|
||||
- gettext (including autopoint)
|
||||
- libtool
|
||||
- zip (optional)
|
||||
|
||||
|
||||
Building
|
||||
--------
|
||||
|
||||
First, prepare the tree. Change to the root of the source directory
|
||||
and run
|
||||
|
||||
./utils/autogen.sh
|
||||
|
||||
This will regenerate various things using the prerequisite tools so
|
||||
that you end up with a buildable tree.
|
||||
|
||||
After this, you can run the configure script and build TRE as usual:
|
||||
|
||||
./configure
|
||||
make
|
||||
make check
|
||||
make install
|
||||
|
||||
|
||||
Building a source code package
|
||||
------------------------------
|
||||
|
||||
In a prepared tree, this command creates a source code tarball:
|
||||
|
||||
./configure && make dist
|
||||
|
||||
Alternatively, you can run
|
||||
|
||||
./utils/build-sources.sh
|
||||
|
||||
which builds the source code packages and puts them in the `dist`
|
||||
subdirectory. This script needs a working `zip` command.
|
||||
|
||||
|
||||
Features
|
||||
========
|
||||
|
||||
TRE is not just yet another regexp matcher. TRE has some features
|
||||
which are not there in most free POSIX compatible implementations.
|
||||
Most of these features are not present in non-free implementations
|
||||
either, for that matter.
|
||||
|
||||
Approximate matching
|
||||
--------------------
|
||||
|
||||
Approximate pattern matching allows matches to be approximate, that
|
||||
is, allows the matches to be close to the searched pattern under some
|
||||
measure of closeness. TRE uses the edit-distance measure (also known
|
||||
as the Levenshtein distance) where characters can be inserted,
|
||||
deleted, or substituted in the searched text in order to get an exact
|
||||
match.
|
||||
|
||||
Each insertion, deletion, or substitution adds the distance, or cost,
|
||||
of the match. TRE can report the matches which have a cost lower than
|
||||
some given threshold value. TRE can also be used to search for
|
||||
matches with the lowest cost.
|
||||
|
||||
TRE includes a version of the agrep (approximate grep) command line
|
||||
tool for approximate regexp matching in the style of grep. Unlike
|
||||
other agrep implementations (like the one by Sun Wu and Udi Manber
|
||||
from University of Arizona) TRE agrep allows full regexps of any
|
||||
length, any number of errors, and non-uniform costs for insertion,
|
||||
deletion and substitution.
|
||||
|
||||
Strict standard conformance
|
||||
---------------------------
|
||||
|
||||
POSIX defines the behaviour of regexp functions precisely. TRE
|
||||
attempts to conform to these specifications as strictly as possible.
|
||||
TRE always returns the correct matches for subpatterns, for example.
|
||||
Very few other implementations do this correctly. In fact, the only
|
||||
other implementations besides TRE that I am aware of (free or not)
|
||||
that get it right are Rx by Tom Lord, Regex++ by John Maddock, and the
|
||||
AT&T ast regex by Glenn Fowler and Doug McIlroy.
|
||||
|
||||
The standard TRE tries to conform to is the IEEE Std 1003.1-2001, or
|
||||
Open Group Base Specifications Issue 6, commonly referred to as
|
||||
“POSIX”. The relevant parts are the base specifications on regular
|
||||
expressions (and the rationale) and the description of the `regcomp()`
|
||||
API.
|
||||
|
||||
For an excellent survey on POSIX regexp matchers, see the testregex
|
||||
pages by Glenn Fowler of AT&T Labs Research.
|
||||
|
||||
Predictable matching speed
|
||||
--------------------------
|
||||
|
||||
Because of the matching algorithm used in TRE, the maximum time
|
||||
consumed by any `regexec()` call is always directly proportional to
|
||||
the length of the searched string. There is one exception: if back
|
||||
references are used, the matching may take time that grows
|
||||
exponentially with the length of the string. This is because matching
|
||||
back references is an NP complete problem, and almost certainly
|
||||
requires exponential time to match in the worst case.
|
||||
|
||||
Predictable and modest memory consumption
|
||||
-----------------------------------------
|
||||
|
||||
A `regexec()` call never allocates memory from the heap. TRE allocates
|
||||
all the memory it needs during a `regcomp()` call, and some temporary
|
||||
working space from the stack frame for the duration of the `regexec()`
|
||||
call. The amount of temporary space needed is constant during
|
||||
matching and does not depend on the searched string. For regexps of
|
||||
reasonable size TRE needs less than 50K of dynamically allocated
|
||||
memory during the `regcomp()` call, less than 20K for the compiled
|
||||
pattern buffer, and less than two kilobytes of temporary working space
|
||||
from the stack frame during a `regexec()` call. There is no time /
|
||||
memory tradeoff. TRE is also small in code size; statically linking
|
||||
with TRE increases the executable size less than 30K (gcc-3.2, x86,
|
||||
GNU/Linux).
|
||||
|
||||
Wide character and multibyte character set support
|
||||
--------------------------------------------------
|
||||
|
||||
TRE supports multibyte character sets. This makes it possible to use
|
||||
regexps seamlessly with, for example, Japanese locales. TRE also
|
||||
provides a wide character API.
|
||||
|
||||
Binary pattern and data support
|
||||
-------------------------------
|
||||
|
||||
TRE provides APIs which allow binary zero characters both in regexps
|
||||
and searched strings. The standard API cannot be easily used to, for
|
||||
example, search for printable words from binary data (although it is
|
||||
possible with some hacking). Searching for patterns which contain
|
||||
binary zeroes embedded is not possible at all with the standard API.
|
||||
|
||||
Completely thread safe
|
||||
----------------------
|
||||
|
||||
TRE is completely thread safe. All the exported functions are
|
||||
re-entrant, and a single compiled regexp object can be used
|
||||
simultaneously in multiple contexts; e.g. in `main()` and a signal
|
||||
handler, or in many threads of a multithreaded application.
|
||||
|
||||
Portable
|
||||
--------
|
||||
|
||||
TRE is portable across multiple platforms. Below is a table of
|
||||
platforms and compilers used to develop and test TRE:
|
||||
|
||||
<table>
|
||||
<tr><th>Platform</th> <th>Compiler</th></tr>
|
||||
<tr><td>FreeBSD 14.1</td> <td>Clang 18</td></tr>
|
||||
<tr><td>Ubuntu 22.04</td> <td>GCC 11</td></tr>
|
||||
<tr><td>macOS 14.6</td> <td>Clang 14</td></tr>
|
||||
<tr><td>Windows 11</td> <td>Microsoft Visual Studio 2022</td></tr>
|
||||
</table>
|
||||
|
||||
TRE should compile without changes on most modern POSIX-like
|
||||
platforms, and be easily portable to any platform with a hosted C
|
||||
implementation.
|
||||
|
||||
Depending on the platform, you may need to install libutf8 to get
|
||||
wide character and multibyte character set support.
|
||||
|
||||
Free
|
||||
----
|
||||
|
||||
TRE is released under a license which is essentially the same as the
|
||||
“2 clause” BSD-style license used in NetBSD. See the file LICENSE for
|
||||
details.
|
||||
|
||||
Roadmap
|
||||
-------
|
||||
|
||||
There are currently two features, both related to collating elements,
|
||||
missing from 100% POSIX compliance. These are:
|
||||
|
||||
* Support for collating elements (e.g. `[[.\<X>.]]`, where `\<X>` is a
|
||||
collating element). It is not possible to support multi-character
|
||||
collating elements portably, since POSIX does not define a way to
|
||||
determine whether a character sequence is a multi-character
|
||||
collating element or not.
|
||||
|
||||
* Support for equivalence classes, for example `[[=\<X>=]]`, where
|
||||
`\<X>` is a collating element. An equivalence class matches any
|
||||
character which has the same primary collation weight as `\<X>`.
|
||||
Again, POSIX provides no portable mechanism for determining the
|
||||
primary collation weight of a collating element.
|
||||
|
||||
Note that other portable regexp implementations don't support
|
||||
collating elements either. The single exception is Regex++, which
|
||||
comes with its own database for collating elements for different
|
||||
locales. Support for collating elements and equivalence classes has
|
||||
not been widely requested and is not very high on the TODO list at the
|
||||
moment.
|
||||
|
||||
These are other features I'm planning to implement real soon now:
|
||||
|
||||
* All the missing GNU extensions enabled in GNU regex, such as
|
||||
`[[:<:]]` and `[[:>:]]`.
|
||||
|
||||
* A `REG_SHORTEST` `regexec()` flag for returning the shortest match
|
||||
instead of the longest match.
|
||||
|
||||
* Perl-compatible syntax:
|
||||
* `[:^class:]`
|
||||
Matches anything but the characters in class. Note that
|
||||
`[^[:class:]]` works already, this would be just a convenience
|
||||
shorthand.
|
||||
|
||||
* `\A`
|
||||
Match only at beginning of string.
|
||||
|
||||
* `\Z`
|
||||
Match only at end of string, or before newline at the end.
|
||||
|
||||
* `\z`
|
||||
Match only at end of string.
|
||||
|
||||
* `\l`
|
||||
Lowercase next char (think vi).
|
||||
|
||||
* `\u`
|
||||
Uppercase next char (think vi).
|
||||
|
||||
* `\L`
|
||||
Lowercase till `\E` (think vi).
|
||||
|
||||
* `\U`
|
||||
Uppercase till `\E` (think vi).
|
||||
|
||||
* `(?=pattern)`
|
||||
Zero-width positive look-ahead assertions.
|
||||
|
||||
* `(?!pattern)`
|
||||
Zero-width negative look-ahead assertions.
|
||||
|
||||
* `(?<=pattern)`
|
||||
Zero-width positive look-behind assertions.
|
||||
|
||||
* `(?<!pattern)`
|
||||
Zero-width negative look-behind assertions.
|
||||
|
||||
Documentation especially for the nonstandard features of TRE, such as
|
||||
approximate matching, is a work in progress (with “progress” loosely
|
||||
defined...) If you want to find an extension to use, reading the
|
||||
`include/tre/tre.h` header might provide some additional hints if you
|
||||
are comfortable with C source code.
|
||||
Vendored
+188
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
tre_regcomp.c - TRE POSIX compatible regex compilation functions.
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
int
|
||||
tre_regncomp(regex_t *preg, const char *regex, size_t n, int cflags)
|
||||
{
|
||||
int ret;
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
#if TRE_WCHAR
|
||||
tre_char_t *wregex;
|
||||
size_t wlen;
|
||||
|
||||
wregex = xmalloc(sizeof(tre_char_t) * (n + 1));
|
||||
if (wregex == NULL)
|
||||
return REG_ESPACE;
|
||||
|
||||
/* If the current locale uses the standard single byte encoding of
|
||||
characters, we don't do a multibyte string conversion. If we did,
|
||||
many applications which use the default locale would break since
|
||||
the default "C" locale uses the 7-bit ASCII character set, and
|
||||
all characters with the eighth bit set would be considered invalid. */
|
||||
#if TRE_MULTIBYTE
|
||||
if (TRE_MB_CUR_MAX == 1)
|
||||
#endif /* TRE_MULTIBYTE */
|
||||
{
|
||||
size_t i;
|
||||
const unsigned char *str = (const unsigned char *)regex;
|
||||
tre_char_t *wstr = wregex;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
*(wstr++) = *(str++);
|
||||
wlen = n;
|
||||
}
|
||||
#if TRE_MULTIBYTE
|
||||
else
|
||||
{
|
||||
size_t consumed;
|
||||
tre_char_t *wcptr = wregex;
|
||||
#ifdef HAVE_MBSTATE_T
|
||||
mbstate_t state;
|
||||
memset(&state, '\0', sizeof(state));
|
||||
#endif /* HAVE_MBSTATE_T */
|
||||
while (n > 0)
|
||||
{
|
||||
consumed = tre_mbrtowc(wcptr, regex, n, &state);
|
||||
|
||||
switch (consumed)
|
||||
{
|
||||
case 0:
|
||||
if (*regex == '\0')
|
||||
consumed = 1;
|
||||
else
|
||||
{
|
||||
xfree(wregex);
|
||||
return REG_BADPAT;
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
DPRINT(("mbrtowc: error %d: %s.\n", errno, strerror(errno)));
|
||||
xfree(wregex);
|
||||
return REG_BADPAT;
|
||||
case -2:
|
||||
/* The last character wasn't complete. Let's not call it a
|
||||
fatal error. */
|
||||
consumed = n;
|
||||
break;
|
||||
}
|
||||
regex += consumed;
|
||||
n -= consumed;
|
||||
wcptr++;
|
||||
}
|
||||
wlen = wcptr - wregex;
|
||||
}
|
||||
#endif /* TRE_MULTIBYTE */
|
||||
|
||||
wregex[wlen] = L'\0';
|
||||
ret = tre_compile(preg, wregex, wlen, cflags);
|
||||
xfree(wregex);
|
||||
#else /* !TRE_WCHAR */
|
||||
ret = tre_compile(preg, (const tre_char_t *)regex, n, cflags);
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* this version takes bytes literally, to be used with raw vectors */
|
||||
int
|
||||
tre_regncompb(regex_t *preg, const char *regex, size_t n, int cflags)
|
||||
{
|
||||
int ret;
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
#if TRE_WCHAR /* wide chars = we need to convert it all to the wide format */
|
||||
tre_char_t *wregex;
|
||||
size_t i;
|
||||
|
||||
wregex = xmalloc(sizeof(tre_char_t) * n);
|
||||
if (wregex == NULL)
|
||||
return REG_ESPACE;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
wregex[i] = (tre_char_t) ((unsigned char) regex[i]);
|
||||
|
||||
ret = tre_compile(preg, wregex, n, cflags | REG_USEBYTES);
|
||||
xfree(wregex);
|
||||
#else /* !TRE_WCHAR */
|
||||
ret = tre_compile(preg, (const tre_char_t *)regex, n, cflags | REG_USEBYTES);
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
tre_regcomp(regex_t *preg, const char *regex, int cflags)
|
||||
{
|
||||
size_t n = regex ? strlen(regex) : 0;
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
return tre_regncomp(preg, regex, n, cflags);
|
||||
}
|
||||
|
||||
int
|
||||
tre_regcompb(regex_t *preg, const char *regex, int cflags)
|
||||
{
|
||||
int ret;
|
||||
tre_char_t *wregex;
|
||||
size_t i, n = regex ? strlen(regex) : 0;
|
||||
const unsigned char *str = (const unsigned char *)regex;
|
||||
tre_char_t *wstr;
|
||||
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
wregex = xmalloc(sizeof(tre_char_t) * (n + 1));
|
||||
if (wregex == NULL) return REG_ESPACE;
|
||||
wstr = wregex;
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
*(wstr++) = *(str++);
|
||||
wregex[n] = L'\0';
|
||||
ret = tre_compile(preg, wregex, n, cflags | REG_USEBYTES);
|
||||
xfree(wregex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
int
|
||||
tre_regwncomp(regex_t *preg, const wchar_t *regex, size_t n, int cflags)
|
||||
{
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
return tre_compile(preg, regex, n, cflags);
|
||||
}
|
||||
|
||||
int
|
||||
tre_regwcomp(regex_t *preg, const wchar_t *regex, int cflags)
|
||||
{
|
||||
size_t n = regex ? wcslen(regex) : 0;
|
||||
if (n > TRE_MAX_RE)
|
||||
return REG_ESPACE;
|
||||
return tre_compile(preg, regex, n, cflags);
|
||||
}
|
||||
#endif /* TRE_WCHAR */
|
||||
|
||||
void
|
||||
tre_regfree(regex_t *preg)
|
||||
{
|
||||
tre_free(preg);
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
tre_regerror.c - POSIX tre_regerror() implementation for TRE.
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <string.h>
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
#ifdef HAVE_WCTYPE_H
|
||||
#include <wctype.h>
|
||||
#endif /* HAVE_WCTYPE_H */
|
||||
|
||||
#include "tre-internal.h"
|
||||
|
||||
#ifdef HAVE_GETTEXT
|
||||
#include <libintl.h>
|
||||
#else
|
||||
#define dgettext(p, s) s
|
||||
#define gettext(s) s
|
||||
#endif
|
||||
|
||||
#define _(String) dgettext(PACKAGE, String)
|
||||
#define gettext_noop(String) String
|
||||
|
||||
#define xstr(s) str(s)
|
||||
#define str(s) #s
|
||||
|
||||
/* Error message strings for error codes listed in `tre.h'. This list
|
||||
needs to be in sync with the codes listed there, naturally. */
|
||||
static const char *tre_error_messages[] =
|
||||
{ gettext_noop("No error"), /* REG_OK */
|
||||
gettext_noop("No match"), /* REG_NOMATCH */
|
||||
gettext_noop("Invalid regexp"), /* REG_BADPAT */
|
||||
gettext_noop("Unknown collating element"), /* REG_ECOLLATE */
|
||||
gettext_noop("Unknown character class name"), /* REG_ECTYPE */
|
||||
gettext_noop("Trailing backslash"), /* REG_EESCAPE */
|
||||
gettext_noop("Invalid back reference"), /* REG_ESUBREG */
|
||||
gettext_noop("Missing ']'"), /* REG_EBRACK */
|
||||
gettext_noop("Missing ')'"), /* REG_EPAREN */
|
||||
gettext_noop("Missing '}'"), /* REG_EBRACE */
|
||||
gettext_noop("Invalid contents of {}"), /* REG_BADBR */
|
||||
gettext_noop("Invalid character range"), /* REG_ERANGE */
|
||||
gettext_noop("Out of memory"), /* REG_ESPACE */
|
||||
gettext_noop("Invalid use of repetition operators"), /* REG_BADRPT */
|
||||
gettext_noop("Maximum repetition in {} larger than " xstr(RE_DUP_MAX)), /* REG_BADMAX */
|
||||
};
|
||||
|
||||
size_t
|
||||
tre_regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size)
|
||||
{
|
||||
const char *err;
|
||||
size_t err_len;
|
||||
|
||||
/*LINTED*/(void)&preg;
|
||||
if (errcode >= 0
|
||||
&& errcode < (int)(sizeof(tre_error_messages)
|
||||
/ sizeof(*tre_error_messages)))
|
||||
err = gettext(tre_error_messages[errcode]);
|
||||
else
|
||||
err = gettext("Unknown error");
|
||||
|
||||
err_len = strlen(err) + 1;
|
||||
if (errbuf_size > 0 && errbuf != NULL)
|
||||
{
|
||||
if (err_len > errbuf_size)
|
||||
{
|
||||
strncpy(errbuf, err, errbuf_size - 1);
|
||||
errbuf[errbuf_size - 1] = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(errbuf, err);
|
||||
}
|
||||
}
|
||||
return err_len;
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
Vendored
+584
@@ -0,0 +1,584 @@
|
||||
/*
|
||||
tre_regexec.c - TRE POSIX compatible matching functions (and more).
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
/* AIX requires this to be the first thing in the file. */
|
||||
#ifndef __GNUC__
|
||||
# if HAVE_ALLOCA_H
|
||||
# include <alloca.h>
|
||||
# else
|
||||
# ifdef _AIX
|
||||
#pragma alloca
|
||||
# else
|
||||
# ifndef alloca /* predefined by HP cc +Olibcalls */
|
||||
char *alloca ();
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
#endif /* TRE_USE_ALLOCA */
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
#ifdef HAVE_WCTYPE_H
|
||||
#include <wctype.h>
|
||||
#endif /* HAVE_WCTYPE_H */
|
||||
#ifndef TRE_WCHAR
|
||||
#include <ctype.h>
|
||||
#endif /* !TRE_WCHAR */
|
||||
#ifdef HAVE_MALLOC_H
|
||||
#include <malloc.h>
|
||||
#endif /* HAVE_MALLOC_H */
|
||||
#include <limits.h>
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
/* Literal alternatives are grouped by the first byte so the matcher can
|
||||
* reach the relevant candidates in O(1). In nocase mode the lookup uses the
|
||||
* same folded byte mapping that was applied at compile time. */
|
||||
static void
|
||||
tre_litopt_candidate_range(const tre_literal_opt_t *opt, unsigned char first_byte,
|
||||
size_t *start, size_t *end)
|
||||
{
|
||||
unsigned char key = opt->nocase ? opt->fold_map[first_byte] : first_byte;
|
||||
*start = opt->start_offsets[key];
|
||||
*end = opt->start_offsets[key + 1];
|
||||
}
|
||||
|
||||
static int
|
||||
tre_litopt_bytes_equal(const unsigned char *haystack,
|
||||
const unsigned char *needle, size_t len,
|
||||
const unsigned char *fold_map)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (fold_map == NULL)
|
||||
return memcmp(haystack, needle, len) == 0;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
if (fold_map[haystack[i]] != needle[i])
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
tre_litopt_contains_case(const unsigned char *haystack, size_t hay_len,
|
||||
const unsigned char *needle, size_t needle_len,
|
||||
int *match_end_ofs)
|
||||
{
|
||||
const unsigned char *p;
|
||||
size_t remaining;
|
||||
|
||||
if (needle_len > hay_len)
|
||||
return 0;
|
||||
|
||||
p = haystack;
|
||||
remaining = hay_len;
|
||||
while (remaining >= needle_len)
|
||||
{
|
||||
p = memchr(p, needle[0], remaining - needle_len + 1);
|
||||
if (p == NULL)
|
||||
return 0;
|
||||
if (memcmp(p, needle, needle_len) == 0)
|
||||
{
|
||||
if (match_end_ofs != NULL)
|
||||
*match_end_ofs = (int)(p - haystack + needle_len);
|
||||
return 1;
|
||||
}
|
||||
remaining = hay_len - (size_t)(p - haystack) - 1;
|
||||
p++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Nocase substring matching is still byte-oriented, but scanning once and
|
||||
* only checking literals that share the same folded first byte avoids the
|
||||
* old O(haystack * literals) restart pattern. */
|
||||
static int
|
||||
tre_litopt_contains_nocase(const tre_literal_opt_t *opt,
|
||||
const unsigned char *haystack, size_t hay_len,
|
||||
int *match_end_ofs)
|
||||
{
|
||||
size_t i, start, end, j;
|
||||
|
||||
for (i = 0; i < hay_len; i++)
|
||||
{
|
||||
tre_litopt_candidate_range(opt, haystack[i], &start, &end);
|
||||
for (j = start; j < end; j++)
|
||||
{
|
||||
const tre_literal_opt_literal_t *lit = &opt->literals[j];
|
||||
if (lit->len <= hay_len - i
|
||||
&& tre_litopt_bytes_equal(haystack + i, lit->data, lit->len,
|
||||
opt->fold_map))
|
||||
{
|
||||
if (match_end_ofs != NULL)
|
||||
*match_end_ofs = (int)(i + lit->len);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static reg_errcode_t
|
||||
tre_match_literal_opt(const tre_tnfa_t *tnfa, const char *string, size_t len,
|
||||
int eflags, int *match_end_ofs)
|
||||
{
|
||||
const tre_literal_opt_t *opt = &tnfa->literal_opt;
|
||||
const unsigned char *haystack = (const unsigned char *)string;
|
||||
size_t start = 0, end = opt->num_literals, i;
|
||||
const unsigned char *fold_map = opt->nocase ? opt->fold_map : NULL;
|
||||
|
||||
if ((opt->mode == TRE_LITERAL_OPT_PREFIX
|
||||
|| opt->mode == TRE_LITERAL_OPT_EXACT)
|
||||
&& (eflags & REG_NOTBOL))
|
||||
return REG_NOMATCH;
|
||||
if ((opt->mode == TRE_LITERAL_OPT_SUFFIX
|
||||
|| opt->mode == TRE_LITERAL_OPT_EXACT)
|
||||
&& (eflags & REG_NOTEOL))
|
||||
return REG_NOMATCH;
|
||||
|
||||
if ((opt->mode == TRE_LITERAL_OPT_EXACT
|
||||
|| opt->mode == TRE_LITERAL_OPT_PREFIX)
|
||||
&& len > 0)
|
||||
tre_litopt_candidate_range(opt, haystack[0], &start, &end);
|
||||
|
||||
if (opt->mode == TRE_LITERAL_OPT_CONTAINS)
|
||||
{
|
||||
if (opt->nocase)
|
||||
return tre_litopt_contains_nocase(opt, haystack, len, match_end_ofs)
|
||||
? REG_OK : REG_NOMATCH;
|
||||
|
||||
for (i = 0; i < opt->num_literals; i++)
|
||||
{
|
||||
const tre_literal_opt_literal_t *lit = &opt->literals[i];
|
||||
if (tre_litopt_contains_case(haystack, len, lit->data, lit->len,
|
||||
match_end_ofs))
|
||||
return REG_OK;
|
||||
}
|
||||
return REG_NOMATCH;
|
||||
}
|
||||
|
||||
for (i = start; i < end; i++)
|
||||
{
|
||||
const tre_literal_opt_literal_t *lit = &opt->literals[i];
|
||||
|
||||
switch (opt->mode)
|
||||
{
|
||||
case TRE_LITERAL_OPT_EXACT:
|
||||
if (len == lit->len
|
||||
&& tre_litopt_bytes_equal(haystack, lit->data, len, fold_map))
|
||||
{
|
||||
if (match_end_ofs != NULL)
|
||||
*match_end_ofs = (int)len;
|
||||
return REG_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRE_LITERAL_OPT_PREFIX:
|
||||
if (len >= lit->len
|
||||
&& tre_litopt_bytes_equal(haystack, lit->data, lit->len,
|
||||
fold_map))
|
||||
{
|
||||
if (match_end_ofs != NULL)
|
||||
*match_end_ofs = (int)lit->len;
|
||||
return REG_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRE_LITERAL_OPT_SUFFIX:
|
||||
if (len >= lit->len
|
||||
&& tre_litopt_bytes_equal(haystack + len - lit->len, lit->data,
|
||||
lit->len, fold_map))
|
||||
{
|
||||
if (match_end_ofs != NULL)
|
||||
*match_end_ofs = (int)len;
|
||||
return REG_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case TRE_LITERAL_OPT_CONTAINS:
|
||||
case TRE_LITERAL_OPT_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return REG_NOMATCH;
|
||||
}
|
||||
|
||||
|
||||
/* Fills the POSIX.2 regmatch_t array according to the TNFA tag and match
|
||||
endpoint values. */
|
||||
void
|
||||
tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
|
||||
const tre_tnfa_t *tnfa, int *tags, int match_eo)
|
||||
{
|
||||
tre_submatch_data_t *submatch_data;
|
||||
unsigned int i, j;
|
||||
int *parents;
|
||||
|
||||
i = 0;
|
||||
if (match_eo >= 0 && !(cflags & REG_NOSUB))
|
||||
{
|
||||
/* Construct submatch offsets from the tags. */
|
||||
DPRINT(("end tag = t%d = %d\n", tnfa->end_tag, match_eo));
|
||||
submatch_data = tnfa->submatch_data;
|
||||
while (i < tnfa->num_submatches && i < nmatch)
|
||||
{
|
||||
if (submatch_data[i].so_tag == tnfa->end_tag)
|
||||
pmatch[i].rm_so = match_eo;
|
||||
else
|
||||
pmatch[i].rm_so = tags[submatch_data[i].so_tag];
|
||||
|
||||
if (submatch_data[i].eo_tag == tnfa->end_tag)
|
||||
pmatch[i].rm_eo = match_eo;
|
||||
else
|
||||
pmatch[i].rm_eo = tags[submatch_data[i].eo_tag];
|
||||
|
||||
/* If either of the endpoints were not used, this submatch
|
||||
was not part of the match. */
|
||||
if (pmatch[i].rm_so == -1 || pmatch[i].rm_eo == -1)
|
||||
pmatch[i].rm_so = pmatch[i].rm_eo = -1;
|
||||
|
||||
DPRINT(("pmatch[%d] = {t%d = %d, t%d = %d}\n", i,
|
||||
submatch_data[i].so_tag, pmatch[i].rm_so,
|
||||
submatch_data[i].eo_tag, pmatch[i].rm_eo));
|
||||
i++;
|
||||
}
|
||||
/* Reset all submatches that are not within all of their parent
|
||||
submatches. */
|
||||
i = 0;
|
||||
while (i < tnfa->num_submatches && i < nmatch)
|
||||
{
|
||||
if (pmatch[i].rm_eo == -1)
|
||||
assert(pmatch[i].rm_so == -1);
|
||||
assert(pmatch[i].rm_so <= pmatch[i].rm_eo);
|
||||
|
||||
parents = submatch_data[i].parents;
|
||||
if (parents != NULL)
|
||||
for (j = 0; parents[j] >= 0; j++)
|
||||
{
|
||||
DPRINT(("pmatch[%d] parent %d\n", i, parents[j]));
|
||||
if (pmatch[i].rm_so < pmatch[parents[j]].rm_so
|
||||
|| pmatch[i].rm_eo > pmatch[parents[j]].rm_eo)
|
||||
pmatch[i].rm_so = pmatch[i].rm_eo = -1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
while (i < nmatch)
|
||||
{
|
||||
pmatch[i].rm_so = -1;
|
||||
pmatch[i].rm_eo = -1;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Wrapper functions for POSIX compatible regexp matching.
|
||||
*/
|
||||
|
||||
int
|
||||
tre_have_backrefs(const regex_t *preg)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
return tnfa->have_backrefs;
|
||||
}
|
||||
|
||||
int
|
||||
tre_have_approx(const regex_t *preg)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
return tnfa->have_approx;
|
||||
}
|
||||
|
||||
static int
|
||||
tre_match(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
|
||||
tre_str_type_t type, size_t nmatch, regmatch_t pmatch[],
|
||||
int eflags)
|
||||
{
|
||||
reg_errcode_t status;
|
||||
int *tags = NULL, eo;
|
||||
if (tnfa->num_tags > 0 && nmatch > 0)
|
||||
{
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
tags = alloca(sizeof(*tags) * tnfa->num_tags);
|
||||
#else /* !TRE_USE_ALLOCA */
|
||||
tags = xmalloc(sizeof(*tags) * tnfa->num_tags);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
if (tags == NULL)
|
||||
return REG_ESPACE;
|
||||
}
|
||||
|
||||
if (type == STR_BYTE
|
||||
&& tnfa->literal_opt.mode != TRE_LITERAL_OPT_NONE
|
||||
&& (nmatch == 0 || (tnfa->cflags & REG_NOSUB))
|
||||
#ifdef TRE_APPROX
|
||||
&& !(eflags & REG_APPROX_MATCHER)
|
||||
#endif /* TRE_APPROX */
|
||||
&& !(eflags & REG_BACKTRACKING_MATCHER))
|
||||
{
|
||||
size_t byte_len = (len >= 0) ? (size_t)len : strlen((const char *)string);
|
||||
status = tre_match_literal_opt(tnfa, string, byte_len, eflags, &eo);
|
||||
|
||||
/* Even when the caller asked for no submatches, regexec() still has to
|
||||
* clear any pmatch entries it was handed. The normal matcher path does
|
||||
* this through tre_fill_pmatch(), so mirror that behavior here. */
|
||||
if (status == REG_OK && nmatch > 0)
|
||||
tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, NULL, eo);
|
||||
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (tags)
|
||||
xfree(tags);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Dispatch to the appropriate matcher. */
|
||||
if (tnfa->have_backrefs || eflags & REG_BACKTRACKING_MATCHER)
|
||||
{
|
||||
/* The regex has back references, use the backtracking matcher. */
|
||||
if (type == STR_USER)
|
||||
{
|
||||
const tre_str_source *source = string;
|
||||
if (source->rewind == NULL || source->compare == NULL)
|
||||
{
|
||||
/* The backtracking matcher requires rewind and compare
|
||||
capabilities from the input stream. */
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (tags)
|
||||
xfree(tags);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
return REG_BADPAT;
|
||||
}
|
||||
}
|
||||
status = tre_tnfa_run_backtrack(tnfa, string, len, type,
|
||||
tags, eflags, &eo);
|
||||
}
|
||||
#ifdef TRE_APPROX
|
||||
else if (tnfa->have_approx || eflags & REG_APPROX_MATCHER)
|
||||
{
|
||||
/* The regex uses approximate matching, use the approximate matcher. */
|
||||
regamatch_t match;
|
||||
regaparams_t params;
|
||||
tre_regaparams_default(¶ms);
|
||||
params.max_err = 0;
|
||||
params.max_cost = 0;
|
||||
status = tre_tnfa_run_approx(tnfa, string, len, type, tags,
|
||||
&match, params, eflags, &eo);
|
||||
}
|
||||
#endif /* TRE_APPROX */
|
||||
else
|
||||
{
|
||||
/* Exact matching, no back references, use the parallel matcher. */
|
||||
status = tre_tnfa_run_parallel(tnfa, string, len, type,
|
||||
tags, eflags, &eo);
|
||||
}
|
||||
|
||||
if (status == REG_OK)
|
||||
/* A match was found, so fill the submatch registers. */
|
||||
tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, tags, eo);
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (tags)
|
||||
xfree(tags);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
return status;
|
||||
}
|
||||
|
||||
int
|
||||
tre_regnexec(const regex_t *preg, const char *str, size_t len,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
tre_str_type_t type = (TRE_MB_CUR_MAX == 1) ? STR_BYTE : STR_MBS;
|
||||
|
||||
return tre_match(tnfa, str, len, type, nmatch, pmatch, eflags);
|
||||
}
|
||||
|
||||
#ifdef TRE_USE_GNUC_REGEXEC_FPL
|
||||
int
|
||||
tre_regexec(const regex_t *preg, const char *str,
|
||||
size_t nmatch, regmatch_t pmatch[_Restrict_arr_ _REGEX_NELTS (nmatch)],
|
||||
int eflags)
|
||||
#else
|
||||
int
|
||||
tre_regexec(const regex_t *preg, const char *str,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags)
|
||||
#endif
|
||||
{
|
||||
return tre_regnexec(preg, str, -1, nmatch, pmatch, eflags);
|
||||
}
|
||||
|
||||
int
|
||||
tre_regexecb(const regex_t *preg, const char *str,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
|
||||
return tre_match(tnfa, str, -1, STR_BYTE, nmatch, pmatch, eflags);
|
||||
}
|
||||
|
||||
int
|
||||
tre_regnexecb(const regex_t *preg, const char *str, size_t len,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
|
||||
return tre_match(tnfa, str, len, STR_BYTE, nmatch, pmatch, eflags);
|
||||
}
|
||||
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
|
||||
int
|
||||
tre_regwnexec(const regex_t *preg, const wchar_t *str, size_t len,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
return tre_match(tnfa, str, len, STR_WIDE, nmatch, pmatch, eflags);
|
||||
}
|
||||
|
||||
int
|
||||
tre_regwexec(const regex_t *preg, const wchar_t *str,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags)
|
||||
{
|
||||
return tre_regwnexec(preg, str, -1, nmatch, pmatch, eflags);
|
||||
}
|
||||
|
||||
#endif /* TRE_WCHAR */
|
||||
|
||||
int
|
||||
tre_reguexec(const regex_t *preg, const tre_str_source *str,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
return tre_match(tnfa, str, -1, STR_USER, nmatch, pmatch, eflags);
|
||||
}
|
||||
|
||||
|
||||
#ifdef TRE_APPROX
|
||||
|
||||
/*
|
||||
Wrapper functions for approximate regexp matching.
|
||||
*/
|
||||
|
||||
static int
|
||||
tre_match_approx(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
|
||||
tre_str_type_t type, regamatch_t *match, regaparams_t params,
|
||||
int eflags)
|
||||
{
|
||||
reg_errcode_t status;
|
||||
int *tags = NULL, eo;
|
||||
|
||||
/* If the regexp does not use approximate matching features, the
|
||||
maximum cost is zero, and the approximate matcher isn't forced,
|
||||
use the exact matcher instead. */
|
||||
if (params.max_cost == 0 && !tnfa->have_approx
|
||||
&& !(eflags & REG_APPROX_MATCHER))
|
||||
return tre_match(tnfa, string, len, type, match->nmatch, match->pmatch,
|
||||
eflags);
|
||||
|
||||
/* Back references are not supported by the approximate matcher. */
|
||||
if (tnfa->have_backrefs)
|
||||
return REG_BADPAT;
|
||||
|
||||
if (tnfa->num_tags > 0 && match->nmatch > 0)
|
||||
{
|
||||
#if TRE_USE_ALLOCA
|
||||
tags = alloca(sizeof(*tags) * tnfa->num_tags);
|
||||
#else /* !TRE_USE_ALLOCA */
|
||||
tags = xmalloc(sizeof(*tags) * tnfa->num_tags);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
if (tags == NULL)
|
||||
return REG_ESPACE;
|
||||
}
|
||||
status = tre_tnfa_run_approx(tnfa, string, len, type, tags,
|
||||
match, params, eflags, &eo);
|
||||
if (status == REG_OK)
|
||||
tre_fill_pmatch(match->nmatch, match->pmatch, tnfa->cflags, tnfa, tags, eo);
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (tags)
|
||||
xfree(tags);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
return status;
|
||||
}
|
||||
|
||||
int
|
||||
tre_reganexec(const regex_t *preg, const char *str, size_t len,
|
||||
regamatch_t *match, regaparams_t params, int eflags)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
tre_str_type_t type = (TRE_MB_CUR_MAX == 1) ? STR_BYTE : STR_MBS;
|
||||
|
||||
return tre_match_approx(tnfa, str, len, type, match, params, eflags);
|
||||
}
|
||||
|
||||
int
|
||||
tre_regaexec(const regex_t *preg, const char *str,
|
||||
regamatch_t *match, regaparams_t params, int eflags)
|
||||
{
|
||||
return tre_reganexec(preg, str, -1, match, params, eflags);
|
||||
}
|
||||
|
||||
int
|
||||
tre_regaexecb(const regex_t *preg, const char *str,
|
||||
regamatch_t *match, regaparams_t params, int eflags)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
|
||||
return tre_match_approx(tnfa, str, -1, STR_BYTE, match, params, eflags);
|
||||
}
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
|
||||
int
|
||||
tre_regawnexec(const regex_t *preg, const wchar_t *str, size_t len,
|
||||
regamatch_t *match, regaparams_t params, int eflags)
|
||||
{
|
||||
tre_tnfa_t *tnfa = (void *)preg->TRE_REGEX_T_FIELD;
|
||||
return tre_match_approx(tnfa, str, len, STR_WIDE,
|
||||
match, params, eflags);
|
||||
}
|
||||
|
||||
int
|
||||
tre_regawexec(const regex_t *preg, const wchar_t *str,
|
||||
regamatch_t *match, regaparams_t params, int eflags)
|
||||
{
|
||||
return tre_regawnexec(preg, str, -1, match, params, eflags);
|
||||
}
|
||||
|
||||
#endif /* TRE_WCHAR */
|
||||
|
||||
void
|
||||
tre_regaparams_default(regaparams_t *params)
|
||||
{
|
||||
memset(params, 0, sizeof(*params));
|
||||
params->cost_ins = 1;
|
||||
params->cost_del = 1;
|
||||
params->cost_subst = 1;
|
||||
params->max_cost = INT_MAX;
|
||||
params->max_ins = INT_MAX;
|
||||
params->max_del = INT_MAX;
|
||||
params->max_subst = INT_MAX;
|
||||
params->max_err = INT_MAX;
|
||||
}
|
||||
|
||||
#endif /* TRE_APPROX */
|
||||
|
||||
/* EOF */
|
||||
Vendored
+226
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
tre-ast.c - Abstract syntax tree (AST) routines
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
#include <assert.h>
|
||||
|
||||
#include "tre-ast.h"
|
||||
#include "tre-mem.h"
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_node(tre_mem_t mem, tre_ast_type_t type, size_t size)
|
||||
{
|
||||
tre_ast_node_t *node;
|
||||
|
||||
node = tre_mem_calloc(mem, sizeof(*node));
|
||||
if (!node)
|
||||
return NULL;
|
||||
node->obj = tre_mem_calloc(mem, size);
|
||||
if (!node->obj)
|
||||
return NULL;
|
||||
node->type = type;
|
||||
node->nullable = -1;
|
||||
node->submatch_id = -1;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max)
|
||||
{
|
||||
tre_ast_node_t *node;
|
||||
tre_literal_t *lit;
|
||||
|
||||
node = tre_ast_new_node(mem, LITERAL, sizeof(tre_literal_t));
|
||||
if (!node)
|
||||
return NULL;
|
||||
lit = node->obj;
|
||||
lit->code_min = code_min;
|
||||
lit->code_max = code_max;
|
||||
lit->position = -1;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max,
|
||||
int minimal)
|
||||
{
|
||||
tre_ast_node_t *node;
|
||||
tre_iteration_t *iter;
|
||||
|
||||
node = tre_ast_new_node(mem, ITERATION, sizeof(tre_iteration_t));
|
||||
if (!node)
|
||||
return NULL;
|
||||
iter = node->obj;
|
||||
iter->arg = arg;
|
||||
iter->min = min;
|
||||
iter->max = max;
|
||||
iter->minimal = minimal;
|
||||
node->num_submatches = arg->num_submatches;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right)
|
||||
{
|
||||
tre_ast_node_t *node;
|
||||
|
||||
node = tre_ast_new_node(mem, UNION, sizeof(tre_union_t));
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
((tre_union_t *)node->obj)->left = left;
|
||||
((tre_union_t *)node->obj)->right = right;
|
||||
node->num_submatches = left->num_submatches + right->num_submatches;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left,
|
||||
tre_ast_node_t *right)
|
||||
{
|
||||
tre_ast_node_t *node;
|
||||
|
||||
node = tre_ast_new_node(mem, CATENATION, sizeof(tre_catenation_t));
|
||||
if (node == NULL)
|
||||
return NULL;
|
||||
((tre_catenation_t *)node->obj)->left = left;
|
||||
((tre_catenation_t *)node->obj)->right = right;
|
||||
node->num_submatches = left->num_submatches + right->num_submatches;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
|
||||
static void
|
||||
tre_findent(FILE *stream, int i)
|
||||
{
|
||||
while (i-- > 0)
|
||||
fputc(' ', stream);
|
||||
}
|
||||
|
||||
void
|
||||
tre_print_params(int *params)
|
||||
{
|
||||
int i;
|
||||
if (params)
|
||||
{
|
||||
DPRINT(("params ["));
|
||||
for (i = 0; i < TRE_PARAM_LAST; i++)
|
||||
{
|
||||
if (params[i] == TRE_PARAM_UNSET)
|
||||
DPRINT(("unset"));
|
||||
else if (params[i] == TRE_PARAM_DEFAULT)
|
||||
DPRINT(("default"));
|
||||
else
|
||||
DPRINT(("%d", params[i]));
|
||||
if (i < TRE_PARAM_LAST - 1)
|
||||
DPRINT((", "));
|
||||
}
|
||||
DPRINT(("]"));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
tre_do_print(FILE *stream, tre_ast_node_t *ast, int indent)
|
||||
{
|
||||
int code_min, code_max, pos;
|
||||
int num_tags = ast->num_tags;
|
||||
tre_literal_t *lit;
|
||||
tre_iteration_t *iter;
|
||||
|
||||
tre_findent(stream, indent);
|
||||
switch (ast->type)
|
||||
{
|
||||
case LITERAL:
|
||||
lit = ast->obj;
|
||||
code_min = lit->code_min;
|
||||
code_max = lit->code_max;
|
||||
pos = lit->position;
|
||||
if (IS_EMPTY(lit))
|
||||
{
|
||||
fprintf(stream, "literal empty\n");
|
||||
}
|
||||
else if (IS_ASSERTION(lit))
|
||||
{
|
||||
int i;
|
||||
char *assertions[] = { "bol", "eol", "ctype", "!ctype",
|
||||
"bow", "eow", "wb", "!wb" };
|
||||
if (code_max >= ASSERT_LAST << 1)
|
||||
assert(0);
|
||||
fprintf(stream, "assertions: ");
|
||||
for (i = 0; (1 << i) <= ASSERT_LAST; i++)
|
||||
if (code_max & (1 << i))
|
||||
fprintf(stream, "%s ", assertions[i]);
|
||||
fprintf(stream, "\n");
|
||||
}
|
||||
else if (IS_TAG(lit))
|
||||
{
|
||||
fprintf(stream, "tag %d\n", code_max);
|
||||
}
|
||||
else if (IS_BACKREF(lit))
|
||||
{
|
||||
fprintf(stream, "backref %d, pos %d\n", code_max, pos);
|
||||
}
|
||||
else if (IS_PARAMETER(lit))
|
||||
{
|
||||
tre_print_params(lit->u.params);
|
||||
fprintf(stream, "\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stream, "literal (%c, %c) (%d, %d), pos %d, sub %d, "
|
||||
"%d tags\n", code_min, code_max, code_min, code_max, pos,
|
||||
ast->submatch_id, num_tags);
|
||||
}
|
||||
break;
|
||||
case ITERATION:
|
||||
iter = ast->obj;
|
||||
fprintf(stream, "iteration {%d, %d}, sub %d, %d tags, %s\n",
|
||||
iter->min, iter->max, ast->submatch_id, num_tags,
|
||||
iter->minimal ? "minimal" : "greedy");
|
||||
tre_do_print(stream, iter->arg, indent + 2);
|
||||
break;
|
||||
case UNION:
|
||||
fprintf(stream, "union, sub %d, %d tags\n", ast->submatch_id, num_tags);
|
||||
tre_do_print(stream, ((tre_union_t *)ast->obj)->left, indent + 2);
|
||||
tre_do_print(stream, ((tre_union_t *)ast->obj)->right, indent + 2);
|
||||
break;
|
||||
case CATENATION:
|
||||
fprintf(stream, "catenation, sub %d, %d tags\n", ast->submatch_id,
|
||||
num_tags);
|
||||
tre_do_print(stream, ((tre_catenation_t *)ast->obj)->left, indent + 2);
|
||||
tre_do_print(stream, ((tre_catenation_t *)ast->obj)->right, indent + 2);
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
tre_ast_fprint(FILE *stream, tre_ast_node_t *ast)
|
||||
{
|
||||
tre_do_print(stream, ast, 0);
|
||||
}
|
||||
|
||||
void
|
||||
tre_ast_print(tre_ast_node_t *tree)
|
||||
{
|
||||
printf("AST:\n");
|
||||
tre_ast_fprint(stdout, tree);
|
||||
}
|
||||
|
||||
#endif /* TRE_DEBUG */
|
||||
|
||||
/* EOF */
|
||||
Vendored
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
tre-ast.h - Abstract syntax tree (AST) definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef TRE_AST_H
|
||||
#define TRE_AST_H 1
|
||||
|
||||
#include "tre-mem.h"
|
||||
#include "tre-internal.h"
|
||||
#include "tre-compile.h"
|
||||
|
||||
/* The different AST node types. */
|
||||
typedef enum {
|
||||
LITERAL,
|
||||
CATENATION,
|
||||
ITERATION,
|
||||
UNION
|
||||
} tre_ast_type_t;
|
||||
|
||||
/* Special subtypes of TRE_LITERAL. */
|
||||
#define EMPTY -1 /* Empty leaf (denotes empty string). */
|
||||
#define ASSERTION -2 /* Assertion leaf. */
|
||||
#define TAG -3 /* Tag leaf. */
|
||||
#define BACKREF -4 /* Back reference leaf. */
|
||||
#define PARAMETER -5 /* Parameter. */
|
||||
|
||||
#define IS_SPECIAL(x) ((x)->code_min < 0)
|
||||
#define IS_EMPTY(x) ((x)->code_min == EMPTY)
|
||||
#define IS_ASSERTION(x) ((x)->code_min == ASSERTION)
|
||||
#define IS_TAG(x) ((x)->code_min == TAG)
|
||||
#define IS_BACKREF(x) ((x)->code_min == BACKREF)
|
||||
#define IS_PARAMETER(x) ((x)->code_min == PARAMETER)
|
||||
|
||||
|
||||
/* A generic AST node. All AST nodes consist of this node on the top
|
||||
level with `obj' pointing to the actual content. */
|
||||
typedef struct {
|
||||
tre_ast_type_t type; /* Type of the node. */
|
||||
void *obj; /* Pointer to actual node. */
|
||||
int nullable;
|
||||
int submatch_id;
|
||||
unsigned int num_submatches;
|
||||
unsigned int num_tags;
|
||||
tre_pos_and_tags_t *firstpos;
|
||||
tre_pos_and_tags_t *lastpos;
|
||||
} tre_ast_node_t;
|
||||
|
||||
|
||||
/* A "literal" node. These are created for assertions, back references,
|
||||
tags, matching parameter settings, and all expressions that match one
|
||||
character. */
|
||||
typedef struct {
|
||||
long code_min;
|
||||
long code_max;
|
||||
int position;
|
||||
union {
|
||||
tre_ctype_t class;
|
||||
int *params;
|
||||
} u;
|
||||
tre_ctype_t *neg_classes;
|
||||
} tre_literal_t;
|
||||
|
||||
/* A "catenation" node. These are created when two regexps are concatenated.
|
||||
If there are more than one subexpressions in sequence, the `left' part
|
||||
holds all but the last, and `right' part holds the last subexpression
|
||||
(catenation is left associative). */
|
||||
typedef struct {
|
||||
tre_ast_node_t *left;
|
||||
tre_ast_node_t *right;
|
||||
} tre_catenation_t;
|
||||
|
||||
/* An "iteration" node. These are created for the "*", "+", "?", and "{m,n}"
|
||||
operators. */
|
||||
typedef struct {
|
||||
/* Subexpression to match. */
|
||||
tre_ast_node_t *arg;
|
||||
/* Minimum number of consecutive matches. */
|
||||
int min;
|
||||
/* Maximum number of consecutive matches. */
|
||||
int max;
|
||||
/* If 0, match as many characters as possible, if 1 match as few as
|
||||
possible. Note that this does not always mean the same thing as
|
||||
matching as many/few repetitions as possible. */
|
||||
unsigned int minimal:1;
|
||||
/* Approximate matching parameters (or NULL). */
|
||||
int *params;
|
||||
} tre_iteration_t;
|
||||
|
||||
/* An "union" node. These are created for the "|" operator. */
|
||||
typedef struct {
|
||||
tre_ast_node_t *left;
|
||||
tre_ast_node_t *right;
|
||||
} tre_union_t;
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_node(tre_mem_t mem, tre_ast_type_t type, size_t size);
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max);
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max,
|
||||
int minimal);
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right);
|
||||
|
||||
tre_ast_node_t *
|
||||
tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left,
|
||||
tre_ast_node_t *right);
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
void
|
||||
tre_ast_print(tre_ast_node_t *tree);
|
||||
|
||||
/* XXX - rethink AST printing API */
|
||||
void
|
||||
tre_print_params(int *params);
|
||||
#endif /* TRE_DEBUG */
|
||||
|
||||
#endif /* TRE_AST_H */
|
||||
|
||||
/* EOF */
|
||||
Vendored
+2673
File diff suppressed because it is too large
Load Diff
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
tre-compile.h: Regex compilation definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef TRE_COMPILE_H
|
||||
#define TRE_COMPILE_H 1
|
||||
|
||||
typedef struct {
|
||||
int position;
|
||||
int code_min;
|
||||
int code_max;
|
||||
int *tags;
|
||||
int assertions;
|
||||
tre_ctype_t class;
|
||||
tre_ctype_t *neg_classes;
|
||||
int backref;
|
||||
int *params;
|
||||
} tre_pos_and_tags_t;
|
||||
|
||||
#endif /* TRE_COMPILE_H */
|
||||
|
||||
/* EOF */
|
||||
Vendored
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
tre-filter.c: Histogram filter to quickly find regexp match candidates
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
/* The idea of this filter is quite simple. First, let's assume the
|
||||
search pattern is a simple string. In order for a substring of a
|
||||
longer string to match the search pattern, it must have the same
|
||||
numbers of different characters as the pattern, and those
|
||||
characters must occur in the same order as they occur in pattern. */
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
#include <stdio.h>
|
||||
#include "tre-internal.h"
|
||||
#include "tre-filter.h"
|
||||
|
||||
int
|
||||
tre_filter_find(const unsigned char *str, size_t len, tre_filter_t *filter)
|
||||
{
|
||||
unsigned short counts[256];
|
||||
unsigned int i;
|
||||
unsigned int window_len = filter->window_len;
|
||||
tre_filter_profile_t *profile = filter->profile;
|
||||
const unsigned char *str_orig = str;
|
||||
|
||||
DPRINT(("tre_filter_find: %.*s\n", len, str));
|
||||
|
||||
for (i = 0; i < elementsof(counts); i++)
|
||||
counts[i] = 0;
|
||||
|
||||
i = 0;
|
||||
while (*str && i < window_len && i < len)
|
||||
{
|
||||
counts[*str]++;
|
||||
i++;
|
||||
str++;
|
||||
len--;
|
||||
}
|
||||
|
||||
while (len > 0)
|
||||
{
|
||||
tre_filter_profile_t *p;
|
||||
counts[*str]++;
|
||||
counts[*(str - window_len)]--;
|
||||
|
||||
p = profile;
|
||||
while (p->ch)
|
||||
{
|
||||
if (counts[p->ch] < p->count)
|
||||
break;
|
||||
p++;
|
||||
}
|
||||
if (!p->ch)
|
||||
{
|
||||
DPRINT(("Found possible match at %d\n",
|
||||
str - str_orig));
|
||||
return str - str_orig;
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINT(("No match so far...\n"));
|
||||
}
|
||||
len--;
|
||||
str++;
|
||||
}
|
||||
DPRINT(("This string cannot match.\n"));
|
||||
return -1;
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
unsigned char ch;
|
||||
unsigned char count;
|
||||
} tre_filter_profile_t;
|
||||
|
||||
typedef struct {
|
||||
/* Length of the window where the character counts are kept. */
|
||||
int window_len;
|
||||
/* Required character counts table. */
|
||||
tre_filter_profile_t *profile;
|
||||
} tre_filter_t;
|
||||
|
||||
|
||||
int
|
||||
tre_filter_find(const unsigned char *str, size_t len, tre_filter_t *filter);
|
||||
Vendored
+319
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
tre-internal.h - TRE internal definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TRE_INTERNAL_H
|
||||
#define TRE_INTERNAL_H 1
|
||||
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
|
||||
#ifdef HAVE_WCTYPE_H
|
||||
#include <wctype.h>
|
||||
#endif /* HAVE_WCTYPE_H */
|
||||
|
||||
#ifdef HAVE_SYS_TYPES_H
|
||||
#include <sys/types.h>
|
||||
#endif /* HAVE_SYS_TYPES_H */
|
||||
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
#include "../local_includes/tre.h"
|
||||
|
||||
#define TRE_MAX_RE 65536
|
||||
#define TRE_MAX_STRING INT_MAX
|
||||
#define TRE_MAX_STACK 1048576
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
#include <stdio.h>
|
||||
#define DPRINT(msg) do {printf msg; fflush(stdout);} while(/*CONSTCOND*/(void)0,0)
|
||||
#else /* !TRE_DEBUG */
|
||||
#define DPRINT(msg) do { } while(/*CONSTCOND*/(void)0,0)
|
||||
#endif /* !TRE_DEBUG */
|
||||
|
||||
#define elementsof(x) ( sizeof(x) / sizeof(x[0]) )
|
||||
|
||||
#ifdef HAVE_MBRTOWC
|
||||
#define tre_mbrtowc(pwc, s, n, ps) (mbrtowc((pwc), (s), (n), (ps)))
|
||||
#else /* !HAVE_MBRTOWC */
|
||||
#ifdef HAVE_MBTOWC
|
||||
#define tre_mbrtowc(pwc, s, n, ps) (mbtowc((pwc), (s), (n)))
|
||||
#endif /* HAVE_MBTOWC */
|
||||
#endif /* !HAVE_MBRTOWC */
|
||||
|
||||
#ifdef TRE_MULTIBYTE
|
||||
#ifdef HAVE_MBSTATE_T
|
||||
#define TRE_MBSTATE
|
||||
#endif /* TRE_MULTIBYTE */
|
||||
#endif /* HAVE_MBSTATE_T */
|
||||
|
||||
/* Define the character types and functions. */
|
||||
#ifdef TRE_WCHAR
|
||||
|
||||
/* Wide characters. */
|
||||
typedef wint_t tre_cint_t;
|
||||
#if WCHAR_MAX <= INT_MAX
|
||||
#define TRE_CHAR_MAX WCHAR_MAX
|
||||
#else /* WCHAR_MAX > INT_MAX */
|
||||
#define TRE_CHAR_MAX INT_MAX
|
||||
#endif
|
||||
|
||||
#ifdef TRE_MULTIBYTE
|
||||
#define TRE_MB_CUR_MAX MB_CUR_MAX
|
||||
#else /* !TRE_MULTIBYTE */
|
||||
#define TRE_MB_CUR_MAX 1
|
||||
#endif /* !TRE_MULTIBYTE */
|
||||
|
||||
#define tre_isalnum iswalnum
|
||||
#define tre_isalpha iswalpha
|
||||
#ifdef HAVE_ISWBLANK
|
||||
#define tre_isblank iswblank
|
||||
#endif /* HAVE_ISWBLANK */
|
||||
#define tre_iscntrl iswcntrl
|
||||
#define tre_isdigit iswdigit
|
||||
#define tre_isgraph iswgraph
|
||||
#define tre_islower iswlower
|
||||
#define tre_isprint iswprint
|
||||
#define tre_ispunct iswpunct
|
||||
#define tre_isspace iswspace
|
||||
#define tre_isupper iswupper
|
||||
#define tre_isxdigit iswxdigit
|
||||
|
||||
#define tre_tolower towlower
|
||||
#define tre_toupper towupper
|
||||
#define tre_strlen wcslen
|
||||
|
||||
#else /* !TRE_WCHAR */
|
||||
|
||||
/* 8 bit characters. */
|
||||
typedef short tre_cint_t;
|
||||
#define TRE_CHAR_MAX 255
|
||||
#define TRE_MB_CUR_MAX 1
|
||||
|
||||
#define tre_isalnum isalnum
|
||||
#define tre_isalpha isalpha
|
||||
#ifdef HAVE_ISASCII
|
||||
#define tre_isascii isascii
|
||||
#endif /* HAVE_ISASCII */
|
||||
#ifdef HAVE_ISBLANK
|
||||
#define tre_isblank isblank
|
||||
#endif /* HAVE_ISBLANK */
|
||||
#define tre_iscntrl iscntrl
|
||||
#define tre_isdigit isdigit
|
||||
#define tre_isgraph isgraph
|
||||
#define tre_islower islower
|
||||
#define tre_isprint isprint
|
||||
#define tre_ispunct ispunct
|
||||
#define tre_isspace isspace
|
||||
#define tre_isupper isupper
|
||||
#define tre_isxdigit isxdigit
|
||||
|
||||
#define tre_tolower(c) (tre_cint_t)(tolower(c))
|
||||
#define tre_toupper(c) (tre_cint_t)(toupper(c))
|
||||
#define tre_strlen(s) (strlen((const char*)s))
|
||||
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
#if defined(TRE_WCHAR) && defined(HAVE_ISWCTYPE) && defined(HAVE_WCTYPE)
|
||||
#define TRE_USE_SYSTEM_WCTYPE 1
|
||||
#endif
|
||||
|
||||
#ifdef TRE_USE_SYSTEM_WCTYPE
|
||||
/* Use system provided iswctype() and wctype(). */
|
||||
typedef wctype_t tre_ctype_t;
|
||||
#define tre_isctype iswctype
|
||||
#define tre_ctype wctype
|
||||
#else /* !TRE_USE_SYSTEM_WCTYPE */
|
||||
/* Define our own versions of iswctype() and wctype(). */
|
||||
typedef int (*tre_ctype_t)(tre_cint_t);
|
||||
#define tre_isctype(c, type) ( (type)(c) )
|
||||
tre_ctype_t tre_ctype(const char *name);
|
||||
#endif /* !TRE_USE_SYSTEM_WCTYPE */
|
||||
|
||||
typedef enum { STR_WIDE, STR_BYTE, STR_MBS, STR_USER } tre_str_type_t;
|
||||
|
||||
/* Returns number of bytes to add to (char *)ptr to make it
|
||||
properly aligned for the type. */
|
||||
#define ALIGN(ptr, type) \
|
||||
((((long)ptr) % sizeof(type)) \
|
||||
? (sizeof(type) - (((long)ptr) % sizeof(type))) \
|
||||
: 0)
|
||||
|
||||
#undef MAX
|
||||
#undef MIN
|
||||
#define MAX(a, b) (((a) >= (b)) ? (a) : (b))
|
||||
#define MIN(a, b) (((a) <= (b)) ? (a) : (b))
|
||||
|
||||
/* Define STRF to the correct printf formatter for strings. */
|
||||
#ifdef TRE_WCHAR
|
||||
#define STRF "ls"
|
||||
#else /* !TRE_WCHAR */
|
||||
#define STRF "s"
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
/* TNFA transition type. A TNFA state is an array of transitions,
|
||||
the terminator is a transition with NULL `state'. */
|
||||
typedef struct tnfa_transition tre_tnfa_transition_t;
|
||||
|
||||
struct tnfa_transition {
|
||||
/* Range of accepted characters. */
|
||||
tre_cint_t code_min;
|
||||
tre_cint_t code_max;
|
||||
/* Pointer to the destination state. */
|
||||
tre_tnfa_transition_t *state;
|
||||
/* ID number of the destination state. */
|
||||
int state_id;
|
||||
/* -1 terminated array of tags (or NULL). */
|
||||
int *tags;
|
||||
/* Matching parameters settings (or NULL). */
|
||||
int *params;
|
||||
/* Assertion bitmap. */
|
||||
int assertions;
|
||||
/* Assertion parameters. */
|
||||
union {
|
||||
/* Character class assertion. */
|
||||
tre_ctype_t class;
|
||||
/* Back reference assertion. */
|
||||
int backref;
|
||||
} u;
|
||||
/* Negative character class assertions. */
|
||||
tre_ctype_t *neg_classes;
|
||||
};
|
||||
|
||||
|
||||
/* Assertions. */
|
||||
#define ASSERT_AT_BOL 1 /* Beginning of line. */
|
||||
#define ASSERT_AT_EOL 2 /* End of line. */
|
||||
#define ASSERT_CHAR_CLASS 4 /* Character class in `class'. */
|
||||
#define ASSERT_CHAR_CLASS_NEG 8 /* Character classes in `neg_classes'. */
|
||||
#define ASSERT_AT_BOW 16 /* Beginning of word. */
|
||||
#define ASSERT_AT_EOW 32 /* End of word. */
|
||||
#define ASSERT_AT_WB 64 /* Word boundary. */
|
||||
#define ASSERT_AT_WB_NEG 128 /* Not a word boundary. */
|
||||
#define ASSERT_BACKREF 256 /* A back reference in `backref'. */
|
||||
#define ASSERT_LAST 256
|
||||
|
||||
/* Tag directions. */
|
||||
typedef enum {
|
||||
TRE_TAG_MINIMIZE = 0,
|
||||
TRE_TAG_MAXIMIZE = 1
|
||||
} tre_tag_direction_t;
|
||||
|
||||
/* Parameters that can be changed dynamically while matching. */
|
||||
typedef enum {
|
||||
TRE_PARAM_COST_INS = 0,
|
||||
TRE_PARAM_COST_DEL = 1,
|
||||
TRE_PARAM_COST_SUBST = 2,
|
||||
TRE_PARAM_COST_MAX = 3,
|
||||
TRE_PARAM_MAX_INS = 4,
|
||||
TRE_PARAM_MAX_DEL = 5,
|
||||
TRE_PARAM_MAX_SUBST = 6,
|
||||
TRE_PARAM_MAX_ERR = 7,
|
||||
TRE_PARAM_DEPTH = 8,
|
||||
TRE_PARAM_LAST = 9
|
||||
} tre_param_t;
|
||||
|
||||
/* Unset matching parameter */
|
||||
#define TRE_PARAM_UNSET -1
|
||||
|
||||
/* Signifies the default matching parameter value. */
|
||||
#define TRE_PARAM_DEFAULT -2
|
||||
|
||||
/* Instructions to compute submatch register values from tag values
|
||||
after a successful match. */
|
||||
struct tre_submatch_data {
|
||||
/* Tag that gives the value for rm_so (submatch start offset). */
|
||||
int so_tag;
|
||||
/* Tag that gives the value for rm_eo (submatch end offset). */
|
||||
int eo_tag;
|
||||
/* List of submatches this submatch is contained in. */
|
||||
int *parents;
|
||||
};
|
||||
|
||||
typedef struct tre_submatch_data tre_submatch_data_t;
|
||||
|
||||
typedef enum {
|
||||
TRE_LITERAL_OPT_NONE = 0,
|
||||
TRE_LITERAL_OPT_CONTAINS,
|
||||
TRE_LITERAL_OPT_PREFIX,
|
||||
TRE_LITERAL_OPT_SUFFIX,
|
||||
TRE_LITERAL_OPT_EXACT
|
||||
} tre_literal_opt_mode_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned char *data;
|
||||
size_t len;
|
||||
} tre_literal_opt_literal_t;
|
||||
|
||||
typedef struct {
|
||||
tre_literal_opt_mode_t mode;
|
||||
int nocase;
|
||||
size_t num_literals;
|
||||
/* Folded byte mapping used by the nocase fast path. */
|
||||
unsigned char fold_map[256];
|
||||
/* Literal index ranges grouped by the first literal byte. */
|
||||
size_t start_offsets[257];
|
||||
tre_literal_opt_literal_t *literals;
|
||||
} tre_literal_opt_t;
|
||||
|
||||
|
||||
/* TNFA definition. */
|
||||
typedef struct tnfa tre_tnfa_t;
|
||||
|
||||
struct tnfa {
|
||||
tre_tnfa_transition_t *transitions;
|
||||
unsigned int num_transitions;
|
||||
tre_tnfa_transition_t *initial;
|
||||
tre_tnfa_transition_t *final;
|
||||
tre_submatch_data_t *submatch_data;
|
||||
char *firstpos_chars;
|
||||
int first_char;
|
||||
unsigned int num_submatches;
|
||||
tre_tag_direction_t *tag_directions;
|
||||
int *minimal_tags;
|
||||
int num_tags;
|
||||
int num_minimals;
|
||||
int end_tag;
|
||||
int num_states;
|
||||
int cflags;
|
||||
int have_backrefs;
|
||||
int have_approx;
|
||||
int params_depth;
|
||||
tre_literal_opt_t literal_opt;
|
||||
};
|
||||
|
||||
int
|
||||
tre_compile(regex_t *preg, const tre_char_t *regex, size_t n, int cflags);
|
||||
|
||||
void
|
||||
tre_free(regex_t *preg);
|
||||
|
||||
void
|
||||
tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
|
||||
const tre_tnfa_t *tnfa, int *tags, int match_eo);
|
||||
|
||||
reg_errcode_t
|
||||
tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
|
||||
tre_str_type_t type, int *match_tags, int eflags,
|
||||
int *match_end_ofs);
|
||||
|
||||
reg_errcode_t
|
||||
tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
|
||||
tre_str_type_t type, int *match_tags, int eflags,
|
||||
int *match_end_ofs);
|
||||
|
||||
#ifdef TRE_APPROX
|
||||
reg_errcode_t
|
||||
tre_tnfa_run_approx(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
|
||||
tre_str_type_t type, int *match_tags, regamatch_t *match,
|
||||
regaparams_t params, int eflags, int *match_end_ofs);
|
||||
#endif /* TRE_APPROX */
|
||||
|
||||
#endif /* TRE_INTERNAL_H */
|
||||
|
||||
/* EOF */
|
||||
Vendored
+676
@@ -0,0 +1,676 @@
|
||||
/*
|
||||
tre-match-backtrack.c - TRE backtracking regex matching engine
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
This matcher is for regexps that use back referencing. Regexp matching
|
||||
with back referencing is an NP-complete problem on the number of back
|
||||
references. The easiest way to match them is to use a backtracking
|
||||
routine which basically goes through all possible paths in the TNFA
|
||||
and chooses the one which results in the best (leftmost and longest)
|
||||
match. This can be spectacularly expensive and may run out of stack
|
||||
space, but there really is no better known generic algorithm. Quoting
|
||||
Henry Spencer from comp.compilers:
|
||||
<URL: http://compilers.iecc.com/comparch/article/93-03-102>
|
||||
|
||||
POSIX.2 REs require longest match, which is really exciting to
|
||||
implement since the obsolete ("basic") variant also includes
|
||||
\<digit>. I haven't found a better way of tackling this than doing
|
||||
a preliminary match using a DFA (or simulation) on a modified RE
|
||||
that just replicates subREs for \<digit>, and then doing a
|
||||
backtracking match to determine whether the subRE matches were
|
||||
right. This can be rather slow, but I console myself with the
|
||||
thought that people who use \<digit> deserve very slow execution.
|
||||
(Pun unintentional but very appropriate.)
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
/* AIX requires this to be the first thing in the file. */
|
||||
#ifndef __GNUC__
|
||||
# if HAVE_ALLOCA_H
|
||||
# include <alloca.h>
|
||||
# else
|
||||
# ifdef _AIX
|
||||
#pragma alloca
|
||||
# else
|
||||
# ifndef alloca /* predefined by HP cc +Olibcalls */
|
||||
char *alloca ();
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
#endif /* TRE_USE_ALLOCA */
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
#ifdef HAVE_WCTYPE_H
|
||||
#include <wctype.h>
|
||||
#endif /* HAVE_WCTYPE_H */
|
||||
#ifndef TRE_WCHAR
|
||||
#include <ctype.h>
|
||||
#endif /* !TRE_WCHAR */
|
||||
#ifdef HAVE_MALLOC_H
|
||||
#include <malloc.h>
|
||||
#endif /* HAVE_MALLOC_H */
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "tre-mem.h"
|
||||
#include "tre-match-utils.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
typedef struct {
|
||||
int pos;
|
||||
const char *str_byte;
|
||||
#ifdef TRE_WCHAR
|
||||
const wchar_t *str_wide;
|
||||
#endif /* TRE_WCHAR */
|
||||
tre_tnfa_transition_t *state;
|
||||
int state_id;
|
||||
int next_c;
|
||||
int *tags;
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate_t mbstate;
|
||||
#endif /* TRE_MBSTATE */
|
||||
} tre_backtrack_item_t;
|
||||
|
||||
typedef struct tre_backtrack_struct {
|
||||
tre_backtrack_item_t item;
|
||||
struct tre_backtrack_struct *prev;
|
||||
struct tre_backtrack_struct *next;
|
||||
} *tre_backtrack_t;
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
#define BT_STACK_WIDE_IN(_str_wide) stack->item.str_wide = (_str_wide)
|
||||
#define BT_STACK_WIDE_OUT (str_wide) = stack->item.str_wide
|
||||
#else /* !TRE_WCHAR */
|
||||
#define BT_STACK_WIDE_IN(_str_wide)
|
||||
#define BT_STACK_WIDE_OUT
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
#ifdef TRE_MBSTATE
|
||||
#define BT_STACK_MBSTATE_IN stack->item.mbstate = (mbstate)
|
||||
#define BT_STACK_MBSTATE_OUT (mbstate) = stack->item.mbstate
|
||||
#else /* !TRE_MBSTATE */
|
||||
#define BT_STACK_MBSTATE_IN
|
||||
#define BT_STACK_MBSTATE_OUT
|
||||
#endif /* !TRE_MBSTATE */
|
||||
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
#define tre_bt_mem_new tre_mem_newa
|
||||
#define tre_bt_mem_alloc tre_mem_alloca
|
||||
#define tre_bt_mem_destroy(obj) do { } while (0)
|
||||
#define xafree(obj) do { } while (0) /* do nothing, obj was obtained with alloca() */
|
||||
#else /* !TRE_USE_ALLOCA */
|
||||
#define tre_bt_mem_new tre_mem_new
|
||||
#define tre_bt_mem_alloc tre_mem_alloc
|
||||
#define tre_bt_mem_destroy tre_mem_destroy
|
||||
#define xafree(obj) xfree(obj)
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
|
||||
|
||||
#define BT_STACK_PUSH(_pos, _str_byte, _str_wide, _state, _state_id, _next_c, _tags, _mbstate) \
|
||||
do \
|
||||
{ \
|
||||
int i; \
|
||||
if (!stack->next) \
|
||||
{ \
|
||||
tre_backtrack_t s; \
|
||||
s = tre_bt_mem_alloc(mem, sizeof(*s)); \
|
||||
if (!s) \
|
||||
{ \
|
||||
tre_bt_mem_destroy(mem); \
|
||||
if (tags) \
|
||||
xafree(tags); \
|
||||
if (pmatch) \
|
||||
xafree(pmatch); \
|
||||
if (states_seen) \
|
||||
xafree(states_seen); \
|
||||
return REG_ESPACE; \
|
||||
} \
|
||||
s->prev = stack; \
|
||||
s->next = NULL; \
|
||||
s->item.tags = tre_bt_mem_alloc(mem, \
|
||||
sizeof(*tags) * tnfa->num_tags); \
|
||||
if (!s->item.tags) \
|
||||
{ \
|
||||
tre_bt_mem_destroy(mem); \
|
||||
if (tags) \
|
||||
xafree(tags); \
|
||||
if (pmatch) \
|
||||
xafree(pmatch); \
|
||||
if (states_seen) \
|
||||
xafree(states_seen); \
|
||||
return REG_ESPACE; \
|
||||
} \
|
||||
stack->next = s; \
|
||||
stack = s; \
|
||||
} \
|
||||
else \
|
||||
stack = stack->next; \
|
||||
stack->item.pos = (_pos); \
|
||||
stack->item.str_byte = (_str_byte); \
|
||||
BT_STACK_WIDE_IN(_str_wide); \
|
||||
stack->item.state = (_state); \
|
||||
stack->item.state_id = (_state_id); \
|
||||
stack->item.next_c = (_next_c); \
|
||||
for (i = 0; i < tnfa->num_tags; i++) \
|
||||
stack->item.tags[i] = (_tags)[i]; \
|
||||
BT_STACK_MBSTATE_IN; \
|
||||
} \
|
||||
while (/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#define BT_STACK_POP() \
|
||||
do \
|
||||
{ \
|
||||
int i; \
|
||||
assert(stack->prev); \
|
||||
pos = stack->item.pos; \
|
||||
if (type == STR_USER) \
|
||||
str_source->rewind(pos + pos_add_next, str_source->context); \
|
||||
str_byte = stack->item.str_byte; \
|
||||
BT_STACK_WIDE_OUT; \
|
||||
state = stack->item.state; \
|
||||
next_c = (tre_char_t) stack->item.next_c; \
|
||||
for (i = 0; i < tnfa->num_tags; i++) \
|
||||
tags[i] = stack->item.tags[i]; \
|
||||
BT_STACK_MBSTATE_OUT; \
|
||||
stack = stack->prev; \
|
||||
} \
|
||||
while (/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#undef MIN
|
||||
#define MIN(a, b) ((a) <= (b) ? (a) : (b))
|
||||
|
||||
reg_errcode_t
|
||||
tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *string,
|
||||
ssize_t len, tre_str_type_t type, int *match_tags,
|
||||
int eflags, int *match_end_ofs)
|
||||
{
|
||||
/* State variables required by GET_NEXT_WCHAR. */
|
||||
tre_char_t prev_c = 0, next_c = 0;
|
||||
const char *str_byte = string;
|
||||
ssize_t pos = 0;
|
||||
unsigned int pos_add_next = 1;
|
||||
#ifdef TRE_WCHAR
|
||||
const wchar_t *str_wide = string;
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate_t mbstate;
|
||||
#endif /* TRE_MBSTATE */
|
||||
#endif /* TRE_WCHAR */
|
||||
int reg_notbol = eflags & REG_NOTBOL;
|
||||
int reg_noteol = eflags & REG_NOTEOL;
|
||||
int reg_newline = tnfa->cflags & REG_NEWLINE;
|
||||
int str_user_end = 0;
|
||||
|
||||
/* These are used to remember the necessary values of the above
|
||||
variables to return to the position where the current search
|
||||
started from. */
|
||||
int next_c_start;
|
||||
const char *str_byte_start;
|
||||
int pos_start = -1;
|
||||
#ifdef TRE_WCHAR
|
||||
const wchar_t *str_wide_start;
|
||||
#endif /* TRE_WCHAR */
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate_t mbstate_start;
|
||||
#endif /* TRE_MBSTATE */
|
||||
reg_errcode_t ret;
|
||||
|
||||
/* End offset of best match so far, or -1 if no match found yet. */
|
||||
int match_eo = -1;
|
||||
/* Tag arrays. */
|
||||
int *next_tags, *tags = NULL;
|
||||
/* Current TNFA state. */
|
||||
tre_tnfa_transition_t *state;
|
||||
int *states_seen = NULL;
|
||||
|
||||
/* Memory allocator to for allocating the backtracking stack. */
|
||||
tre_mem_t mem = tre_bt_mem_new();
|
||||
|
||||
/* The backtracking stack. */
|
||||
tre_backtrack_t stack;
|
||||
|
||||
tre_tnfa_transition_t *trans_i;
|
||||
regmatch_t *pmatch = NULL;
|
||||
|
||||
/*
|
||||
* TRE internals tend to use int instead of size_t for positions or
|
||||
* lengths and don't check for overflow. This will take time to fix
|
||||
* properly. In the meantime, simply limit the input to what we can
|
||||
* handle.
|
||||
*/
|
||||
if (len > TRE_MAX_STRING)
|
||||
len = TRE_MAX_STRING;
|
||||
|
||||
#ifdef TRE_MBSTATE
|
||||
memset(&mbstate, '\0', sizeof(mbstate));
|
||||
#endif /* TRE_MBSTATE */
|
||||
|
||||
if (!mem)
|
||||
return REG_ESPACE;
|
||||
stack = tre_bt_mem_alloc(mem, sizeof(*stack));
|
||||
if (!stack)
|
||||
{
|
||||
ret = REG_ESPACE;
|
||||
goto error_exit;
|
||||
}
|
||||
stack->prev = NULL;
|
||||
stack->next = NULL;
|
||||
|
||||
DPRINT(("tnfa_execute_backtrack, input type %d\n", type));
|
||||
DPRINT(("len = %zd\n", len));
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
tags = alloca(sizeof(*tags) * tnfa->num_tags);
|
||||
pmatch = alloca(sizeof(*pmatch) * tnfa->num_submatches);
|
||||
states_seen = alloca(sizeof(*states_seen) * tnfa->num_states);
|
||||
#else /* !TRE_USE_ALLOCA */
|
||||
if (tnfa->num_tags)
|
||||
{
|
||||
tags = xmalloc(sizeof(*tags) * tnfa->num_tags);
|
||||
if (!tags)
|
||||
{
|
||||
ret = REG_ESPACE;
|
||||
goto error_exit;
|
||||
}
|
||||
}
|
||||
if (tnfa->num_submatches)
|
||||
{
|
||||
pmatch = xmalloc(sizeof(*pmatch) * tnfa->num_submatches);
|
||||
if (!pmatch)
|
||||
{
|
||||
ret = REG_ESPACE;
|
||||
goto error_exit;
|
||||
}
|
||||
}
|
||||
if (tnfa->num_states)
|
||||
{
|
||||
states_seen = xmalloc(sizeof(*states_seen) * tnfa->num_states);
|
||||
if (!states_seen)
|
||||
{
|
||||
ret = REG_ESPACE;
|
||||
goto error_exit;
|
||||
}
|
||||
}
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
|
||||
retry:
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < tnfa->num_tags; i++)
|
||||
{
|
||||
tags[i] = -1;
|
||||
if (match_tags)
|
||||
match_tags[i] = -1;
|
||||
}
|
||||
for (i = 0; i < tnfa->num_states; i++)
|
||||
states_seen[i] = 0;
|
||||
}
|
||||
|
||||
state = NULL;
|
||||
pos = pos_start;
|
||||
if (type == STR_USER)
|
||||
str_source->rewind(pos + pos_add_next, str_source->context);
|
||||
GET_NEXT_WCHAR();
|
||||
pos_start = pos;
|
||||
next_c_start = next_c;
|
||||
str_byte_start = str_byte;
|
||||
#ifdef TRE_WCHAR
|
||||
str_wide_start = str_wide;
|
||||
#endif /* TRE_WCHAR */
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate_start = mbstate;
|
||||
#endif /* TRE_MBSTATE */
|
||||
|
||||
/* Handle initial states. */
|
||||
next_tags = NULL;
|
||||
for (trans_i = tnfa->initial; trans_i->state; trans_i++)
|
||||
{
|
||||
DPRINT(("> init %p, prev_c %lc\n", trans_i->state, (tre_cint_t)prev_c));
|
||||
if (trans_i->assertions && CHECK_ASSERTIONS(trans_i->assertions))
|
||||
{
|
||||
DPRINT(("assert failed\n"));
|
||||
continue;
|
||||
}
|
||||
if (state == NULL)
|
||||
{
|
||||
/* Start from this state. */
|
||||
state = trans_i->state;
|
||||
next_tags = trans_i->tags;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Backtrack to this state. */
|
||||
DPRINT(("saving state %d for backtracking\n", trans_i->state_id));
|
||||
BT_STACK_PUSH(pos, str_byte, str_wide, trans_i->state,
|
||||
trans_i->state_id, next_c, tags, mbstate);
|
||||
{
|
||||
int *tmp = trans_i->tags;
|
||||
if (tmp)
|
||||
while (*tmp >= 0)
|
||||
stack->item.tags[*tmp++] = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (next_tags)
|
||||
for (; *next_tags >= 0; next_tags++)
|
||||
tags[*next_tags] = pos;
|
||||
|
||||
|
||||
DPRINT(("entering match loop, pos %zd, str_byte %p\n", pos, str_byte));
|
||||
DPRINT(("pos:chr/code | state and tags\n"));
|
||||
DPRINT(("-------------+------------------------------------------------\n"));
|
||||
|
||||
if (state == NULL)
|
||||
goto backtrack;
|
||||
|
||||
while (/*CONSTCOND*/(void)1,1)
|
||||
{
|
||||
tre_tnfa_transition_t *next_state;
|
||||
int empty_br_match;
|
||||
|
||||
DPRINT(("start loop\n"));
|
||||
if (state == tnfa->final)
|
||||
{
|
||||
DPRINT((" match found, %d %zd\n", match_eo, pos));
|
||||
if (match_eo < pos
|
||||
|| (match_eo == pos
|
||||
&& match_tags
|
||||
&& tre_tag_order(tnfa->num_tags, tnfa->tag_directions,
|
||||
tags, match_tags)))
|
||||
{
|
||||
int i;
|
||||
/* This match wins the previous match. */
|
||||
DPRINT((" win previous\n"));
|
||||
match_eo = pos;
|
||||
if (match_tags)
|
||||
for (i = 0; i < tnfa->num_tags; i++)
|
||||
match_tags[i] = tags[i];
|
||||
}
|
||||
/* Our TNFAs never have transitions leaving from the final state,
|
||||
so we jump right to backtracking. */
|
||||
goto backtrack;
|
||||
}
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
DPRINT(("%3zd:%2lc/%05d | %p ", pos, (tre_cint_t)next_c, (int)next_c,
|
||||
state));
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < tnfa->num_tags; i++)
|
||||
DPRINT(("%d%s", tags[i], i < tnfa->num_tags - 1 ? ", " : ""));
|
||||
DPRINT(("\n"));
|
||||
}
|
||||
#endif /* TRE_DEBUG */
|
||||
|
||||
/* Go to the next character in the input string. */
|
||||
empty_br_match = 0;
|
||||
trans_i = state;
|
||||
if (trans_i->state && trans_i->assertions & ASSERT_BACKREF)
|
||||
{
|
||||
/* This is a back reference state. All transitions leaving from
|
||||
this state have the same back reference "assertion". Instead
|
||||
of reading the next character, we match the back reference. */
|
||||
int so, eo, bt = trans_i->u.backref;
|
||||
int bt_len;
|
||||
int result;
|
||||
|
||||
DPRINT((" should match back reference %d\n", bt));
|
||||
/* Get the substring we need to match against. Remember to
|
||||
turn off REG_NOSUB temporarily. */
|
||||
tre_fill_pmatch(bt + 1, pmatch, tnfa->cflags & ~REG_NOSUB,
|
||||
tnfa, tags, pos);
|
||||
so = pmatch[bt].rm_so;
|
||||
eo = pmatch[bt].rm_eo;
|
||||
bt_len = eo - so;
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
{
|
||||
int slen;
|
||||
if (len < 0)
|
||||
slen = bt_len;
|
||||
else
|
||||
slen = MIN(bt_len, len - pos);
|
||||
|
||||
if (type == STR_BYTE)
|
||||
{
|
||||
DPRINT((" substring (len %d) is [%d, %d[: '%.*s'\n",
|
||||
bt_len, so, eo, bt_len, (char*)string + so));
|
||||
DPRINT((" current string is '%.*s'\n", slen, str_byte - 1));
|
||||
}
|
||||
#ifdef TRE_WCHAR
|
||||
else if (type == STR_WIDE)
|
||||
{
|
||||
DPRINT((" substring (len %d) is [%d, %d[: '%.*" STRF "'\n",
|
||||
bt_len, so, eo, bt_len, (wchar_t*)string + so));
|
||||
DPRINT((" current string is '%.*" STRF "'\n",
|
||||
slen, str_wide - 1));
|
||||
}
|
||||
#endif /* TRE_WCHAR */
|
||||
}
|
||||
#endif
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
if (type == STR_USER)
|
||||
result = str_source->compare((unsigned)so, (unsigned)pos,
|
||||
(unsigned)bt_len,
|
||||
str_source->context);
|
||||
#ifdef TRE_WCHAR
|
||||
else if (type == STR_WIDE)
|
||||
result = wcsncmp((const wchar_t*)string + so, str_wide - 1,
|
||||
(size_t)bt_len);
|
||||
#endif /* TRE_WCHAR */
|
||||
else
|
||||
result = strncmp((const char*)string + so, str_byte - 1,
|
||||
(size_t)bt_len);
|
||||
}
|
||||
else if (len - pos < bt_len)
|
||||
result = 1;
|
||||
#ifdef TRE_WCHAR
|
||||
else if (type == STR_WIDE)
|
||||
result = wmemcmp((const wchar_t*)string + so, str_wide - 1,
|
||||
(size_t)bt_len);
|
||||
#endif /* TRE_WCHAR */
|
||||
else
|
||||
result = memcmp((const char*)string + so, str_byte - 1,
|
||||
(size_t)bt_len);
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
/* Back reference matched. Check for infinite loop. */
|
||||
if (bt_len == 0)
|
||||
empty_br_match = 1;
|
||||
if (empty_br_match && states_seen[trans_i->state_id])
|
||||
{
|
||||
DPRINT((" avoid loop\n"));
|
||||
goto backtrack;
|
||||
}
|
||||
|
||||
states_seen[trans_i->state_id] = empty_br_match;
|
||||
|
||||
/* Advance in input string and resync `prev_c', `next_c'
|
||||
and pos. */
|
||||
DPRINT((" back reference matched\n"));
|
||||
str_byte += bt_len - 1;
|
||||
#ifdef TRE_WCHAR
|
||||
str_wide += bt_len - 1;
|
||||
#endif /* TRE_WCHAR */
|
||||
pos += bt_len - 1;
|
||||
GET_NEXT_WCHAR();
|
||||
DPRINT((" pos now %zd\n", pos));
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINT((" back reference did not match\n"));
|
||||
goto backtrack;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Check for end of string. */
|
||||
if (len < 0)
|
||||
{
|
||||
if (type == STR_USER)
|
||||
{
|
||||
if (str_user_end)
|
||||
goto backtrack;
|
||||
}
|
||||
else if (next_c == L'\0' || pos >= TRE_MAX_STRING)
|
||||
goto backtrack;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pos >= len)
|
||||
goto backtrack;
|
||||
}
|
||||
|
||||
/* Read the next character. */
|
||||
GET_NEXT_WCHAR();
|
||||
}
|
||||
|
||||
next_state = NULL;
|
||||
for (trans_i = state; trans_i->state; trans_i++)
|
||||
{
|
||||
DPRINT((" transition %d-%d (%c-%c) %d to %d\n",
|
||||
trans_i->code_min, trans_i->code_max,
|
||||
trans_i->code_min, trans_i->code_max,
|
||||
trans_i->assertions, trans_i->state_id));
|
||||
if (trans_i->code_min <= (tre_cint_t)prev_c
|
||||
&& trans_i->code_max >= (tre_cint_t)prev_c)
|
||||
{
|
||||
if (trans_i->assertions
|
||||
&& (CHECK_ASSERTIONS(trans_i->assertions)
|
||||
|| CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)))
|
||||
{
|
||||
DPRINT((" assertion failed\n"));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (next_state == NULL)
|
||||
{
|
||||
/* First matching transition. */
|
||||
DPRINT((" Next state is %d\n", trans_i->state_id));
|
||||
next_state = trans_i->state;
|
||||
next_tags = trans_i->tags;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Second matching transition. We may need to backtrack here
|
||||
to take this transition instead of the first one, so we
|
||||
push this transition in the backtracking stack so we can
|
||||
jump back here if needed. */
|
||||
DPRINT((" saving state %d for backtracking\n",
|
||||
trans_i->state_id));
|
||||
BT_STACK_PUSH(pos, str_byte, str_wide, trans_i->state,
|
||||
trans_i->state_id, next_c, tags, mbstate);
|
||||
{
|
||||
int *tmp;
|
||||
for (tmp = trans_i->tags; tmp && *tmp >= 0; tmp++)
|
||||
stack->item.tags[*tmp] = pos;
|
||||
}
|
||||
#if 0 /* XXX - it's important not to look at all transitions here to keep
|
||||
the stack small! */
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (next_state != NULL)
|
||||
{
|
||||
/* Matching transitions were found. Take the first one. */
|
||||
state = next_state;
|
||||
|
||||
/* Update the tag values. */
|
||||
if (next_tags)
|
||||
while (*next_tags >= 0)
|
||||
tags[*next_tags++] = pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
backtrack:
|
||||
/* A matching transition was not found. Try to backtrack. */
|
||||
if (stack->prev)
|
||||
{
|
||||
DPRINT((" backtracking\n"));
|
||||
if (stack->item.state->assertions & ASSERT_BACKREF)
|
||||
{
|
||||
DPRINT((" states_seen[%d] = 0\n",
|
||||
stack->item.state_id));
|
||||
states_seen[stack->item.state_id] = 0;
|
||||
}
|
||||
|
||||
BT_STACK_POP();
|
||||
}
|
||||
else if (match_eo < 0)
|
||||
{
|
||||
/* Try starting from a later position in the input string. */
|
||||
/* Check for end of string. */
|
||||
if (len < 0)
|
||||
{
|
||||
if (next_c_start == L'\0' || pos_start >= TRE_MAX_STRING)
|
||||
{
|
||||
DPRINT(("end of string.\n"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pos_start >= len)
|
||||
{
|
||||
DPRINT(("end of string.\n"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
DPRINT(("restarting from next start position\n"));
|
||||
next_c = (tre_char_t) next_c_start;
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate = mbstate_start;
|
||||
#endif /* TRE_MBSTATE */
|
||||
str_byte = str_byte_start;
|
||||
#ifdef TRE_WCHAR
|
||||
str_wide = str_wide_start;
|
||||
#endif /* TRE_WCHAR */
|
||||
goto retry;
|
||||
}
|
||||
else
|
||||
{
|
||||
DPRINT(("finished\n"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret = match_eo >= 0 ? REG_OK : REG_NOMATCH;
|
||||
*match_end_ofs = match_eo;
|
||||
|
||||
error_exit:
|
||||
tre_bt_mem_destroy(mem);
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (tags)
|
||||
xafree(tags);
|
||||
if (pmatch)
|
||||
xafree(pmatch);
|
||||
if (states_seen)
|
||||
xafree(states_seen);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
|
||||
return ret;
|
||||
}
|
||||
Vendored
+538
@@ -0,0 +1,538 @@
|
||||
/*
|
||||
tre-match-parallel.c - TRE parallel regex matching engine
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
This algorithm searches for matches basically by reading characters
|
||||
in the searched string one by one, starting at the beginning. All
|
||||
matching paths in the TNFA are traversed in parallel. When two or
|
||||
more paths reach the same state, exactly one is chosen according to
|
||||
tag ordering rules; if returning submatches is not required it does
|
||||
not matter which path is chosen.
|
||||
|
||||
The worst case time required for finding the leftmost and longest
|
||||
match, or determining that there is no match, is always linearly
|
||||
dependent on the length of the text being searched.
|
||||
|
||||
This algorithm cannot handle TNFAs with back referencing nodes.
|
||||
See `tre-match-backtrack.c'.
|
||||
*/
|
||||
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
/* AIX requires this to be the first thing in the file. */
|
||||
#ifndef __GNUC__
|
||||
# if HAVE_ALLOCA_H
|
||||
# include <alloca.h>
|
||||
# else
|
||||
# ifdef _AIX
|
||||
#pragma alloca
|
||||
# else
|
||||
# ifndef alloca /* predefined by HP cc +Olibcalls */
|
||||
char *alloca ();
|
||||
# endif
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
#endif /* TRE_USE_ALLOCA */
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
#ifdef HAVE_WCTYPE_H
|
||||
#include <wctype.h>
|
||||
#endif /* HAVE_WCTYPE_H */
|
||||
#ifndef TRE_WCHAR
|
||||
#include <ctype.h>
|
||||
#endif /* !TRE_WCHAR */
|
||||
#ifdef HAVE_MALLOC_H
|
||||
#include <malloc.h>
|
||||
#endif /* HAVE_MALLOC_H */
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "tre-match-utils.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
|
||||
|
||||
typedef struct {
|
||||
tre_tnfa_transition_t *state;
|
||||
int *tags;
|
||||
} tre_tnfa_reach_t;
|
||||
|
||||
typedef struct {
|
||||
int pos;
|
||||
int **tags;
|
||||
} tre_reach_pos_t;
|
||||
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
static void
|
||||
tre_print_reach(const tre_tnfa_reach_t *reach, int num_tags)
|
||||
{
|
||||
int i;
|
||||
|
||||
while (reach->state != NULL)
|
||||
{
|
||||
DPRINT((" %p", (void *)reach->state));
|
||||
if (num_tags > 0)
|
||||
{
|
||||
DPRINT(("/"));
|
||||
for (i = 0; i < num_tags; i++)
|
||||
{
|
||||
DPRINT(("%d:%d", i, reach->tags[i]));
|
||||
if (i < (num_tags-1))
|
||||
DPRINT((","));
|
||||
}
|
||||
}
|
||||
reach++;
|
||||
}
|
||||
DPRINT(("\n"));
|
||||
|
||||
}
|
||||
#endif /* TRE_DEBUG */
|
||||
|
||||
reg_errcode_t
|
||||
tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *string, ssize_t len,
|
||||
tre_str_type_t type, int *match_tags, int eflags,
|
||||
int *match_end_ofs)
|
||||
{
|
||||
/* State variables required by GET_NEXT_WCHAR. */
|
||||
tre_char_t prev_c = 0, next_c = 0;
|
||||
const char *str_byte = string;
|
||||
ssize_t pos = -1;
|
||||
unsigned int pos_add_next = 1;
|
||||
#ifdef TRE_WCHAR
|
||||
const wchar_t *str_wide = string;
|
||||
#ifdef TRE_MBSTATE
|
||||
mbstate_t mbstate;
|
||||
#endif /* TRE_MBSTATE */
|
||||
#endif /* TRE_WCHAR */
|
||||
reg_errcode_t ret;
|
||||
int reg_notbol = eflags & REG_NOTBOL;
|
||||
int reg_noteol = eflags & REG_NOTEOL;
|
||||
int reg_newline = tnfa->cflags & REG_NEWLINE;
|
||||
int str_user_end = 0;
|
||||
|
||||
char *buf;
|
||||
tre_tnfa_transition_t *trans_i;
|
||||
tre_tnfa_reach_t *reach, *reach_next, *reach_i, *reach_next_i;
|
||||
tre_reach_pos_t *reach_pos;
|
||||
int *tag_i;
|
||||
int num_tags, i;
|
||||
|
||||
int match_eo = -1; /* end offset of match (-1 if no match found yet) */
|
||||
int new_match = 0;
|
||||
int *tmp_tags = NULL;
|
||||
int *tmp_iptr;
|
||||
|
||||
/*
|
||||
* TRE internals tend to use int instead of size_t for positions or
|
||||
* lengths and don't check for overflow. This will take time to fix
|
||||
* properly. In the meantime, simply limit the input to what we can
|
||||
* handle.
|
||||
*/
|
||||
if (len > TRE_MAX_STRING)
|
||||
len = TRE_MAX_STRING;
|
||||
|
||||
#ifdef TRE_MBSTATE
|
||||
memset(&mbstate, '\0', sizeof(mbstate));
|
||||
#endif /* TRE_MBSTATE */
|
||||
|
||||
DPRINT(("tre_tnfa_run_parallel, input type %d\n", type));
|
||||
|
||||
if (!match_tags)
|
||||
num_tags = 0;
|
||||
else
|
||||
num_tags = tnfa->num_tags;
|
||||
|
||||
/* Allocate memory for temporary data required for matching. This needs to
|
||||
be done for every matching operation to be thread safe. This allocates
|
||||
everything in a single large block from the stack frame using alloca()
|
||||
or with malloc() if alloca is unavailable. */
|
||||
{
|
||||
size_t tbytes, rbytes, pbytes, xbytes, total_bytes;
|
||||
size_t num_states = (size_t)tnfa->num_states;
|
||||
size_t state_tag_bytes, reach_bytes;
|
||||
size_t padding = (sizeof(long) - 1) * 4;
|
||||
char *tmp_buf;
|
||||
|
||||
if (num_states > SIZE_MAX / sizeof(*reach_pos))
|
||||
return REG_ESPACE;
|
||||
pbytes = sizeof(*reach_pos) * num_states;
|
||||
|
||||
if (num_states + 1 > SIZE_MAX / sizeof(*reach_next))
|
||||
return REG_ESPACE;
|
||||
rbytes = sizeof(*reach_next) * (num_states + 1);
|
||||
|
||||
if ((size_t)num_tags > SIZE_MAX / sizeof(*tmp_tags))
|
||||
return REG_ESPACE;
|
||||
tbytes = sizeof(*tmp_tags) * (size_t)num_tags;
|
||||
|
||||
if ((size_t)num_tags > SIZE_MAX / sizeof(int))
|
||||
return REG_ESPACE;
|
||||
xbytes = sizeof(int) * (size_t)num_tags;
|
||||
|
||||
if (num_states > 0 && xbytes > SIZE_MAX / num_states)
|
||||
return REG_ESPACE;
|
||||
state_tag_bytes = xbytes * num_states;
|
||||
|
||||
if (rbytes > SIZE_MAX - state_tag_bytes)
|
||||
return REG_ESPACE;
|
||||
reach_bytes = rbytes + state_tag_bytes;
|
||||
|
||||
if (reach_bytes > (SIZE_MAX - padding - tbytes - pbytes) / 2)
|
||||
return REG_ESPACE;
|
||||
|
||||
/* Compute the length of the block we need. */
|
||||
total_bytes =
|
||||
padding + reach_bytes * 2 + tbytes + pbytes;
|
||||
|
||||
/* Allocate the memory. */
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
buf = alloca(total_bytes);
|
||||
#else /* !TRE_USE_ALLOCA */
|
||||
buf = xmalloc(total_bytes);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
if (buf == NULL)
|
||||
return REG_ESPACE;
|
||||
memset(buf, 0, total_bytes);
|
||||
|
||||
/* Get the various pointers within tmp_buf (properly aligned). */
|
||||
tmp_tags = (void *)buf;
|
||||
tmp_buf = buf + tbytes;
|
||||
tmp_buf += ALIGN(tmp_buf, long);
|
||||
reach_next = (void *)tmp_buf;
|
||||
tmp_buf += rbytes;
|
||||
tmp_buf += ALIGN(tmp_buf, long);
|
||||
reach = (void *)tmp_buf;
|
||||
tmp_buf += rbytes;
|
||||
tmp_buf += ALIGN(tmp_buf, long);
|
||||
reach_pos = (void *)tmp_buf;
|
||||
tmp_buf += pbytes;
|
||||
tmp_buf += ALIGN(tmp_buf, long);
|
||||
for (i = 0; i < tnfa->num_states; i++)
|
||||
{
|
||||
reach[i].tags = (void *)tmp_buf;
|
||||
tmp_buf += xbytes;
|
||||
reach_next[i].tags = (void *)tmp_buf;
|
||||
tmp_buf += xbytes;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < tnfa->num_states; i++)
|
||||
reach_pos[i].pos = -1;
|
||||
|
||||
/* If only one character can start a match, find it first. */
|
||||
if (tnfa->first_char >= 0 && type == STR_BYTE && str_byte)
|
||||
{
|
||||
const char *orig_str = str_byte;
|
||||
int first = tnfa->first_char;
|
||||
|
||||
if (len >= 0)
|
||||
str_byte = memchr(orig_str, first, (size_t)len);
|
||||
else
|
||||
str_byte = strchr(orig_str, first);
|
||||
if (str_byte == NULL)
|
||||
{
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (buf)
|
||||
xfree(buf);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
return REG_NOMATCH;
|
||||
}
|
||||
DPRINT(("skipped %lu chars\n", (unsigned long)(str_byte - orig_str)));
|
||||
if (str_byte >= orig_str + 1)
|
||||
prev_c = (unsigned char)*(str_byte - 1);
|
||||
next_c = (unsigned char)*str_byte;
|
||||
pos = str_byte - orig_str;
|
||||
if (len < 0 || pos < len)
|
||||
str_byte++;
|
||||
}
|
||||
else
|
||||
{
|
||||
GET_NEXT_WCHAR();
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* Skip over characters that cannot possibly be the first character
|
||||
of a match. */
|
||||
if (tnfa->firstpos_chars != NULL)
|
||||
{
|
||||
char *chars = tnfa->firstpos_chars;
|
||||
|
||||
if (len < 0)
|
||||
{
|
||||
const char *orig_str = str_byte;
|
||||
/* XXX - use strpbrk() and wcspbrk() because they might be
|
||||
optimized for the target architecture. Try also strcspn()
|
||||
and wcscspn() and compare the speeds. */
|
||||
while (next_c != L'\0' && !chars[next_c])
|
||||
{
|
||||
next_c = *str_byte++;
|
||||
}
|
||||
prev_c = *(str_byte - 2);
|
||||
pos += str_byte - orig_str;
|
||||
DPRINT(("skipped %d chars\n", str_byte - orig_str));
|
||||
}
|
||||
else
|
||||
{
|
||||
while (pos <= len && !chars[next_c])
|
||||
{
|
||||
prev_c = next_c;
|
||||
next_c = (unsigned char)(*str_byte++);
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
DPRINT(("length: %zd\n", len));
|
||||
DPRINT(("pos:chr/code | states and tags\n"));
|
||||
DPRINT(("-------------+------------------------------------------------\n"));
|
||||
|
||||
reach_next_i = reach_next;
|
||||
while (/*CONSTCOND*/(void)1,1)
|
||||
{
|
||||
/* If no match found yet, add the initial states to `reach_next'. */
|
||||
if (match_eo < 0)
|
||||
{
|
||||
DPRINT((" init >"));
|
||||
trans_i = tnfa->initial;
|
||||
while (trans_i->state != NULL)
|
||||
{
|
||||
if (reach_pos[trans_i->state_id].pos < pos)
|
||||
{
|
||||
if (trans_i->assertions
|
||||
&& CHECK_ASSERTIONS(trans_i->assertions))
|
||||
{
|
||||
DPRINT(("assertion failed\n"));
|
||||
trans_i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
DPRINT((" %p", (void *)trans_i->state));
|
||||
reach_next_i->state = trans_i->state;
|
||||
for (i = 0; i < num_tags; i++)
|
||||
reach_next_i->tags[i] = -1;
|
||||
tag_i = trans_i->tags;
|
||||
if (tag_i)
|
||||
while (*tag_i >= 0)
|
||||
{
|
||||
if (*tag_i < num_tags)
|
||||
reach_next_i->tags[*tag_i] = pos;
|
||||
tag_i++;
|
||||
}
|
||||
if (reach_next_i->state == tnfa->final)
|
||||
{
|
||||
DPRINT((" found empty match\n"));
|
||||
match_eo = pos;
|
||||
new_match = 1;
|
||||
for (i = 0; i < num_tags; i++)
|
||||
match_tags[i] = reach_next_i->tags[i];
|
||||
}
|
||||
reach_pos[trans_i->state_id].pos = pos;
|
||||
reach_pos[trans_i->state_id].tags = &reach_next_i->tags;
|
||||
reach_next_i++;
|
||||
}
|
||||
trans_i++;
|
||||
}
|
||||
DPRINT(("\n"));
|
||||
reach_next_i->state = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (num_tags == 0 || reach_next_i == reach_next)
|
||||
/* We have found a match. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check for end of string. */
|
||||
if (len < 0)
|
||||
{
|
||||
if (type == STR_USER)
|
||||
{
|
||||
if (str_user_end)
|
||||
break;
|
||||
}
|
||||
else if (next_c == L'\0' || pos >= TRE_MAX_STRING)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pos >= len)
|
||||
break;
|
||||
}
|
||||
|
||||
GET_NEXT_WCHAR();
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
DPRINT(("%3zd:%2lc/%05d |", pos - 1, (tre_cint_t)prev_c, (int)prev_c));
|
||||
tre_print_reach(reach_next, num_tags);
|
||||
DPRINT(("%3zd:%2lc/%05d |", pos, (tre_cint_t)next_c, (int)next_c));
|
||||
tre_print_reach(reach_next, num_tags);
|
||||
#endif /* TRE_DEBUG */
|
||||
|
||||
/* Swap `reach' and `reach_next'. */
|
||||
reach_i = reach;
|
||||
reach = reach_next;
|
||||
reach_next = reach_i;
|
||||
|
||||
/* For each state in `reach', weed out states that don't fulfill the
|
||||
minimal matching conditions. */
|
||||
if (tnfa->num_minimals && new_match)
|
||||
{
|
||||
new_match = 0;
|
||||
reach_next_i = reach_next;
|
||||
for (reach_i = reach; reach_i->state; reach_i++)
|
||||
{
|
||||
int skip = 0;
|
||||
for (i = 0; tnfa->minimal_tags[i] >= 0; i += 2)
|
||||
{
|
||||
int end = tnfa->minimal_tags[i];
|
||||
int start = tnfa->minimal_tags[i + 1];
|
||||
DPRINT((" Minimal start %d, end %d\n", start, end));
|
||||
if (end >= num_tags)
|
||||
{
|
||||
DPRINT((" Throwing %p out.\n", reach_i->state));
|
||||
skip = 1;
|
||||
break;
|
||||
}
|
||||
else if (reach_i->tags[start] == match_tags[start]
|
||||
&& reach_i->tags[end] < match_tags[end])
|
||||
{
|
||||
DPRINT((" Throwing %p out because t%d < %d\n",
|
||||
reach_i->state, end, match_tags[end]));
|
||||
skip = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!skip)
|
||||
{
|
||||
reach_next_i->state = reach_i->state;
|
||||
tmp_iptr = reach_next_i->tags;
|
||||
reach_next_i->tags = reach_i->tags;
|
||||
reach_i->tags = tmp_iptr;
|
||||
reach_next_i++;
|
||||
}
|
||||
}
|
||||
reach_next_i->state = NULL;
|
||||
|
||||
/* Swap `reach' and `reach_next'. */
|
||||
reach_i = reach;
|
||||
reach = reach_next;
|
||||
reach_next = reach_i;
|
||||
}
|
||||
|
||||
/* For each state in `reach' see if there is a transition leaving with
|
||||
the current input symbol to a state not yet in `reach_next', and
|
||||
add the destination states to `reach_next'. */
|
||||
reach_next_i = reach_next;
|
||||
for (reach_i = reach; reach_i->state; reach_i++)
|
||||
{
|
||||
for (trans_i = reach_i->state; trans_i->state; trans_i++)
|
||||
{
|
||||
/* Does this transition match the input symbol? */
|
||||
if (trans_i->code_min <= (tre_cint_t)prev_c &&
|
||||
trans_i->code_max >= (tre_cint_t)prev_c)
|
||||
{
|
||||
if (trans_i->assertions
|
||||
&& (CHECK_ASSERTIONS(trans_i->assertions)
|
||||
|| CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)))
|
||||
{
|
||||
DPRINT(("assertion failed\n"));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Compute the tags after this transition. */
|
||||
for (i = 0; i < num_tags; i++)
|
||||
tmp_tags[i] = reach_i->tags[i];
|
||||
tag_i = trans_i->tags;
|
||||
if (tag_i != NULL)
|
||||
while (*tag_i >= 0)
|
||||
{
|
||||
if (*tag_i < num_tags)
|
||||
tmp_tags[*tag_i] = pos;
|
||||
tag_i++;
|
||||
}
|
||||
|
||||
if (reach_pos[trans_i->state_id].pos < pos)
|
||||
{
|
||||
/* Found an unvisited node. */
|
||||
reach_next_i->state = trans_i->state;
|
||||
tmp_iptr = reach_next_i->tags;
|
||||
reach_next_i->tags = tmp_tags;
|
||||
tmp_tags = tmp_iptr;
|
||||
reach_pos[trans_i->state_id].pos = pos;
|
||||
reach_pos[trans_i->state_id].tags = &reach_next_i->tags;
|
||||
|
||||
if (reach_next_i->state == tnfa->final
|
||||
&& (match_eo == -1
|
||||
|| (num_tags > 0
|
||||
&& reach_next_i->tags[0] <= match_tags[0])))
|
||||
{
|
||||
DPRINT((" found match %p\n", trans_i->state));
|
||||
match_eo = pos;
|
||||
new_match = 1;
|
||||
for (i = 0; i < num_tags; i++)
|
||||
match_tags[i] = reach_next_i->tags[i];
|
||||
}
|
||||
reach_next_i++;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(reach_pos[trans_i->state_id].pos == pos);
|
||||
/* Another path has also reached this state. We choose
|
||||
the winner by examining the tag values for both
|
||||
paths. */
|
||||
if (tre_tag_order(num_tags, tnfa->tag_directions,
|
||||
tmp_tags,
|
||||
*reach_pos[trans_i->state_id].tags))
|
||||
{
|
||||
/* The new path wins. */
|
||||
tmp_iptr = *reach_pos[trans_i->state_id].tags;
|
||||
*reach_pos[trans_i->state_id].tags = tmp_tags;
|
||||
if (trans_i->state == tnfa->final)
|
||||
{
|
||||
DPRINT((" found better match\n"));
|
||||
match_eo = pos;
|
||||
new_match = 1;
|
||||
for (i = 0; i < num_tags; i++)
|
||||
match_tags[i] = tmp_tags[i];
|
||||
}
|
||||
tmp_tags = tmp_iptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
reach_next_i->state = NULL;
|
||||
}
|
||||
|
||||
DPRINT(("match end offset = %d\n", match_eo));
|
||||
|
||||
*match_end_ofs = match_eo;
|
||||
ret = match_eo >= 0 ? REG_OK : REG_NOMATCH;
|
||||
|
||||
#ifndef TRE_USE_ALLOCA
|
||||
if (buf)
|
||||
xfree(buf);
|
||||
#endif /* !TRE_USE_ALLOCA */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
Vendored
+215
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
tre-match-utils.h - TRE matcher helper definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#define str_source ((const tre_str_source*)string)
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
|
||||
#ifdef TRE_MULTIBYTE
|
||||
|
||||
/* Wide character and multibyte support. */
|
||||
|
||||
#define GET_NEXT_WCHAR() \
|
||||
do { \
|
||||
prev_c = next_c; \
|
||||
if (type == STR_BYTE) \
|
||||
{ \
|
||||
pos++; \
|
||||
if (len >= 0 && pos >= len) \
|
||||
next_c = '\0'; \
|
||||
else \
|
||||
next_c = (unsigned char)(*str_byte++); \
|
||||
} \
|
||||
else if (type == STR_WIDE) \
|
||||
{ \
|
||||
pos++; \
|
||||
if (len >= 0 && pos >= len) \
|
||||
next_c = L'\0'; \
|
||||
else \
|
||||
next_c = *str_wide++; \
|
||||
} \
|
||||
else if (type == STR_MBS) \
|
||||
{ \
|
||||
pos += pos_add_next; \
|
||||
if (str_byte == NULL) \
|
||||
next_c = L'\0'; \
|
||||
else \
|
||||
{ \
|
||||
size_t w; \
|
||||
size_t max; \
|
||||
if (len >= 0) \
|
||||
max = len - pos; \
|
||||
else \
|
||||
max = 32; \
|
||||
if (max <= 0) \
|
||||
{ \
|
||||
next_c = L'\0'; \
|
||||
pos_add_next = 1; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
w = tre_mbrtowc(&next_c, str_byte, (size_t)max, &mbstate); \
|
||||
if (w == (size_t)-1 || w == (size_t)-2) \
|
||||
return REG_NOMATCH; \
|
||||
if (w == 0 && len >= 0) \
|
||||
{ \
|
||||
pos_add_next = 1; \
|
||||
next_c = 0; \
|
||||
str_byte++; \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
pos_add_next = w; \
|
||||
str_byte += w; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
else if (type == STR_USER) \
|
||||
{ \
|
||||
pos += pos_add_next; \
|
||||
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
|
||||
str_source->context); \
|
||||
} \
|
||||
} while(/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#else /* !TRE_MULTIBYTE */
|
||||
|
||||
/* Wide character support, no multibyte support. */
|
||||
|
||||
#define GET_NEXT_WCHAR() \
|
||||
do { \
|
||||
prev_c = next_c; \
|
||||
if (type == STR_BYTE) \
|
||||
{ \
|
||||
pos++; \
|
||||
if (len >= 0 && pos >= len) \
|
||||
next_c = '\0'; \
|
||||
else \
|
||||
next_c = (unsigned char)(*str_byte++); \
|
||||
} \
|
||||
else if (type == STR_WIDE) \
|
||||
{ \
|
||||
pos++; \
|
||||
if (len >= 0 && pos >= len) \
|
||||
next_c = L'\0'; \
|
||||
else \
|
||||
next_c = *str_wide++; \
|
||||
} \
|
||||
else if (type == STR_USER) \
|
||||
{ \
|
||||
pos += pos_add_next; \
|
||||
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
|
||||
str_source->context); \
|
||||
} \
|
||||
} while(/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#endif /* !TRE_MULTIBYTE */
|
||||
|
||||
#else /* !TRE_WCHAR */
|
||||
|
||||
/* No wide character or multibyte support. */
|
||||
|
||||
#define GET_NEXT_WCHAR() \
|
||||
do { \
|
||||
prev_c = next_c; \
|
||||
if (type == STR_BYTE) \
|
||||
{ \
|
||||
pos++; \
|
||||
if (len >= 0 && pos >= len) \
|
||||
next_c = '\0'; \
|
||||
else \
|
||||
next_c = (unsigned char)(*str_byte++); \
|
||||
} \
|
||||
else if (type == STR_USER) \
|
||||
{ \
|
||||
pos += pos_add_next; \
|
||||
str_user_end = str_source->get_next_char(&next_c, &pos_add_next, \
|
||||
str_source->context); \
|
||||
} \
|
||||
} while(/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
|
||||
|
||||
#define IS_WORD_CHAR(c) ((c) == L'_' || tre_isalnum(c))
|
||||
|
||||
#define CHECK_ASSERTIONS(assertions) \
|
||||
(((assertions & ASSERT_AT_BOL) \
|
||||
&& (pos > 0 || reg_notbol) \
|
||||
&& (prev_c != L'\n' || !reg_newline)) \
|
||||
|| ((assertions & ASSERT_AT_EOL) \
|
||||
&& (next_c != L'\0' || reg_noteol) \
|
||||
&& (next_c != L'\n' || !reg_newline)) \
|
||||
|| ((assertions & ASSERT_AT_BOW) \
|
||||
&& (IS_WORD_CHAR(prev_c) || !IS_WORD_CHAR(next_c))) \
|
||||
|| ((assertions & ASSERT_AT_EOW) \
|
||||
&& (!IS_WORD_CHAR(prev_c) || IS_WORD_CHAR(next_c))) \
|
||||
|| ((assertions & ASSERT_AT_WB) \
|
||||
&& (pos != 0 && next_c != L'\0' \
|
||||
&& IS_WORD_CHAR(prev_c) == IS_WORD_CHAR(next_c))) \
|
||||
|| ((assertions & ASSERT_AT_WB_NEG) \
|
||||
&& (pos == 0 || next_c == L'\0' \
|
||||
|| IS_WORD_CHAR(prev_c) != IS_WORD_CHAR(next_c))))
|
||||
|
||||
#define CHECK_CHAR_CLASSES(trans_i, tnfa, eflags) \
|
||||
(((trans_i->assertions & ASSERT_CHAR_CLASS) \
|
||||
&& !(tnfa->cflags & REG_ICASE) \
|
||||
&& !tre_isctype((tre_cint_t)prev_c, trans_i->u.class)) \
|
||||
|| ((trans_i->assertions & ASSERT_CHAR_CLASS) \
|
||||
&& (tnfa->cflags & REG_ICASE) \
|
||||
&& !tre_isctype(tre_tolower((tre_cint_t)prev_c),trans_i->u.class) \
|
||||
&& !tre_isctype(tre_toupper((tre_cint_t)prev_c),trans_i->u.class)) \
|
||||
|| ((trans_i->assertions & ASSERT_CHAR_CLASS_NEG) \
|
||||
&& tre_neg_char_classes_match(trans_i->neg_classes,(tre_cint_t)prev_c,\
|
||||
tnfa->cflags & REG_ICASE)))
|
||||
|
||||
|
||||
|
||||
|
||||
/* Returns 1 if `t1' wins `t2', 0 otherwise. */
|
||||
inline static int
|
||||
tre_tag_order(int num_tags, tre_tag_direction_t *tag_directions,
|
||||
int *t1, int *t2)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < num_tags; i++)
|
||||
{
|
||||
if (tag_directions[i] == TRE_TAG_MINIMIZE)
|
||||
{
|
||||
if (t1[i] < t2[i])
|
||||
return 1;
|
||||
if (t1[i] > t2[i])
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (t1[i] > t2[i])
|
||||
return 1;
|
||||
if (t1[i] < t2[i])
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* assert(0);*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline static int
|
||||
tre_neg_char_classes_match(tre_ctype_t *classes, tre_cint_t wc, int icase)
|
||||
{
|
||||
DPRINT(("neg_char_classes_test: %p, %d, %d\n", classes, wc, icase));
|
||||
while (*classes != (tre_ctype_t)0)
|
||||
if ((!icase && tre_isctype(wc, *classes))
|
||||
|| (icase && (tre_isctype(tre_toupper(wc), *classes)
|
||||
|| tre_isctype(tre_tolower(wc), *classes))))
|
||||
return 1; /* Match. */
|
||||
else
|
||||
classes++;
|
||||
return 0; /* No match. */
|
||||
}
|
||||
Vendored
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
tre-mem.c - TRE memory allocator
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
This memory allocator is for allocating small memory blocks efficiently
|
||||
in terms of memory overhead and execution speed. The allocated blocks
|
||||
cannot be freed individually, only all at once. There can be multiple
|
||||
allocators, though.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "tre-mem.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
|
||||
/* Returns a new memory allocator or NULL if out of memory. */
|
||||
tre_mem_t
|
||||
tre_mem_new_impl(int provided, void *provided_block)
|
||||
{
|
||||
tre_mem_t mem;
|
||||
if (provided)
|
||||
{
|
||||
mem = provided_block;
|
||||
memset(mem, 0, sizeof(*mem));
|
||||
}
|
||||
else
|
||||
mem = xcalloc(1, sizeof(*mem));
|
||||
if (mem == NULL)
|
||||
return NULL;
|
||||
return mem;
|
||||
}
|
||||
|
||||
|
||||
/* Frees the memory allocator and all memory allocated with it. */
|
||||
void
|
||||
tre_mem_destroy(tre_mem_t mem)
|
||||
{
|
||||
tre_list_t *tmp, *l = mem->blocks;
|
||||
|
||||
while (l != NULL)
|
||||
{
|
||||
xfree(l->data);
|
||||
tmp = l->next;
|
||||
xfree(l);
|
||||
l = tmp;
|
||||
}
|
||||
xfree(mem);
|
||||
}
|
||||
|
||||
|
||||
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
|
||||
allocated block or NULL if an underlying malloc() failed. */
|
||||
void *
|
||||
tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block,
|
||||
int zero, size_t size)
|
||||
{
|
||||
void *ptr;
|
||||
|
||||
if (mem->failed)
|
||||
{
|
||||
DPRINT(("tre_mem_alloc: oops, called after failure?!\n"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#ifdef MALLOC_DEBUGGING
|
||||
if (!provided)
|
||||
{
|
||||
ptr = xmalloc(1);
|
||||
if (ptr == NULL)
|
||||
{
|
||||
DPRINT(("tre_mem_alloc: xmalloc forced failure\n"));
|
||||
mem->failed = 1;
|
||||
return NULL;
|
||||
}
|
||||
xfree(ptr);
|
||||
}
|
||||
#endif /* MALLOC_DEBUGGING */
|
||||
|
||||
if (mem->n < size)
|
||||
{
|
||||
/* We need more memory than is available in the current block.
|
||||
Allocate a new block. */
|
||||
tre_list_t *l;
|
||||
if (provided)
|
||||
{
|
||||
DPRINT(("tre_mem_alloc: using provided block\n"));
|
||||
if (provided_block == NULL)
|
||||
{
|
||||
DPRINT(("tre_mem_alloc: provided block was NULL\n"));
|
||||
mem->failed = 1;
|
||||
return NULL;
|
||||
}
|
||||
mem->ptr = provided_block;
|
||||
mem->n = TRE_MEM_BLOCK_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t block_size;
|
||||
if (size * 8 > TRE_MEM_BLOCK_SIZE)
|
||||
block_size = size * 8;
|
||||
else
|
||||
block_size = TRE_MEM_BLOCK_SIZE;
|
||||
DPRINT(("tre_mem_alloc: allocating new %zu byte block\n",
|
||||
block_size));
|
||||
l = xmalloc(sizeof(*l));
|
||||
if (l == NULL)
|
||||
{
|
||||
mem->failed = 1;
|
||||
return NULL;
|
||||
}
|
||||
l->data = xmalloc(block_size);
|
||||
if (l->data == NULL)
|
||||
{
|
||||
xfree(l);
|
||||
mem->failed = 1;
|
||||
return NULL;
|
||||
}
|
||||
l->next = NULL;
|
||||
if (mem->current != NULL)
|
||||
mem->current->next = l;
|
||||
if (mem->blocks == NULL)
|
||||
mem->blocks = l;
|
||||
mem->current = l;
|
||||
mem->ptr = l->data;
|
||||
mem->n = block_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sure the next pointer will be aligned. */
|
||||
size += ALIGN(mem->ptr + size, long);
|
||||
|
||||
/* Allocate from current block. */
|
||||
ptr = mem->ptr;
|
||||
mem->ptr += size;
|
||||
mem->n -= size;
|
||||
|
||||
/* Set to zero if needed. */
|
||||
if (zero)
|
||||
memset(ptr, 0, size);
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/* EOF */
|
||||
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
tre-mem.h - TRE memory allocator interface
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TRE_MEM_H
|
||||
#define TRE_MEM_H 1
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define TRE_MEM_BLOCK_SIZE 1024
|
||||
|
||||
typedef struct tre_list {
|
||||
void *data;
|
||||
struct tre_list *next;
|
||||
} tre_list_t;
|
||||
|
||||
typedef struct tre_mem_struct {
|
||||
tre_list_t *blocks;
|
||||
tre_list_t *current;
|
||||
char *ptr;
|
||||
size_t n;
|
||||
int failed;
|
||||
void **provided;
|
||||
} *tre_mem_t;
|
||||
|
||||
|
||||
tre_mem_t tre_mem_new_impl(int provided, void *provided_block);
|
||||
void *tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block,
|
||||
int zero, size_t size);
|
||||
|
||||
/* Returns a new memory allocator or NULL if out of memory. */
|
||||
#define tre_mem_new() tre_mem_new_impl(0, NULL)
|
||||
|
||||
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
|
||||
allocated block or NULL if an underlying malloc() failed. */
|
||||
#define tre_mem_alloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 0, size)
|
||||
|
||||
/* Allocates a block of `size' bytes from `mem'. Returns a pointer to the
|
||||
allocated block or NULL if an underlying malloc() failed. The memory
|
||||
is set to zero. */
|
||||
#define tre_mem_calloc(mem, size) tre_mem_alloc_impl(mem, 0, NULL, 1, size)
|
||||
|
||||
#ifdef TRE_USE_ALLOCA
|
||||
/* alloca() versions. Like above, but memory is allocated with alloca()
|
||||
instead of malloc(). */
|
||||
|
||||
#define tre_mem_newa() \
|
||||
tre_mem_new_impl(1, alloca(sizeof(struct tre_mem_struct)))
|
||||
|
||||
#define tre_mem_alloca(mem, size) \
|
||||
((mem)->n >= (size) \
|
||||
? tre_mem_alloc_impl((mem), 1, NULL, 0, (size)) \
|
||||
: tre_mem_alloc_impl((mem), 1, alloca(TRE_MEM_BLOCK_SIZE), 0, (size)))
|
||||
#endif /* TRE_USE_ALLOCA */
|
||||
|
||||
|
||||
/* Frees the memory allocator and all memory allocated with it. */
|
||||
void tre_mem_destroy(tre_mem_t mem);
|
||||
|
||||
#endif /* TRE_MEM_H */
|
||||
|
||||
/* EOF */
|
||||
Vendored
+1758
File diff suppressed because it is too large
Load Diff
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
tre-parse.c - Regexp parser definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TRE_PARSE_H
|
||||
#define TRE_PARSE_H 1
|
||||
|
||||
/* Parse context. */
|
||||
typedef struct {
|
||||
/* Memory allocator. The AST is allocated using this. */
|
||||
tre_mem_t mem;
|
||||
/* Stack used for keeping track of regexp syntax. */
|
||||
tre_stack_t *stack;
|
||||
/* The parse result. */
|
||||
tre_ast_node_t *result;
|
||||
/* The regexp to parse and its length. */
|
||||
const tre_char_t *re;
|
||||
/* The first character of the entire regexp. */
|
||||
const tre_char_t *re_start;
|
||||
/* The first character after the end of the regexp. */
|
||||
const tre_char_t *re_end;
|
||||
size_t len;
|
||||
/* Current submatch ID. */
|
||||
int submatch_id;
|
||||
/* The highest back reference or -1 if none seen so far. */
|
||||
int max_backref;
|
||||
/* This flag is set if the regexp uses approximate matching. */
|
||||
int have_approx;
|
||||
/* This flag is set if the regexp changes cflags inline using (?...) */
|
||||
int have_inline_cflags;
|
||||
/* Compilation flags. */
|
||||
int cflags;
|
||||
/* If this flag is set the top-level submatch is not captured. */
|
||||
int nofirstsub;
|
||||
/* The currently set approximate matching parameters. */
|
||||
int params[TRE_PARAM_LAST];
|
||||
/* the MB_CUR_MAX in use */
|
||||
int mb_cur_max;
|
||||
} tre_parse_ctx_t;
|
||||
|
||||
/* Parses a wide character regexp pattern into a syntax tree. This parser
|
||||
handles both syntaxes (BRE and ERE), including the TRE extensions. */
|
||||
reg_errcode_t
|
||||
tre_parse(tre_parse_ctx_t *ctx);
|
||||
|
||||
#endif /* TRE_PARSE_H */
|
||||
|
||||
/* EOF */
|
||||
Vendored
+123
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
tre-stack.c - Simple stack implementation
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "tre-internal.h"
|
||||
#include "tre-stack.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
union tre_stack_item {
|
||||
void *voidptr_value;
|
||||
int int_value;
|
||||
};
|
||||
|
||||
struct tre_stack_rec {
|
||||
size_t size;
|
||||
size_t max_size;
|
||||
size_t ptr;
|
||||
union tre_stack_item *stack;
|
||||
};
|
||||
|
||||
|
||||
tre_stack_t *
|
||||
tre_stack_new(size_t size, size_t max_size)
|
||||
{
|
||||
tre_stack_t *s;
|
||||
|
||||
s = xmalloc(sizeof(*s));
|
||||
if (s != NULL)
|
||||
{
|
||||
s->stack = xmalloc(sizeof(*s->stack) * size);
|
||||
if (s->stack == NULL)
|
||||
{
|
||||
xfree(s);
|
||||
return NULL;
|
||||
}
|
||||
s->size = size;
|
||||
s->max_size = max_size;
|
||||
s->ptr = 0;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
void
|
||||
tre_stack_destroy(tre_stack_t *s)
|
||||
{
|
||||
xfree(s->stack);
|
||||
xfree(s);
|
||||
}
|
||||
|
||||
size_t
|
||||
tre_stack_num_items(tre_stack_t *s)
|
||||
{
|
||||
return s->ptr;
|
||||
}
|
||||
|
||||
static reg_errcode_t
|
||||
tre_stack_push(tre_stack_t *s, union tre_stack_item value)
|
||||
{
|
||||
if (s->ptr < s->size)
|
||||
{
|
||||
s->stack[s->ptr] = value;
|
||||
s->ptr++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s->size >= s->max_size)
|
||||
{
|
||||
DPRINT(("tre_stack_push: stack full\n"));
|
||||
return REG_ESPACE;
|
||||
}
|
||||
else
|
||||
{
|
||||
union tre_stack_item *new_buffer;
|
||||
size_t new_size;
|
||||
DPRINT(("tre_stack_push: trying to realloc more space\n"));
|
||||
new_size = s->size + s->size;
|
||||
if (new_size > s->max_size)
|
||||
new_size = s->max_size;
|
||||
new_buffer = xrealloc(s->stack, sizeof(*new_buffer) * new_size);
|
||||
if (new_buffer == NULL)
|
||||
{
|
||||
DPRINT(("tre_stack_push: realloc failed.\n"));
|
||||
return REG_ESPACE;
|
||||
}
|
||||
DPRINT(("tre_stack_push: realloc succeeded.\n"));
|
||||
assert(new_size > s->size);
|
||||
s->size = new_size;
|
||||
s->stack = new_buffer;
|
||||
tre_stack_push(s, value);
|
||||
}
|
||||
}
|
||||
return REG_OK;
|
||||
}
|
||||
|
||||
#define define_pushf(typetag, type) \
|
||||
declare_pushf(typetag, type) { \
|
||||
union tre_stack_item item; \
|
||||
item.typetag ## _value = value; \
|
||||
return tre_stack_push(s, item); \
|
||||
}
|
||||
|
||||
define_pushf(int, int)
|
||||
define_pushf(voidptr, void *)
|
||||
|
||||
#define define_popf(typetag, type) \
|
||||
declare_popf(typetag, type) { \
|
||||
return s->stack[--s->ptr].typetag ## _value; \
|
||||
}
|
||||
|
||||
define_popf(int, int)
|
||||
define_popf(voidptr, void *)
|
||||
|
||||
/* EOF */
|
||||
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
tre-stack.h: Stack definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
#ifndef TRE_STACK_H
|
||||
#define TRE_STACK_H 1
|
||||
|
||||
#include "../local_includes/tre.h"
|
||||
|
||||
typedef struct tre_stack_rec tre_stack_t;
|
||||
|
||||
/* Creates a new stack object with initial size `size' and maximum size
|
||||
`max_size'. Pushing an additional item onto a full stack will resize
|
||||
the stack to double its capacity until the maximum is reached. Returns
|
||||
the stack object or NULL if out of memory. */
|
||||
tre_stack_t *
|
||||
tre_stack_new(size_t size, size_t max_size);
|
||||
|
||||
/* Frees the stack object. */
|
||||
void
|
||||
tre_stack_destroy(tre_stack_t *s);
|
||||
|
||||
/* Returns the current number of items on the stack. */
|
||||
size_t
|
||||
tre_stack_num_items(tre_stack_t *s);
|
||||
|
||||
/* Each tre_stack_push_*(tre_stack_t *s, <type> value) function pushes
|
||||
`value' on top of stack `s'. Returns REG_ESPACE if out of memory.
|
||||
This tries to realloc() more space before failing if maximum size
|
||||
has not yet been reached. Returns REG_OK if successful. */
|
||||
#define declare_pushf(typetag, type) \
|
||||
reg_errcode_t tre_stack_push_ ## typetag(tre_stack_t *s, type value)
|
||||
|
||||
declare_pushf(voidptr, void *);
|
||||
declare_pushf(int, int);
|
||||
|
||||
/* Each tre_stack_pop_*(tre_stack_t *s) function pops the topmost
|
||||
element off of stack `s' and returns it. The stack must not be
|
||||
empty. */
|
||||
#define declare_popf(typetag, type) \
|
||||
type tre_stack_pop_ ## typetag(tre_stack_t *s)
|
||||
|
||||
declare_popf(voidptr, void *);
|
||||
declare_popf(int, int);
|
||||
|
||||
/* Just to save some typing. */
|
||||
#define STACK_PUSH(s, typetag, value) \
|
||||
do \
|
||||
{ \
|
||||
status = tre_stack_push_ ## typetag(s, value); \
|
||||
} \
|
||||
while (/*CONSTCOND*/(void)0,0)
|
||||
|
||||
#define STACK_PUSHX(s, typetag, value) \
|
||||
{ \
|
||||
status = tre_stack_push_ ## typetag(s, value); \
|
||||
if (status != REG_OK) \
|
||||
break; \
|
||||
}
|
||||
|
||||
#define STACK_PUSHR(s, typetag, value) \
|
||||
{ \
|
||||
reg_errcode_t _status; \
|
||||
_status = tre_stack_push_ ## typetag(s, value); \
|
||||
if (_status != REG_OK) \
|
||||
return _status; \
|
||||
}
|
||||
|
||||
#endif /* TRE_STACK_H */
|
||||
|
||||
/* EOF */
|
||||
Vendored
+362
@@ -0,0 +1,362 @@
|
||||
/*
|
||||
xmalloc.c - Simple malloc debugging library implementation
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
TODO:
|
||||
- red zones
|
||||
- group dumps by source location
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#define XMALLOC_INTERNAL 1
|
||||
#include "xmalloc.h"
|
||||
|
||||
|
||||
/*
|
||||
Internal stuff.
|
||||
*/
|
||||
|
||||
typedef struct hashTableItemRec {
|
||||
void *ptr;
|
||||
size_t bytes;
|
||||
const char *file;
|
||||
int line;
|
||||
const char *func;
|
||||
struct hashTableItemRec *next;
|
||||
} hashTableItem;
|
||||
|
||||
typedef struct {
|
||||
hashTableItem **table;
|
||||
} hashTable;
|
||||
|
||||
static int xmalloc_peak;
|
||||
int xmalloc_current;
|
||||
static int xmalloc_peak_blocks;
|
||||
int xmalloc_current_blocks;
|
||||
static int xmalloc_fail_after;
|
||||
|
||||
#define TABLE_BITS 8
|
||||
#define TABLE_MASK ((1 << TABLE_BITS) - 1)
|
||||
#define TABLE_SIZE (1 << TABLE_BITS)
|
||||
|
||||
static hashTable *
|
||||
hash_table_new(void)
|
||||
{
|
||||
hashTable *tbl;
|
||||
|
||||
tbl = malloc(sizeof(*tbl));
|
||||
|
||||
if (tbl != NULL)
|
||||
{
|
||||
tbl->table = calloc(TABLE_SIZE, sizeof(*tbl->table));
|
||||
|
||||
if (tbl->table == NULL)
|
||||
{
|
||||
free(tbl);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return tbl;
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
hash_void_ptr(void *ptr)
|
||||
{
|
||||
unsigned int hash;
|
||||
unsigned int i;
|
||||
|
||||
/* I took this hash function just off the top of my head, I have
|
||||
no idea whether it is bad or very bad. */
|
||||
hash = 0;
|
||||
for (i = 0; i < sizeof(ptr) * 8 / TABLE_BITS; i++)
|
||||
{
|
||||
hash ^= (uintptr_t)ptr >> i * 8;
|
||||
hash += i * 17;
|
||||
hash &= TABLE_MASK;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static void
|
||||
hash_table_add(hashTable *tbl, void *ptr, size_t bytes,
|
||||
const char *file, int line, const char *func)
|
||||
{
|
||||
unsigned int i;
|
||||
hashTableItem *item, *new;
|
||||
|
||||
i = hash_void_ptr(ptr);
|
||||
|
||||
item = tbl->table[i];
|
||||
if (item != NULL)
|
||||
while (item->next != NULL)
|
||||
item = item->next;
|
||||
|
||||
new = malloc(sizeof(*new));
|
||||
assert(new != NULL);
|
||||
new->ptr = ptr;
|
||||
new->bytes = bytes;
|
||||
new->file = file;
|
||||
new->line = line;
|
||||
new->func = func;
|
||||
new->next = NULL;
|
||||
if (item != NULL)
|
||||
item->next = new;
|
||||
else
|
||||
tbl->table[i] = new;
|
||||
|
||||
xmalloc_current += bytes;
|
||||
if (xmalloc_current > xmalloc_peak)
|
||||
xmalloc_peak = xmalloc_current;
|
||||
xmalloc_current_blocks++;
|
||||
if (xmalloc_current_blocks > xmalloc_peak_blocks)
|
||||
xmalloc_peak_blocks = xmalloc_current_blocks;
|
||||
}
|
||||
|
||||
static void
|
||||
#if defined(__GNUC__) && __GNUC__ >= 11
|
||||
__attribute__((access(none, 2)))
|
||||
#endif
|
||||
hash_table_del(hashTable *tbl, void *ptr)
|
||||
{
|
||||
int i;
|
||||
hashTableItem *item, *prev;
|
||||
|
||||
i = hash_void_ptr(ptr);
|
||||
|
||||
item = tbl->table[i];
|
||||
if (item == NULL)
|
||||
{
|
||||
printf("xfree: invalid ptr %p\n", ptr);
|
||||
abort();
|
||||
}
|
||||
prev = NULL;
|
||||
while (item->ptr != ptr)
|
||||
{
|
||||
prev = item;
|
||||
item = item->next;
|
||||
}
|
||||
if (item->ptr != ptr)
|
||||
{
|
||||
printf("xfree: invalid ptr %p\n", ptr);
|
||||
abort();
|
||||
}
|
||||
|
||||
xmalloc_current -= item->bytes;
|
||||
xmalloc_current_blocks--;
|
||||
|
||||
if (prev != NULL)
|
||||
{
|
||||
prev->next = item->next;
|
||||
free(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
tbl->table[i] = item->next;
|
||||
free(item);
|
||||
}
|
||||
}
|
||||
|
||||
static hashTable *xmalloc_table = NULL;
|
||||
|
||||
static void
|
||||
xmalloc_init(void)
|
||||
{
|
||||
if (xmalloc_table == NULL)
|
||||
{
|
||||
xmalloc_table = hash_table_new();
|
||||
xmalloc_peak = 0;
|
||||
xmalloc_peak_blocks = 0;
|
||||
xmalloc_current = 0;
|
||||
xmalloc_current_blocks = 0;
|
||||
xmalloc_fail_after = -1;
|
||||
}
|
||||
assert(xmalloc_table != NULL);
|
||||
assert(xmalloc_table->table != NULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Public API.
|
||||
*/
|
||||
|
||||
void
|
||||
xmalloc_configure(int fail_after)
|
||||
{
|
||||
xmalloc_init();
|
||||
xmalloc_fail_after = fail_after;
|
||||
}
|
||||
|
||||
int
|
||||
xmalloc_dump_leaks(void)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned int num_leaks = 0;
|
||||
size_t leaked_bytes = 0;
|
||||
hashTableItem *item;
|
||||
|
||||
xmalloc_init();
|
||||
|
||||
for (i = 0; i < TABLE_SIZE; i++)
|
||||
{
|
||||
item = xmalloc_table->table[i];
|
||||
while (item != NULL)
|
||||
{
|
||||
printf("%s:%d: %s: %zu bytes at %p not freed\n",
|
||||
item->file, item->line, item->func, item->bytes, item->ptr);
|
||||
num_leaks++;
|
||||
leaked_bytes += item->bytes;
|
||||
item = item->next;
|
||||
}
|
||||
}
|
||||
if (num_leaks == 0)
|
||||
printf("No memory leaks.\n");
|
||||
else
|
||||
printf("%u unfreed memory chuncks, total %zu unfreed bytes.\n",
|
||||
num_leaks, leaked_bytes);
|
||||
printf("Peak memory consumption %d bytes (%.1f kB, %.1f MB) in %d blocks ",
|
||||
xmalloc_peak, (double)xmalloc_peak / 1024,
|
||||
(double)xmalloc_peak / (1024*1024), xmalloc_peak_blocks);
|
||||
printf("(average ");
|
||||
if (xmalloc_peak_blocks)
|
||||
printf("%d", ((xmalloc_peak + xmalloc_peak_blocks / 2)
|
||||
/ xmalloc_peak_blocks));
|
||||
else
|
||||
printf("N/A");
|
||||
printf(" bytes per block).\n");
|
||||
|
||||
return num_leaks;
|
||||
}
|
||||
|
||||
void *
|
||||
xmalloc_impl(size_t size, const char *file, int line, const char *func)
|
||||
{
|
||||
void *ptr;
|
||||
|
||||
xmalloc_init();
|
||||
assert(size > 0);
|
||||
|
||||
if (xmalloc_fail_after == 0)
|
||||
{
|
||||
xmalloc_fail_after = -2;
|
||||
#if 0
|
||||
printf("xmalloc: forced failure %s:%d: %s\n", file, line, func);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
else if (xmalloc_fail_after == -2)
|
||||
{
|
||||
printf("xmalloc: called after failure from %s:%d: %s\n",
|
||||
file, line, func);
|
||||
assert(0);
|
||||
}
|
||||
else if (xmalloc_fail_after > 0)
|
||||
xmalloc_fail_after--;
|
||||
|
||||
ptr = malloc(size);
|
||||
if (ptr != NULL)
|
||||
hash_table_add(xmalloc_table, ptr, (int)size, file, line, func);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *
|
||||
xcalloc_impl(size_t nmemb, size_t size, const char *file, int line,
|
||||
const char *func)
|
||||
{
|
||||
void *ptr;
|
||||
|
||||
xmalloc_init();
|
||||
assert(size > 0);
|
||||
|
||||
if (xmalloc_fail_after == 0)
|
||||
{
|
||||
xmalloc_fail_after = -2;
|
||||
#if 0
|
||||
printf("xcalloc: forced failure %s:%d: %s\n", file, line, func);
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
else if (xmalloc_fail_after == -2)
|
||||
{
|
||||
printf("xcalloc: called after failure from %s:%d: %s\n",
|
||||
file, line, func);
|
||||
assert(0);
|
||||
}
|
||||
else if (xmalloc_fail_after > 0)
|
||||
xmalloc_fail_after--;
|
||||
|
||||
ptr = calloc(nmemb, size);
|
||||
if (ptr != NULL)
|
||||
hash_table_add(xmalloc_table, ptr, (int)(nmemb * size), file, line, func);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void
|
||||
xfree_impl(void *ptr, const char *file, int line, const char *func)
|
||||
{
|
||||
/*LINTED*/(void)&file;
|
||||
/*LINTED*/(void)&line;
|
||||
/*LINTED*/(void)&func;
|
||||
xmalloc_init();
|
||||
|
||||
if (ptr != NULL)
|
||||
hash_table_del(xmalloc_table, ptr);
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
void *
|
||||
xrealloc_impl(void *ptr, size_t new_size, const char *file, int line,
|
||||
const char *func)
|
||||
{
|
||||
void *new_ptr;
|
||||
|
||||
xmalloc_init();
|
||||
assert(ptr != NULL);
|
||||
assert(new_size > 0);
|
||||
|
||||
if (xmalloc_fail_after == 0)
|
||||
{
|
||||
xmalloc_fail_after = -2;
|
||||
return NULL;
|
||||
}
|
||||
else if (xmalloc_fail_after == -2)
|
||||
{
|
||||
printf("xrealloc: called after failure from %s:%d: %s\n",
|
||||
file, line, func);
|
||||
assert(0);
|
||||
}
|
||||
else if (xmalloc_fail_after > 0)
|
||||
xmalloc_fail_after--;
|
||||
|
||||
new_ptr = realloc(ptr, new_size);
|
||||
if (new_ptr != NULL && new_ptr != ptr)
|
||||
{
|
||||
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 12
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wuse-after-free"
|
||||
#endif
|
||||
hash_table_del(xmalloc_table, ptr);
|
||||
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 12
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
hash_table_add(xmalloc_table, new_ptr, (int)new_size, file, line, func);
|
||||
}
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* EOF */
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
xmalloc.h - Simple malloc debugging library API
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _XMALLOC_H
|
||||
#define _XMALLOC_H 1
|
||||
|
||||
void *xmalloc_impl(size_t size, const char *file, int line, const char *func);
|
||||
void *xcalloc_impl(size_t nmemb, size_t size, const char *file, int line,
|
||||
const char *func);
|
||||
void xfree_impl(void *ptr, const char *file, int line, const char *func);
|
||||
void *xrealloc_impl(void *ptr, size_t new_size, const char *file, int line,
|
||||
const char *func);
|
||||
int xmalloc_dump_leaks(void);
|
||||
void xmalloc_configure(int fail_after);
|
||||
|
||||
|
||||
#ifndef XMALLOC_INTERNAL
|
||||
#ifdef MALLOC_DEBUGGING
|
||||
|
||||
/* Version 2.4 and later of GCC define a magical variable `__PRETTY_FUNCTION__'
|
||||
which contains the name of the function currently being defined.
|
||||
# define __XMALLOC_FUNCTION __PRETTY_FUNCTION__
|
||||
This is broken in G++ before version 2.6.
|
||||
C9x has a similar variable called __func__, but prefer the GCC one since
|
||||
it demangles C++ function names. */
|
||||
# ifdef __GNUC__
|
||||
# if __GNUC__ > 2 || (__GNUC__ == 2 \
|
||||
&& __GNUC_MINOR__ >= (defined __cplusplus ? 6 : 4))
|
||||
# define __XMALLOC_FUNCTION __PRETTY_FUNCTION__
|
||||
# else
|
||||
# define __XMALLOC_FUNCTION ((const char *) 0)
|
||||
# endif
|
||||
# else
|
||||
# if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L
|
||||
# define __XMALLOC_FUNCTION __func__
|
||||
# else
|
||||
# define __XMALLOC_FUNCTION ((const char *) 0)
|
||||
# endif
|
||||
# endif
|
||||
|
||||
#define xmalloc(size) xmalloc_impl(size, __FILE__, __LINE__, \
|
||||
__XMALLOC_FUNCTION)
|
||||
#define xcalloc(nmemb, size) xcalloc_impl(nmemb, size, __FILE__, __LINE__, \
|
||||
__XMALLOC_FUNCTION)
|
||||
#define xfree(ptr) xfree_impl(ptr, __FILE__, __LINE__, __XMALLOC_FUNCTION)
|
||||
#define xrealloc(ptr, new_size) xrealloc_impl(ptr, new_size, __FILE__, \
|
||||
__LINE__, __XMALLOC_FUNCTION)
|
||||
#undef malloc
|
||||
#undef calloc
|
||||
#undef free
|
||||
#undef realloc
|
||||
|
||||
#define malloc USE_XMALLOC_INSTEAD_OF_MALLOC
|
||||
#define calloc USE_XCALLOC_INSTEAD_OF_CALLOC
|
||||
#define free USE_XFREE_INSTEAD_OF_FREE
|
||||
#define realloc USE_XREALLOC_INSTEAD_OF_REALLOC
|
||||
|
||||
#else /* !MALLOC_DEBUGGING */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#define xmalloc(size) malloc(size)
|
||||
#define xcalloc(nmemb, size) calloc(nmemb, size)
|
||||
#define xfree(ptr) free(ptr)
|
||||
#define xrealloc(ptr, new_size) realloc(ptr, new_size)
|
||||
|
||||
#endif /* !MALLOC_DEBUGGING */
|
||||
#endif /* !XMALLOC_INTERNAL */
|
||||
|
||||
#endif /* _XMALLOC_H */
|
||||
|
||||
/* EOF */
|
||||
Vendored
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
regex.h - TRE legacy API
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
This header is for source level compatibility with old code using
|
||||
the <tre/regex.h> header which defined the TRE API functions without
|
||||
a prefix. New code should include <tre/tre.h> instead.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TRE_REXEX_H
|
||||
#define TRE_REGEX_H 1
|
||||
|
||||
#ifdef USE_LOCAL_TRE_H
|
||||
/* Use the header(s) from the TRE package that this file is part of.
|
||||
(Yes, this file is in local_include too, but the explict path
|
||||
means there is no way to get a system tre.h by accident.) */
|
||||
#include "../local_includes/tre.h"
|
||||
#else
|
||||
/* Use the header(s) from an installed version of the TRE package
|
||||
(so that this application matches the installed libtre),
|
||||
not the one(s) in the local_includes directory. */
|
||||
#include <tre/tre.h>
|
||||
#endif
|
||||
|
||||
#ifndef TRE_USE_SYSTEM_REGEX_H
|
||||
#define regcomp tre_regcomp
|
||||
#define regerror tre_regerror
|
||||
#define regexec tre_regexec
|
||||
#define regfree tre_regfree
|
||||
#endif /* TRE_USE_SYSTEM_REGEX_H */
|
||||
|
||||
#define regacomp tre_regacomp
|
||||
#define regaexec tre_regaexec
|
||||
#define regancomp tre_regancomp
|
||||
#define reganexec tre_reganexec
|
||||
#define regawncomp tre_regawncomp
|
||||
#define regawnexec tre_regawnexec
|
||||
#define regncomp tre_regncomp
|
||||
#define regnexec tre_regnexec
|
||||
#define regwcomp tre_regwcomp
|
||||
#define regwexec tre_regwexec
|
||||
#define regwncomp tre_regwncomp
|
||||
#define regwnexec tre_regwnexec
|
||||
|
||||
#endif /* TRE_REGEX_H */
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
/* Minimal TRE configuration for Redis.
|
||||
*
|
||||
* We use TRE as a byte-oriented regex matcher for ARGREP. Redis SDS values are
|
||||
* binary-safe byte strings, so we intentionally keep the dependency build
|
||||
* simple: no wide-char path, no multibyte locale handling, and no approximate
|
||||
* matching engine.
|
||||
*/
|
||||
|
||||
#define HAVE_SYS_TYPES_H 1
|
||||
|
||||
#define TRE_VERSION "redis-vendored"
|
||||
#define TRE_VERSION_1 0
|
||||
#define TRE_VERSION_2 0
|
||||
#define TRE_VERSION_3 0
|
||||
Vendored
+344
@@ -0,0 +1,344 @@
|
||||
/*
|
||||
tre.h - TRE public API definitions
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifndef TRE_H
|
||||
#define TRE_H 1
|
||||
|
||||
#ifdef USE_LOCAL_TRE_H
|
||||
/* Make certain to use the header(s) from the TRE package that this
|
||||
file is part of by giving the full path to the header from this directory. */
|
||||
#include "../local_includes/tre-config.h"
|
||||
#else
|
||||
/* Use the header in the same directory as this file if there is one. */
|
||||
#include "tre-config.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_SYS_TYPES_H
|
||||
#include <sys/types.h>
|
||||
#endif /* HAVE_SYS_TYPES_H */
|
||||
|
||||
#ifdef HAVE_LIBUTF8_H
|
||||
#include <libutf8.h>
|
||||
#endif /* HAVE_LIBUTF8_H */
|
||||
|
||||
#ifdef TRE_USE_SYSTEM_REGEX_H
|
||||
/* Include the system regex.h to make TRE ABI compatible with the
|
||||
system regex. */
|
||||
#include TRE_SYSTEM_REGEX_H_PATH
|
||||
#define tre_regcomp regcomp
|
||||
#define tre_regexec regexec
|
||||
#define tre_regerror regerror
|
||||
#define tre_regfree regfree
|
||||
/* The GNU C regex has a number of refinements to the POSIX standard for the
|
||||
formal parameter list of the regexec() function, and some of those fail to
|
||||
compile when using LLVM. The refinements seem to be opt-out rather than
|
||||
opt-in when using a recent gcc, and they produce a warning when TRE tries
|
||||
to mimic the API without the refinements. The TRE code still works but
|
||||
the warnings are distracting, so try to #define a flag to indicate when to
|
||||
add the refinements to TRE's parameter list too. */
|
||||
#ifdef __GNUC__
|
||||
/* Try to test something that looks pretty REGEX specific and hope we don't
|
||||
need a zillion different platform+compiler specific tests to deal with this. */
|
||||
#ifdef _REGEX_NELTS
|
||||
/* Define a TRE specific flag here so that:
|
||||
1) there is only one place where code has to be changed if the test above is not adequate, and
|
||||
2) the flag can be used in any other parts of the TRE source that might be affected by the
|
||||
GNUC refinements.
|
||||
Note that this flag is only defined when all of TRE_USE_SYSTEM_REGEX_H, __GNUC__, and _REGEX_NELTS are defined. */
|
||||
#define TRE_USE_GNUC_REGEXEC_FPL 1
|
||||
#endif
|
||||
#endif
|
||||
#endif /* TRE_USE_SYSTEM_REGEX_H */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef TRE_USE_SYSTEM_REGEX_H
|
||||
|
||||
#ifndef REG_OK
|
||||
#define REG_OK 0
|
||||
#endif /* !REG_OK */
|
||||
|
||||
#ifndef HAVE_REG_ERRCODE_T
|
||||
typedef int reg_errcode_t;
|
||||
#endif /* !HAVE_REG_ERRCODE_T */
|
||||
|
||||
#if !defined(REG_NOSPEC) && !defined(REG_LITERAL)
|
||||
#define REG_LITERAL 0x1000
|
||||
#endif
|
||||
|
||||
/* Extra tre_regcomp() return error codes. */
|
||||
#define REG_BADMAX REG_BADBR
|
||||
|
||||
/* Extra tre_regcomp() flags. */
|
||||
#ifndef REG_BASIC
|
||||
#define REG_BASIC 0
|
||||
#endif /* !REG_BASIC */
|
||||
#define REG_RIGHT_ASSOC (REG_LITERAL << 1)
|
||||
#ifdef REG_UNGREEDY
|
||||
/* We're going to use TRE code, so we need the TRE define (dodge problem in MacOS). */
|
||||
#undef REG_UNGREEDY
|
||||
#endif
|
||||
#define REG_UNGREEDY (REG_RIGHT_ASSOC << 1)
|
||||
|
||||
#define REG_USEBYTES (REG_UNGREEDY << 1)
|
||||
|
||||
/* Extra tre_regexec() flags. */
|
||||
#define REG_APPROX_MATCHER 0x1000
|
||||
#ifdef REG_BACKTRACKING_MATCHER
|
||||
/* We're going to use TRE code, so we need the TRE define (dodge problem in MacOS). */
|
||||
#undef REG_BACKTRACKING_MATCHER
|
||||
#endif
|
||||
#define REG_BACKTRACKING_MATCHER (REG_APPROX_MATCHER << 1)
|
||||
|
||||
#else /* !TRE_USE_SYSTEM_REGEX_H */
|
||||
|
||||
/* If the we're not using system regex.h, we need to define the
|
||||
structs and enums ourselves. */
|
||||
|
||||
typedef int regoff_t;
|
||||
typedef struct {
|
||||
size_t re_nsub; /* Number of parenthesized subexpressions. */
|
||||
void *value; /* For internal use only. */
|
||||
} regex_t;
|
||||
|
||||
typedef struct {
|
||||
regoff_t rm_so;
|
||||
regoff_t rm_eo;
|
||||
} regmatch_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
REG_OK = 0, /* No error. */
|
||||
/* POSIX tre_regcomp() return error codes. (In the order listed in the
|
||||
standard.) */
|
||||
REG_NOMATCH, /* No match. */
|
||||
REG_BADPAT, /* Invalid regexp. */
|
||||
REG_ECOLLATE, /* Unknown collating element. */
|
||||
REG_ECTYPE, /* Unknown character class name. */
|
||||
REG_EESCAPE, /* Trailing backslash. */
|
||||
REG_ESUBREG, /* Invalid back reference. */
|
||||
REG_EBRACK, /* "[]" imbalance */
|
||||
REG_EPAREN, /* "\(\)" or "()" imbalance */
|
||||
REG_EBRACE, /* "\{\}" or "{}" imbalance */
|
||||
REG_BADBR, /* Invalid content of {} */
|
||||
REG_ERANGE, /* Invalid use of range operator */
|
||||
REG_ESPACE, /* Out of memory. */
|
||||
REG_BADRPT, /* Invalid use of repetition operators. */
|
||||
REG_BADMAX, /* Maximum repetition in {} too large */
|
||||
} reg_errcode_t;
|
||||
|
||||
/* POSIX tre_regcomp() flags. */
|
||||
#define REG_EXTENDED 1
|
||||
#define REG_ICASE (REG_EXTENDED << 1)
|
||||
#define REG_NEWLINE (REG_ICASE << 1)
|
||||
#define REG_NOSUB (REG_NEWLINE << 1)
|
||||
|
||||
/* Extra tre_regcomp() flags. */
|
||||
#define REG_BASIC 0
|
||||
#define REG_LITERAL (REG_NOSUB << 1)
|
||||
#define REG_RIGHT_ASSOC (REG_LITERAL << 1)
|
||||
#define REG_UNGREEDY (REG_RIGHT_ASSOC << 1)
|
||||
#define REG_USEBYTES (REG_UNGREEDY << 1)
|
||||
|
||||
/* POSIX tre_regexec() flags. */
|
||||
#define REG_NOTBOL 1
|
||||
#define REG_NOTEOL (REG_NOTBOL << 1)
|
||||
|
||||
/* Extra tre_regexec() flags. */
|
||||
#define REG_APPROX_MATCHER (REG_NOTEOL << 1)
|
||||
#define REG_BACKTRACKING_MATCHER (REG_APPROX_MATCHER << 1)
|
||||
|
||||
#endif /* !TRE_USE_SYSTEM_REGEX_H */
|
||||
|
||||
/* REG_NOSPEC and REG_LITERAL mean the same thing. */
|
||||
#if defined(REG_LITERAL) && !defined(REG_NOSPEC)
|
||||
#define REG_NOSPEC REG_LITERAL
|
||||
#elif defined(REG_NOSPEC) && !defined(REG_LITERAL)
|
||||
#define REG_LITERAL REG_NOSPEC
|
||||
#endif /* defined(REG_NOSPEC) */
|
||||
|
||||
/* The maximum number of iterations in a bound expression. */
|
||||
#undef RE_DUP_MAX
|
||||
#define RE_DUP_MAX 255
|
||||
|
||||
/* The POSIX.2 regexp functions */
|
||||
extern int
|
||||
tre_regcomp(regex_t *preg, const char *regex, int cflags);
|
||||
|
||||
#ifdef TRE_USE_GNUC_REGEXEC_FPL
|
||||
extern int
|
||||
tre_regexec(const regex_t *preg, const char *string,
|
||||
size_t nmatch, regmatch_t pmatch[_Restrict_arr_ _REGEX_NELTS (nmatch)],
|
||||
int eflags);
|
||||
#else
|
||||
extern int
|
||||
tre_regexec(const regex_t *preg, const char *string, size_t nmatch,
|
||||
regmatch_t pmatch[], int eflags);
|
||||
#endif
|
||||
|
||||
extern int
|
||||
tre_regcompb(regex_t *preg, const char *regex, int cflags);
|
||||
|
||||
extern int
|
||||
tre_regexecb(const regex_t *preg, const char *string, size_t nmatch,
|
||||
regmatch_t pmatch[], int eflags);
|
||||
|
||||
extern size_t
|
||||
tre_regerror(int errcode, const regex_t *preg, char *errbuf,
|
||||
size_t errbuf_size);
|
||||
|
||||
extern void
|
||||
tre_regfree(regex_t *preg);
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
#ifdef HAVE_WCHAR_H
|
||||
#include <wchar.h>
|
||||
#endif /* HAVE_WCHAR_H */
|
||||
|
||||
/* Wide character versions (not in POSIX.2). */
|
||||
extern int
|
||||
tre_regwcomp(regex_t *preg, const wchar_t *regex, int cflags);
|
||||
|
||||
extern int
|
||||
tre_regwexec(const regex_t *preg, const wchar_t *string,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags);
|
||||
#endif /* TRE_WCHAR */
|
||||
|
||||
/* Versions with a maximum length argument and therefore the capability to
|
||||
handle null characters in the middle of the strings (not in POSIX.2). */
|
||||
extern int
|
||||
tre_regncomp(regex_t *preg, const char *regex, size_t len, int cflags);
|
||||
|
||||
extern int
|
||||
tre_regnexec(const regex_t *preg, const char *string, size_t len,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags);
|
||||
|
||||
/* regn*b versions take byte literally as 8-bit values */
|
||||
extern int
|
||||
tre_regncompb(regex_t *preg, const char *regex, size_t n, int cflags);
|
||||
|
||||
extern int
|
||||
tre_regnexecb(const regex_t *preg, const char *str, size_t len,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags);
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
extern int
|
||||
tre_regwncomp(regex_t *preg, const wchar_t *regex, size_t len, int cflags);
|
||||
|
||||
extern int
|
||||
tre_regwnexec(const regex_t *preg, const wchar_t *string, size_t len,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags);
|
||||
#endif /* TRE_WCHAR */
|
||||
|
||||
#ifdef TRE_APPROX
|
||||
|
||||
/* Approximate matching parameter struct. */
|
||||
typedef struct {
|
||||
int cost_ins; /* Default cost of an inserted character. */
|
||||
int cost_del; /* Default cost of a deleted character. */
|
||||
int cost_subst; /* Default cost of a substituted character. */
|
||||
int max_cost; /* Maximum allowed cost of a match. */
|
||||
|
||||
int max_ins; /* Maximum allowed number of inserts. */
|
||||
int max_del; /* Maximum allowed number of deletes. */
|
||||
int max_subst; /* Maximum allowed number of substitutes. */
|
||||
int max_err; /* Maximum allowed number of errors total. */
|
||||
} regaparams_t;
|
||||
|
||||
/* Approximate matching result struct. */
|
||||
typedef struct {
|
||||
size_t nmatch; /* Length of pmatch[] array. */
|
||||
regmatch_t *pmatch; /* Submatch data. */
|
||||
int cost; /* Cost of the match. */
|
||||
int num_ins; /* Number of inserts in the match. */
|
||||
int num_del; /* Number of deletes in the match. */
|
||||
int num_subst; /* Number of substitutes in the match. */
|
||||
} regamatch_t;
|
||||
|
||||
|
||||
/* Approximate matching functions. */
|
||||
extern int
|
||||
tre_regaexec(const regex_t *preg, const char *string,
|
||||
regamatch_t *match, regaparams_t params, int eflags);
|
||||
|
||||
extern int
|
||||
tre_reganexec(const regex_t *preg, const char *string, size_t len,
|
||||
regamatch_t *match, regaparams_t params, int eflags);
|
||||
|
||||
extern int
|
||||
tre_regaexecb(const regex_t *preg, const char *string,
|
||||
regamatch_t *match, regaparams_t params, int eflags);
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
/* Wide character approximate matching. */
|
||||
extern int
|
||||
tre_regawexec(const regex_t *preg, const wchar_t *string,
|
||||
regamatch_t *match, regaparams_t params, int eflags);
|
||||
|
||||
extern int
|
||||
tre_regawnexec(const regex_t *preg, const wchar_t *string, size_t len,
|
||||
regamatch_t *match, regaparams_t params, int eflags);
|
||||
#endif /* TRE_WCHAR */
|
||||
|
||||
/* Sets the parameters to default values. */
|
||||
extern void
|
||||
tre_regaparams_default(regaparams_t *params);
|
||||
#endif /* TRE_APPROX */
|
||||
|
||||
#ifdef TRE_WCHAR
|
||||
typedef wchar_t tre_char_t;
|
||||
#else /* !TRE_WCHAR */
|
||||
typedef unsigned char tre_char_t;
|
||||
#endif /* !TRE_WCHAR */
|
||||
|
||||
typedef struct {
|
||||
int (*get_next_char)(tre_char_t *c, unsigned int *pos_add, void *context);
|
||||
void (*rewind)(size_t pos, void *context);
|
||||
int (*compare)(size_t pos1, size_t pos2, size_t len, void *context);
|
||||
void *context;
|
||||
} tre_str_source;
|
||||
|
||||
extern int
|
||||
tre_reguexec(const regex_t *preg, const tre_str_source *string,
|
||||
size_t nmatch, regmatch_t pmatch[], int eflags);
|
||||
|
||||
/* Returns the version string. The returned string is static. */
|
||||
extern char *
|
||||
tre_version(void);
|
||||
|
||||
/* Returns the value for a config parameter. The type to which `result'
|
||||
must point to depends of the value of `query', see documentation for
|
||||
more details. */
|
||||
extern int
|
||||
tre_config(int query, void *result);
|
||||
|
||||
enum {
|
||||
TRE_CONFIG_APPROX,
|
||||
TRE_CONFIG_WCHAR,
|
||||
TRE_CONFIG_MULTIBYTE,
|
||||
TRE_CONFIG_SYSTEM_ABI,
|
||||
TRE_CONFIG_VERSION
|
||||
};
|
||||
|
||||
/* Returns 1 if the compiled pattern has back references, 0 if not. */
|
||||
extern int
|
||||
tre_have_backrefs(const regex_t *preg);
|
||||
|
||||
/* Returns 1 if the compiled pattern uses approximate matching features,
|
||||
0 if not. */
|
||||
extern int
|
||||
tre_have_approx(const regex_t *preg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* TRE_H */
|
||||
|
||||
/* EOF */
|
||||
Vendored
+1871
File diff suppressed because it is too large
Load Diff
Vendored
+303
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
test-literal-opt.c - Validate TRE literal optimization against the
|
||||
generic matcher.
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tre-internal.h"
|
||||
|
||||
#define PMATCH_SLOTS 4
|
||||
#define RC_ANY -9999
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *pattern;
|
||||
size_t pattern_len;
|
||||
int cflags;
|
||||
const char *string;
|
||||
size_t string_len;
|
||||
int eflags;
|
||||
int expected_rc;
|
||||
tre_literal_opt_mode_t expected_mode;
|
||||
} litopt_case_t;
|
||||
|
||||
static void
|
||||
init_pmatch(regmatch_t pmatch[], size_t count)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
pmatch[i].rm_so = 111;
|
||||
pmatch[i].rm_eo = 222;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
same_pmatch(const regmatch_t a[], const regmatch_t b[], size_t count)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
if (a[i].rm_so != b[i].rm_so || a[i].rm_eo != b[i].rm_eo)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
pmatch_cleared(const regmatch_t pmatch[], size_t count)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
if (pmatch[i].rm_so != -1 || pmatch[i].rm_eo != -1)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
run_case(const litopt_case_t *tc)
|
||||
{
|
||||
regex_t preg;
|
||||
tre_tnfa_t *tnfa;
|
||||
regmatch_t fast[PMATCH_SLOTS], slow[PMATCH_SLOTS];
|
||||
tre_literal_opt_mode_t saved_mode;
|
||||
char errbuf[256];
|
||||
int errcode, fast_rc, slow_rc;
|
||||
|
||||
memset(&preg, 0, sizeof(preg));
|
||||
errcode = tre_regncompb(&preg, tc->pattern, tc->pattern_len, tc->cflags);
|
||||
if (errcode != REG_OK)
|
||||
{
|
||||
tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
|
||||
fprintf(stderr, "%s: compile failed: %s\n", tc->name, errbuf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
tnfa = (tre_tnfa_t *)preg.value;
|
||||
if (tnfa->literal_opt.mode != tc->expected_mode)
|
||||
{
|
||||
fprintf(stderr, "%s: optimizer mode %d, expected %d\n",
|
||||
tc->name, (int)tnfa->literal_opt.mode, (int)tc->expected_mode);
|
||||
tre_regfree(&preg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
init_pmatch(fast, PMATCH_SLOTS);
|
||||
init_pmatch(slow, PMATCH_SLOTS);
|
||||
|
||||
fast_rc = tre_regnexecb(&preg, tc->string, tc->string_len,
|
||||
PMATCH_SLOTS, fast, tc->eflags);
|
||||
|
||||
saved_mode = tnfa->literal_opt.mode;
|
||||
tnfa->literal_opt.mode = TRE_LITERAL_OPT_NONE;
|
||||
slow_rc = tre_regnexecb(&preg, tc->string, tc->string_len,
|
||||
PMATCH_SLOTS, slow, tc->eflags);
|
||||
tnfa->literal_opt.mode = saved_mode;
|
||||
|
||||
if (fast_rc != slow_rc)
|
||||
{
|
||||
fprintf(stderr, "%s: fast rc %d, slow rc %d\n",
|
||||
tc->name, fast_rc, slow_rc);
|
||||
tre_regfree(&preg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (tc->expected_rc != RC_ANY && fast_rc != tc->expected_rc)
|
||||
{
|
||||
fprintf(stderr, "%s: rc %d, expected %d\n",
|
||||
tc->name, fast_rc, tc->expected_rc);
|
||||
tre_regfree(&preg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!same_pmatch(fast, slow, PMATCH_SLOTS))
|
||||
{
|
||||
fprintf(stderr, "%s: fast and slow pmatch differ\n", tc->name);
|
||||
tre_regfree(&preg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((tc->cflags & REG_NOSUB) && fast_rc == REG_OK
|
||||
&& !pmatch_cleared(fast, PMATCH_SLOTS))
|
||||
{
|
||||
fprintf(stderr, "%s: REG_NOSUB match did not clear pmatch\n", tc->name);
|
||||
tre_regfree(&preg);
|
||||
return 1;
|
||||
}
|
||||
|
||||
tre_regfree(&preg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
static const char nonascii_pattern[] = { (char)0xc0, '|', (char)0xe0 };
|
||||
static const char nonascii_haystack[] = { 'x', (char)0xe0, 'y' };
|
||||
static const litopt_case_t cases[] = {
|
||||
{
|
||||
"contains basic",
|
||||
"foo|bar|baz",
|
||||
sizeof("foo|bar|baz") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"xxbaryy",
|
||||
sizeof("xxbaryy") - 1,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_CONTAINS
|
||||
},
|
||||
{
|
||||
"contains ignores bol/eol flags",
|
||||
"foo|bar|baz",
|
||||
sizeof("foo|bar|baz") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"xxbaryy",
|
||||
sizeof("xxbaryy") - 1,
|
||||
REG_NOTBOL | REG_NOTEOL,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_CONTAINS
|
||||
},
|
||||
{
|
||||
"prefix basic",
|
||||
"^(foo|bar|baz)",
|
||||
sizeof("^(foo|bar|baz)") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"barrier",
|
||||
sizeof("barrier") - 1,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_PREFIX
|
||||
},
|
||||
{
|
||||
"prefix respects REG_NOTBOL",
|
||||
"^(foo|bar|baz)",
|
||||
sizeof("^(foo|bar|baz)") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"barrier",
|
||||
sizeof("barrier") - 1,
|
||||
REG_NOTBOL,
|
||||
REG_NOMATCH,
|
||||
TRE_LITERAL_OPT_PREFIX
|
||||
},
|
||||
{
|
||||
"suffix basic",
|
||||
"(foo|bar|baz)$",
|
||||
sizeof("(foo|bar|baz)$") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"crowbar",
|
||||
sizeof("crowbar") - 1,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_SUFFIX
|
||||
},
|
||||
{
|
||||
"suffix respects REG_NOTEOL",
|
||||
"(foo|bar|baz)$",
|
||||
sizeof("(foo|bar|baz)$") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"crowbar",
|
||||
sizeof("crowbar") - 1,
|
||||
REG_NOTEOL,
|
||||
REG_NOMATCH,
|
||||
TRE_LITERAL_OPT_SUFFIX
|
||||
},
|
||||
{
|
||||
"exact basic",
|
||||
"^(foo|bar|baz)$",
|
||||
sizeof("^(foo|bar|baz)$") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"bar",
|
||||
sizeof("bar") - 1,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_EXACT
|
||||
},
|
||||
{
|
||||
"exact respects REG_NOTBOL",
|
||||
"^(foo|bar|baz)$",
|
||||
sizeof("^(foo|bar|baz)$") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"bar",
|
||||
sizeof("bar") - 1,
|
||||
REG_NOTBOL,
|
||||
REG_NOMATCH,
|
||||
TRE_LITERAL_OPT_EXACT
|
||||
},
|
||||
{
|
||||
"exact respects REG_NOTEOL",
|
||||
"^(foo|bar|baz)$",
|
||||
sizeof("^(foo|bar|baz)$") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"bar",
|
||||
sizeof("bar") - 1,
|
||||
REG_NOTEOL,
|
||||
REG_NOMATCH,
|
||||
TRE_LITERAL_OPT_EXACT
|
||||
},
|
||||
{
|
||||
"empty alternation disables optimization",
|
||||
"(|foo|bar)",
|
||||
sizeof("(|foo|bar)") - 1,
|
||||
REG_EXTENDED | REG_NOSUB,
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_NONE
|
||||
},
|
||||
{
|
||||
"inline flag disable stays generic",
|
||||
"foo(?-i:zap)zot",
|
||||
sizeof("foo(?-i:zap)zot") - 1,
|
||||
REG_EXTENDED | REG_ICASE | REG_NOSUB,
|
||||
"FoOzApZOt",
|
||||
sizeof("FoOzApZOt") - 1,
|
||||
0,
|
||||
REG_NOMATCH,
|
||||
TRE_LITERAL_OPT_NONE
|
||||
},
|
||||
{
|
||||
"inline flag disable still matches exact scoped bytes",
|
||||
"foo(?-i:zap)zot",
|
||||
sizeof("foo(?-i:zap)zot") - 1,
|
||||
REG_EXTENDED | REG_ICASE | REG_NOSUB,
|
||||
"FoOzapZOt",
|
||||
sizeof("FoOzapZOt") - 1,
|
||||
0,
|
||||
REG_OK,
|
||||
TRE_LITERAL_OPT_NONE
|
||||
},
|
||||
{
|
||||
"nocase non-ascii bytes stay in sync",
|
||||
nonascii_pattern,
|
||||
sizeof(nonascii_pattern),
|
||||
REG_EXTENDED | REG_ICASE | REG_NOSUB,
|
||||
nonascii_haystack,
|
||||
sizeof(nonascii_haystack),
|
||||
0,
|
||||
RC_ANY,
|
||||
TRE_LITERAL_OPT_CONTAINS
|
||||
}
|
||||
};
|
||||
size_t i;
|
||||
int failures = 0;
|
||||
|
||||
setlocale(LC_CTYPE, "en_US.ISO-8859-1");
|
||||
|
||||
for (i = 0; i < elementsof(cases); i++)
|
||||
failures += run_case(&cases[i]);
|
||||
|
||||
return failures;
|
||||
}
|
||||
Vendored
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
test-malformed-regn.c - Verify exact-length edge-case regexps compile or fail
|
||||
cleanly both with and without a trailing NUL byte.
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "tre.h"
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *pattern;
|
||||
int expected_err;
|
||||
} malformed_case_t;
|
||||
|
||||
static int
|
||||
run_case(const malformed_case_t *tc, int nul_terminated)
|
||||
{
|
||||
regex_t preg;
|
||||
size_t len = strlen(tc->pattern);
|
||||
size_t alloc_len = len + (nul_terminated ? 1 : 0);
|
||||
char *pattern = malloc(alloc_len ? alloc_len : 1);
|
||||
int errcode;
|
||||
|
||||
if (pattern == NULL)
|
||||
{
|
||||
fprintf(stderr, "%s: out of memory\n", tc->name);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (len > 0)
|
||||
memcpy(pattern, tc->pattern, len);
|
||||
if (nul_terminated)
|
||||
pattern[len] = '\0';
|
||||
|
||||
memset(&preg, 0, sizeof(preg));
|
||||
errcode = tre_regncompb(&preg, pattern, len, REG_EXTENDED | REG_NOSUB);
|
||||
if (errcode == REG_OK)
|
||||
tre_regfree(&preg);
|
||||
|
||||
free(pattern);
|
||||
|
||||
if (errcode != tc->expected_err)
|
||||
{
|
||||
char errbuf[128];
|
||||
memset(&preg, 0, sizeof(preg));
|
||||
tre_regerror(errcode, &preg, errbuf, sizeof(errbuf));
|
||||
fprintf(stderr, "%s (%s): got %d (%s), expected %d\n",
|
||||
tc->name, nul_terminated ? "nul" : "exact",
|
||||
errcode, errbuf, tc->expected_err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
static const malformed_case_t cases[] = {
|
||||
{ "open paren", "(", REG_EPAREN },
|
||||
{ "open bracket", "[", REG_EBRACK },
|
||||
{ "unterminated comment", "(?#", REG_BADPAT },
|
||||
{ "unterminated inline flags", "(?i", REG_BADPAT },
|
||||
{ "short hex escape", "\\x", REG_OK },
|
||||
{ "unterminated wide hex", "\\x{", REG_EBRACE },
|
||||
{ "empty wide hex", "\\x{}", REG_OK }
|
||||
};
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sizeof(cases) / sizeof(*cases); i++)
|
||||
{
|
||||
if (run_case(&cases[i], 0))
|
||||
return 1;
|
||||
if (run_case(&cases[i], 1))
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Vendored
+192
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
test-str-source.c - Sample program for using tre_reguexec()
|
||||
|
||||
This software is released under a BSD-style license.
|
||||
See the file LICENSE for details and copyright.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif /* HAVE_CONFIG_H */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
/* look for getopt in order to use a -o option for output. */
|
||||
#if defined(HAVE_UNISTD_H)
|
||||
#include <unistd.h>
|
||||
#elif defined(HAVE_GETOPT_H)
|
||||
#include <getopt.h>
|
||||
#endif
|
||||
|
||||
#include "tre-internal.h"
|
||||
|
||||
static FILE *outf = NULL;
|
||||
|
||||
/* Context structure for the tre_str_source wrappers. */
|
||||
typedef struct {
|
||||
/* Our string. */
|
||||
const char *str;
|
||||
/* Current position in the string. */
|
||||
size_t pos;
|
||||
} str_handler_ctx;
|
||||
|
||||
/* The get_next_char() handler. Sets `c' to the value of the next character,
|
||||
and increases `pos_add' by the number of bytes read. Returns 1 if the
|
||||
string has ended, 0 if there are more characters. */
|
||||
static int
|
||||
str_handler_get_next(tre_char_t *c, unsigned int *pos_add, void *context)
|
||||
{
|
||||
str_handler_ctx *ctx = context;
|
||||
unsigned char ch = ctx->str[ctx->pos];
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
fprintf(outf, "str[%lu] = %d\n", (unsigned long)ctx->pos, ch);
|
||||
#endif /* TRE_DEBUG */
|
||||
*c = ch;
|
||||
if (ch)
|
||||
ctx->pos++;
|
||||
*pos_add = 1;
|
||||
|
||||
return ch == '\0';
|
||||
}
|
||||
|
||||
/* The rewind() handler. Resets the current position in the input string. */
|
||||
static void
|
||||
str_handler_rewind(size_t pos, void *context)
|
||||
{
|
||||
str_handler_ctx *ctx = context;
|
||||
|
||||
#ifdef TRE_DEBUG
|
||||
fprintf(outf, "rewind to %lu\n", (unsigned long)pos);
|
||||
#endif /* TRE_DEBUG */
|
||||
ctx->pos = pos;
|
||||
}
|
||||
|
||||
/* The compare() handler. Compares two substrings in the input and returns
|
||||
0 if the substrings are equal, and a nonzero value if not. */
|
||||
static int
|
||||
str_handler_compare(size_t pos1, size_t pos2, size_t len, void *context)
|
||||
{
|
||||
str_handler_ctx *ctx = context;
|
||||
#ifdef TRE_DEBUG
|
||||
fprintf(outf, "comparing %lu-%lu and %lu-%lu\n",
|
||||
(unsigned long)pos1, (unsigned long)pos1 + len,
|
||||
(unsigned long)pos2, (unsigned long)pos2 + len);
|
||||
#endif /* TRE_DEBUG */
|
||||
return strncmp(ctx->str + pos1, ctx->str + pos2, len);
|
||||
}
|
||||
|
||||
/* Creates a tre_str_source wrapper around the string `str'. Returns the
|
||||
tre_str_source object or NULL if out of memory. */
|
||||
static tre_str_source *
|
||||
make_str_source(const char *str)
|
||||
{
|
||||
tre_str_source *s;
|
||||
str_handler_ctx *ctx;
|
||||
|
||||
s = calloc(1, sizeof(*s));
|
||||
if (!s)
|
||||
return NULL;
|
||||
|
||||
ctx = malloc(sizeof(str_handler_ctx));
|
||||
if (!ctx)
|
||||
{
|
||||
free(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->str = str;
|
||||
ctx->pos = 0;
|
||||
s->context = ctx;
|
||||
s->get_next_char = str_handler_get_next;
|
||||
s->rewind = str_handler_rewind;
|
||||
s->compare = str_handler_compare;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Frees the memory allocated for `s'. */
|
||||
static void
|
||||
free_str_source(tre_str_source *s)
|
||||
{
|
||||
free(s->context);
|
||||
free(s);
|
||||
}
|
||||
|
||||
/* Run one test with tre_reguexec. Returns 1 if the regex matches, 0 if
|
||||
it doesn't, and -1 if an error occurs. */
|
||||
static int
|
||||
test_reguexec(const char *str, const char *regex)
|
||||
{
|
||||
regex_t preg;
|
||||
tre_str_source *source;
|
||||
regmatch_t pmatch[5];
|
||||
int ret;
|
||||
|
||||
if ((source = make_str_source(str)) == NULL)
|
||||
{
|
||||
fprintf(stderr, "Out of memory\n");
|
||||
ret = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tre_regcomp(&preg, regex, REG_EXTENDED) != REG_OK)
|
||||
{
|
||||
fprintf(stderr, "Failed to compile /%s/\n", regex);
|
||||
ret = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tre_reguexec(&preg, source, elementsof(pmatch), pmatch, 0) == 0)
|
||||
{
|
||||
fprintf(outf, "Match: /%s/ matches \"%.*s\" in \"%s\"\n", regex,
|
||||
(int)(pmatch[0].rm_eo - pmatch[0].rm_so),
|
||||
str + pmatch[0].rm_so, str);
|
||||
ret = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(outf, "No match: /%s/ in \"%s\"\n", regex, str);
|
||||
ret = 0;
|
||||
}
|
||||
tre_regfree(&preg);
|
||||
}
|
||||
free_str_source(source);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int ret = 0;
|
||||
outf = stdout;
|
||||
#if defined(HAVE_UNISTD_H) || defined(HAVE_GETOPT_H)
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "o:")) != EOF)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
case 'o':
|
||||
if ((outf = fopen(optarg, "w")) == NULL)
|
||||
{
|
||||
perror(optarg);
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
/* getopt() will have printed an error message already */
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
ret += test_reguexec("xfoofofoofoo", "(foo)\\1") != 1;
|
||||
ret += test_reguexec("catcat", "(cat|dog)\\1") != 1;
|
||||
ret += test_reguexec("catdog", "(cat|dog)\\1") != 0;
|
||||
ret += test_reguexec("dogdog", "(cat|dog)\\1") != 1;
|
||||
ret += test_reguexec("dogcat", "(cat|dog)\\1") != 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
# Hanzo Memory (Redis Fork)
|
||||
|
||||
## Overview
|
||||
|
||||
**Hanzo Memory** is a fork of Redis optimized for the Hanzo AI platform's caching and real-time needs. It provides:
|
||||
|
||||
- **Session Storage** - User sessions, API keys
|
||||
- **Rate Limiting** - Token bucket, sliding window
|
||||
- **Real-time PubSub** - WebSocket notifications
|
||||
- **Caching Layer** - LLM responses, embeddings
|
||||
- **Queue Management** - Background job processing
|
||||
|
||||
Repository: https://github.com/hanzoai/redis
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Start Redis with Hanzo config
|
||||
cd hanzo
|
||||
docker compose up -d
|
||||
|
||||
# Connect to Redis
|
||||
docker exec -it hanzo-redis redis-cli
|
||||
|
||||
# Test connection
|
||||
PING
|
||||
```
|
||||
|
||||
## Hanzo Modules
|
||||
|
||||
Pre-configured modules:
|
||||
- **RedisJSON** - Native JSON support
|
||||
- **RediSearch** - Full-text search
|
||||
- **RedisTimeSeries** - Time-series data
|
||||
- **RedisBloom** - Probabilistic data structures
|
||||
|
||||
## Key Namespaces
|
||||
|
||||
```
|
||||
hanzo:session:{session_id} - User sessions
|
||||
hanzo:api_key:{key_hash} - API key data
|
||||
hanzo:rate:{org_id}:{endpoint} - Rate limit counters
|
||||
hanzo:cache:llm:{hash} - LLM response cache
|
||||
hanzo:cache:embed:{hash} - Embedding cache
|
||||
hanzo:queue:{queue_name} - Job queues
|
||||
hanzo:pubsub:{channel} - Real-time channels
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### With hanzo/console (LangFuse fork)
|
||||
|
||||
Console uses Redis for caching:
|
||||
```env
|
||||
REDIS_URL=redis://localhost:6379
|
||||
```
|
||||
|
||||
### With hanzo/llm (LiteLLM fork)
|
||||
|
||||
LLM Gateway caches responses:
|
||||
```env
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=hanzo_dev
|
||||
```
|
||||
|
||||
### With hanzo/iam (Casdoor fork)
|
||||
|
||||
IAM uses for session storage:
|
||||
```env
|
||||
redisEndpoint=localhost:6379
|
||||
```
|
||||
|
||||
## Syncing with Upstream
|
||||
|
||||
```bash
|
||||
# Fetch upstream changes
|
||||
git fetch upstream
|
||||
|
||||
# Merge upstream unstable
|
||||
git merge upstream/unstable
|
||||
|
||||
# Keep hanzo/ directory
|
||||
git checkout --ours hanzo/
|
||||
|
||||
git push origin unstable
|
||||
```
|
||||
|
||||
## Performance Tuning
|
||||
|
||||
### Memory Management
|
||||
|
||||
```
|
||||
# redis.conf
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# Persistence
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
appendonly yes
|
||||
appendfsync everysec
|
||||
```
|
||||
|
||||
### Client Limits
|
||||
|
||||
```
|
||||
# Connection limits
|
||||
maxclients 10000
|
||||
timeout 300
|
||||
|
||||
# Slow log
|
||||
slowlog-log-slower-than 10000
|
||||
slowlog-max-len 128
|
||||
```
|
||||
|
||||
## Docker Compose
|
||||
|
||||
See `hanzo/compose.yml` for local development with:
|
||||
- Redis Stack (includes modules)
|
||||
- RedisInsight for management
|
||||
- Prometheus metrics export
|
||||
|
||||
## Caching Patterns
|
||||
|
||||
### LLM Response Caching
|
||||
|
||||
```python
|
||||
# Cache key: sha256(model + prompt + params)
|
||||
cache_key = f"hanzo:cache:llm:{hash}"
|
||||
ttl = 3600 # 1 hour
|
||||
|
||||
# Set with JSON
|
||||
redis.json().set(cache_key, "$", response)
|
||||
redis.expire(cache_key, ttl)
|
||||
```
|
||||
|
||||
### Rate Limiting (Sliding Window)
|
||||
|
||||
```python
|
||||
# Key: hanzo:rate:{org_id}:{endpoint}
|
||||
key = f"hanzo:rate:{org_id}:chat_completions"
|
||||
window_seconds = 60
|
||||
max_requests = 100
|
||||
|
||||
# Lua script for atomic operation
|
||||
```
|
||||
|
||||
### Session Storage
|
||||
|
||||
```python
|
||||
# Key: hanzo:session:{session_id}
|
||||
session_key = f"hanzo:session:{session_id}"
|
||||
ttl = 86400 # 24 hours
|
||||
|
||||
redis.json().set(session_key, "$", session_data)
|
||||
redis.expire(session_key, ttl)
|
||||
```
|
||||
|
||||
## Related Repositories
|
||||
|
||||
- **hanzo/console** - AI observability (caching)
|
||||
- **hanzo/llm** - LLM Gateway (response cache)
|
||||
- **hanzo/iam** - Identity management (sessions)
|
||||
- **hanzo/datastore** - ClickHouse fork (OLAP)
|
||||
- **hanzo/relational** - PostgreSQL fork (OLTP)
|
||||
@@ -0,0 +1,42 @@
|
||||
services:
|
||||
redis:
|
||||
image: redis/redis-stack:latest
|
||||
container_name: hanzo-redis
|
||||
ports:
|
||||
- "6379:6379" # Redis
|
||||
- "8001:8001" # RedisInsight
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
- ./redis.conf:/usr/local/etc/redis/redis.conf:ro
|
||||
environment:
|
||||
REDIS_ARGS: "--requirepass ${REDIS_PASSWORD:-hanzo_dev}"
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-hanzo_dev}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
command: >
|
||||
redis-stack-server
|
||||
--appendonly yes
|
||||
--maxmemory 2gb
|
||||
--maxmemory-policy allkeys-lru
|
||||
|
||||
exporter:
|
||||
image: oliver006/redis_exporter:latest
|
||||
container_name: hanzo-redis-exporter
|
||||
ports:
|
||||
- "9121:9121"
|
||||
environment:
|
||||
REDIS_ADDR: redis://redis:6379
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD:-hanzo_dev}
|
||||
depends_on:
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
redis-data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: hanzo-memory
|
||||
@@ -0,0 +1,68 @@
|
||||
# Hanzo Memory - Redis Configuration
|
||||
|
||||
# Network
|
||||
bind 0.0.0.0
|
||||
port 6379
|
||||
tcp-backlog 511
|
||||
timeout 300
|
||||
tcp-keepalive 300
|
||||
|
||||
# Memory
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
maxmemory-samples 5
|
||||
|
||||
# Persistence
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
stop-writes-on-bgsave-error yes
|
||||
rdbcompression yes
|
||||
rdbchecksum yes
|
||||
dbfilename dump.rdb
|
||||
|
||||
# Append-only file
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
appendfsync everysec
|
||||
no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# Logging
|
||||
loglevel notice
|
||||
logfile ""
|
||||
|
||||
# Clients
|
||||
maxclients 10000
|
||||
|
||||
# Slow log
|
||||
slowlog-log-slower-than 10000
|
||||
slowlog-max-len 128
|
||||
|
||||
# Event notifications (for pub/sub)
|
||||
notify-keyspace-events "Ex"
|
||||
|
||||
# Lua scripting
|
||||
lua-time-limit 5000
|
||||
|
||||
# Latency monitoring
|
||||
latency-monitor-threshold 100
|
||||
|
||||
# Active defragmentation
|
||||
activedefrag yes
|
||||
active-defrag-ignore-bytes 100mb
|
||||
active-defrag-threshold-lower 10
|
||||
active-defrag-threshold-upper 100
|
||||
active-defrag-cycle-min 1
|
||||
active-defrag-cycle-max 25
|
||||
|
||||
# Thread I/O
|
||||
io-threads 4
|
||||
io-threads-do-reads yes
|
||||
|
||||
# Module loading (redis-stack includes these)
|
||||
# loadmodule /opt/redis-stack/lib/rejson.so
|
||||
# loadmodule /opt/redis-stack/lib/redisearch.so
|
||||
# loadmodule /opt/redis-stack/lib/redistimeseries.so
|
||||
# loadmodule /opt/redis-stack/lib/redisbloom.so
|
||||
+9
-22
@@ -11,7 +11,7 @@ all: prepare_source
|
||||
get_source:
|
||||
$(call submake,$@)
|
||||
|
||||
prepare_source: get_source handle-werrors setup_environment
|
||||
prepare_source: get_source setup_environment
|
||||
|
||||
clean:
|
||||
$(call submake,$@)
|
||||
@@ -25,14 +25,14 @@ pristine:
|
||||
install:
|
||||
$(call submake,$@)
|
||||
|
||||
setup_environment: install-rust handle-werrors
|
||||
setup_environment: install-rust
|
||||
|
||||
clean_environment: uninstall-rust
|
||||
|
||||
# Keep all of the Rust stuff in one place
|
||||
install-rust:
|
||||
ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
|
||||
@RUST_VERSION=1.88.0; \
|
||||
@RUST_VERSION=1.94.0; \
|
||||
ARCH="$$(uname -m)"; \
|
||||
if ldd --version 2>&1 | grep -q musl; then LIBC_TYPE="musl"; else LIBC_TYPE="gnu"; fi; \
|
||||
echo "Detected architecture: $${ARCH} and libc: $${LIBC_TYPE}"; \
|
||||
@@ -40,24 +40,24 @@ ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
|
||||
'x86_64') \
|
||||
if [ "$${LIBC_TYPE}" = "musl" ]; then \
|
||||
RUST_INSTALLER="rust-$${RUST_VERSION}-x86_64-unknown-linux-musl"; \
|
||||
RUST_SHA256="200bcf3b5d574caededba78c9ea9d27e7afc5c6df4154ed0551879859be328e1"; \
|
||||
RUST_SHA256="9a358120ce1491a4d5b7f71a41e4e97b380b5db5d4ec31f7110f5b3090bd3d55"; \
|
||||
else \
|
||||
RUST_INSTALLER="rust-$${RUST_VERSION}-x86_64-unknown-linux-gnu"; \
|
||||
RUST_SHA256="7b5437c1d18a174faae253a18eac22c32288dccfc09ff78d5ee99b7467e21bca"; \
|
||||
RUST_SHA256="e8fa4185f3ef6ae32725ff638b1ecdbff28f5d651dc0b3111e2539350d03b15a"; \
|
||||
fi ;; \
|
||||
'aarch64') \
|
||||
if [ "$${LIBC_TYPE}" = "musl" ]; then \
|
||||
RUST_INSTALLER="rust-$${RUST_VERSION}-aarch64-unknown-linux-musl"; \
|
||||
RUST_SHA256="f8b3a158f9e5e8cc82e4d92500dd2738ac7d8b5e66e0f18330408856235dec35"; \
|
||||
RUST_SHA256="008b3f0fc4175c956ecbfa4e0c48865ec3f953741b2926e75e8ded7e3adfdb19"; \
|
||||
else \
|
||||
RUST_INSTALLER="rust-$${RUST_VERSION}-aarch64-unknown-linux-gnu"; \
|
||||
RUST_SHA256="d5decc46123eb888f809f2ee3b118d13586a37ffad38afaefe56aa7139481d34"; \
|
||||
RUST_SHA256="c6fd6d1c925ed986df3b2c0b89bbc90ce15afb62e4d522a054e7d50c856b3c1a"; \
|
||||
fi ;; \
|
||||
*) echo >&2 "Unsupported architecture: '$${ARCH}'"; exit 1 ;; \
|
||||
esac; \
|
||||
echo "Downloading and installing Rust standalone installer: $${RUST_INSTALLER}"; \
|
||||
wget --quiet -O $${RUST_INSTALLER}.tar.xz https://static.rust-lang.org/dist/$${RUST_INSTALLER}.tar.xz; \
|
||||
echo "$${RUST_SHA256} $${RUST_INSTALLER}.tar.xz" | sha256sum -c --quiet || { echo "Rust standalone installer checksum failed!"; exit 1; }; \
|
||||
echo "$${RUST_SHA256} $${RUST_INSTALLER}.tar.xz" | sha256sum -c --status || { echo "Rust standalone installer checksum failed!"; exit 1; }; \
|
||||
tar -xf $${RUST_INSTALLER}.tar.xz; \
|
||||
(cd $${RUST_INSTALLER} && ./install.sh); \
|
||||
rm -rf $${RUST_INSTALLER}
|
||||
@@ -74,17 +74,4 @@ ifeq ($(INSTALL_RUST_TOOLCHAIN),yes)
|
||||
fi
|
||||
endif
|
||||
|
||||
handle-werrors: get_source
|
||||
ifeq ($(DISABLE_WERRORS),yes)
|
||||
@echo "Disabling -Werror for all modules"
|
||||
@for dir in $(SUBDIRS); do \
|
||||
echo "Processing $$dir"; \
|
||||
find $$dir/src -type f \
|
||||
\( -name "Makefile" \
|
||||
-o -name "*.mk" \
|
||||
-o -name "CMakeLists.txt" \) \
|
||||
-exec sed -i 's/-Werror//g' {} +; \
|
||||
done
|
||||
endif
|
||||
|
||||
.PHONY: all clean distclean install $(SUBDIRS) setup_environment clean_environment install-rust uninstall-rust handle-werrors
|
||||
.PHONY: all clean distclean install $(SUBDIRS) setup_environment clean_environment install-rust uninstall-rust
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SRC_DIR = src
|
||||
MODULE_VERSION = v8.3.90
|
||||
MODULE_VERSION = v8.8.0
|
||||
MODULE_REPO = https://github.com/redisbloom/redisbloom
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redisbloom.so
|
||||
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
SRC_DIR = src
|
||||
MODULE_VERSION = v8.3.90
|
||||
MODULE_VERSION = v8.8.0
|
||||
MODULE_REPO = https://github.com/redisearch/redisearch
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/search-community/redisearch.so
|
||||
|
||||
include ../common.mk
|
||||
# Enable link-time optimization for RediSearch by default. Override with LTO=0.
|
||||
LTO ?= 1
|
||||
export LTO
|
||||
|
||||
# Use the committed C headers for Rust modules, rather than regenerating them
|
||||
# from Rust source. Override with REDISEARCH_GENERATE_HEADERS=1.
|
||||
REDISEARCH_GENERATE_HEADERS ?= 0
|
||||
export REDISEARCH_GENERATE_HEADERS
|
||||
|
||||
# Set INLINE_LSE_ATOMICS=1 for perf improvement on common ARM CPUs (i.e. Graviton2/3/4); no effect on x86 or macOS.
|
||||
# Default 0 keeps the binary runnable on pre-Armv8.1-a cores (Cortex-A72, Graviton1, RPi4) that would otherwise SIGILL at module load.
|
||||
INLINE_LSE_ATOMICS ?= 0
|
||||
export INLINE_LSE_ATOMICS
|
||||
|
||||
include ../common.mk
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SRC_DIR = src
|
||||
MODULE_VERSION = v8.3.90
|
||||
MODULE_VERSION = v8.8.0
|
||||
MODULE_REPO = https://github.com/redisjson/redisjson
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/rejson.so
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SRC_DIR = src
|
||||
MODULE_VERSION = v8.3.90
|
||||
MODULE_VERSION = v8.8.0
|
||||
MODULE_REPO = https://github.com/redistimeseries/redistimeseries
|
||||
TARGET_MODULE = $(SRC_DIR)/bin/$(FULL_VARIANT)/redistimeseries.so
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ performed in the background, while the command is executed in the main thread.
|
||||
|
||||
`NOQUANT` forces the vector to be created (in the first VADD call to a given key) without integer 8 quantization, which is otherwise the default.
|
||||
|
||||
`BIN` forces the vector to use binary quantization instead of int8. This is much faster and uses less memory, but has impacts on the recall quality.
|
||||
`BIN` forces the vector to use binary quantization instead of int8. This is much faster and uses less memory, but has impacts on the recall quality. The distance is computed as normalized Hamming distance (`hamming_bits * 2 / dim`), yielding values in [0, 2] consistent with cosine distance semantics, not raw Hamming bit counts.
|
||||
|
||||
`Q8` forces the vector to use signed 8 bit quantization. This is the default, and the option only exists in order to make sure to check at insertion time if the vector set is of the same format.
|
||||
|
||||
@@ -721,10 +721,6 @@ During Vector Sets testing, we discovered that often clients introduce considera
|
||||
|
||||
Switching from `VALUES` to `FP32` as a method for transmitting vectors may easily provide 10-20x speedups.
|
||||
|
||||
# Known bugs
|
||||
|
||||
* Replication code is pretty much untested, and very vanilla (replicating the commands verbatim).
|
||||
|
||||
# Implementation details
|
||||
|
||||
Vector sets are based on the `hnsw.c` implementation of the HNSW data structure with extensions for speed and functionality.
|
||||
|
||||
+330
-29
@@ -47,28 +47,45 @@
|
||||
#include "hnsw.h"
|
||||
#include "mixer.h"
|
||||
|
||||
/* Check if we can compile SIMD code with function attributes */
|
||||
#if defined (__x86_64__) && ((defined(__GNUC__) && __GNUC__ >= 5) || (defined(__clang__) && __clang_major__ >= 4))
|
||||
#if defined(__has_attribute) && __has_attribute(target)
|
||||
#define HAVE_AVX2
|
||||
#define HAVE_AVX512
|
||||
#endif
|
||||
/* Check if we can compile SIMD code with function attributes.
|
||||
* This defines HAVE_AVX2, HAVE_AVX512, and HAVE_POPCNT when the compiler
|
||||
* supports the target() attribute for runtime CPU feature dispatch. */
|
||||
#if defined(__x86_64__) && ((defined(__GNUC__) && __GNUC__ >= 5) || (defined(__clang__) && __clang_major__ >= 4))
|
||||
#if defined(__has_attribute) && __has_attribute(target)
|
||||
#define HAVE_AVX2
|
||||
#define HAVE_AVX512
|
||||
#define HAVE_POPCNT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined (HAVE_AVX2)
|
||||
#if defined(HAVE_POPCNT)
|
||||
#define ATTRIBUTE_TARGET_POPCNT __attribute__((target("popcnt")))
|
||||
#define VSET_USE_POPCNT __builtin_cpu_supports("popcnt")
|
||||
#else
|
||||
#define ATTRIBUTE_TARGET_POPCNT
|
||||
#define VSET_USE_POPCNT 0
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_AVX2)
|
||||
#define ATTRIBUTE_TARGET_AVX2 __attribute__((target("avx2,fma")))
|
||||
#define ATTRIBUTE_TARGET_AVX2_POPCNT __attribute__((target("avx2,fma,popcnt")))
|
||||
#define VSET_USE_AVX2 (__builtin_cpu_supports("avx2") && __builtin_cpu_supports("fma"))
|
||||
#else
|
||||
#define ATTRIBUTE_TARGET_AVX2
|
||||
#define ATTRIBUTE_TARGET_AVX2_POPCNT
|
||||
#define VSET_USE_AVX2 0
|
||||
#endif
|
||||
|
||||
#if defined (HAVE_AVX512)
|
||||
#define ATTRIBUTE_TARGET_AVX512 __attribute__((target("avx512f,fma")))
|
||||
#define VSET_USE_AVX512 (__builtin_cpu_supports("avx512f"))
|
||||
#define ATTRIBUTE_TARGET_AVX512 __attribute__((target("avx512f,avx512bw,fma")))
|
||||
#define ATTRIBUTE_TARGET_AVX512_VPOPCNT __attribute__((target("avx512f,fma,avx512vpopcntdq,popcnt")))
|
||||
#define VSET_USE_AVX512 (__builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512bw"))
|
||||
#define VSET_USE_AVX512_VPOPCNT (__builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512vpopcntdq"))
|
||||
#else
|
||||
#define ATTRIBUTE_TARGET_AVX512
|
||||
#define ATTRIBUTE_TARGET_AVX512_VPOPCNT
|
||||
#define VSET_USE_AVX512 0
|
||||
#define VSET_USE_AVX512_VPOPCNT 0
|
||||
#endif
|
||||
|
||||
/* Include SIMD headers when supported */
|
||||
@@ -88,6 +105,15 @@
|
||||
|
||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
/* Define likely macro if not already defined */
|
||||
#ifndef likely
|
||||
#if __GNUC__ >= 3
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#else
|
||||
#define likely(x) (x)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Algorithm parameters. */
|
||||
|
||||
#define HNSW_P 0.25 /* Probability of level increase. */
|
||||
@@ -221,6 +247,55 @@ float pq_max_distance(pqueue *pq) {
|
||||
|
||||
/* ============================ HNSW algorithm ============================== */
|
||||
|
||||
/* Check if CPU supports POPCNT instruction - cached per thread */
|
||||
static inline int hnsw_cpu_supports_popcnt(void) {
|
||||
#if defined(HAVE_POPCNT)
|
||||
static __thread int popcnt_supported = -1;
|
||||
if (popcnt_supported == -1) {
|
||||
popcnt_supported = __builtin_cpu_supports("popcnt");
|
||||
}
|
||||
return popcnt_supported;
|
||||
#else
|
||||
return 0; /* Assume CPU does not support POPCNT if __builtin_cpu_supports() is not available. */
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Manual popcount implementation for platforms without POPCNT support */
|
||||
static inline int hnsw_popcount64(uint64_t x) {
|
||||
x = (x & 0x5555555555555555) + ((x >> 1) & 0x5555555555555555);
|
||||
x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
|
||||
x = (x & 0x0F0F0F0F0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F0F0F0F0F);
|
||||
x = (x & 0x00FF00FF00FF00FF) + ((x >> 8) & 0x00FF00FF00FF00FF);
|
||||
x = (x & 0x0000FFFF0000FFFF) + ((x >> 16) & 0x0000FFFF0000FFFF);
|
||||
x = (x & 0x00000000FFFFFFFF) + ((x >> 32) & 0x00000000FFFFFFFF);
|
||||
return x;
|
||||
}
|
||||
|
||||
/* Optimized popcount function that uses hardware POPCNT instruction when available,
|
||||
* falling back to a software implementation when necessary. The CPU feature detection
|
||||
* result is cached per thread for better performance. */
|
||||
ATTRIBUTE_TARGET_POPCNT
|
||||
static inline int hnsw_popcount(uint64_t x) {
|
||||
if (likely(hnsw_cpu_supports_popcnt())) {
|
||||
return __builtin_popcountll(x);
|
||||
} else {
|
||||
return hnsw_popcount64(x);
|
||||
}
|
||||
}
|
||||
|
||||
/* Binary vectors distance function that uses POPCNT when available */
|
||||
ATTRIBUTE_TARGET_POPCNT
|
||||
static inline float hnsw_vectors_distance_bin(const uint64_t *x, const uint64_t *y, uint32_t dim) {
|
||||
uint32_t len = (dim+63)/64;
|
||||
uint32_t opposite = 0;
|
||||
|
||||
for (uint32_t j = 0; j < len; j++) {
|
||||
uint64_t xor = x[j]^y[j];
|
||||
opposite += hnsw_popcount(xor);
|
||||
}
|
||||
return (float)opposite*2/dim;
|
||||
}
|
||||
|
||||
#if defined(HAVE_AVX512)
|
||||
/* AVX512 optimized dot product for float vectors */
|
||||
ATTRIBUTE_TARGET_AVX512
|
||||
@@ -347,8 +422,155 @@ float vectors_distance_float(const float *x, const float *y, uint32_t dim) {
|
||||
}
|
||||
|
||||
/* Q8 quants dotproduct. We do integer math and later fix it by range. */
|
||||
#if defined(HAVE_AVX512)
|
||||
/* AVX512 optimized dot product for Q8 vectors */
|
||||
ATTRIBUTE_TARGET_AVX512
|
||||
float vectors_distance_q8_avx512(const int8_t *x, const int8_t *y, uint32_t dim,
|
||||
float range_a, float range_b) {
|
||||
// Handle zero vectors special case.
|
||||
if (range_a == 0 || range_b == 0) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
const float scale_product = (range_a/127) * (range_b/127);
|
||||
__m512i sum = _mm512_setzero_si512();
|
||||
uint32_t i;
|
||||
|
||||
/* Process 64 int8 elements at a time with AVX512 */
|
||||
for (i = 0; i + 63 < dim; i += 64) {
|
||||
/* Load 64 int8 values */
|
||||
__m512i vx = _mm512_loadu_si512((__m512i*)&x[i]);
|
||||
__m512i vy = _mm512_loadu_si512((__m512i*)&y[i]);
|
||||
|
||||
/* Unpack and multiply-add in 32-bit precision
|
||||
* This is done in two steps: lower 32 bytes and upper 32 bytes */
|
||||
|
||||
/* Process lower 32 bytes (256 bits) */
|
||||
__m256i vx_lo = _mm512_extracti64x4_epi64(vx, 0);
|
||||
__m256i vy_lo = _mm512_extracti64x4_epi64(vy, 0);
|
||||
|
||||
/* Extend int8 to int16 */
|
||||
__m512i vx_lo_16 = _mm512_cvtepi8_epi16(vx_lo);
|
||||
__m512i vy_lo_16 = _mm512_cvtepi8_epi16(vy_lo);
|
||||
|
||||
/* Multiply and accumulate to int32 */
|
||||
__m512i prod_lo = _mm512_madd_epi16(vx_lo_16, vy_lo_16);
|
||||
sum = _mm512_add_epi32(sum, prod_lo);
|
||||
|
||||
/* Process upper 32 bytes (256 bits) */
|
||||
__m256i vx_hi = _mm512_extracti64x4_epi64(vx, 1);
|
||||
__m256i vy_hi = _mm512_extracti64x4_epi64(vy, 1);
|
||||
|
||||
__m512i vx_hi_16 = _mm512_cvtepi8_epi16(vx_hi);
|
||||
__m512i vy_hi_16 = _mm512_cvtepi8_epi16(vy_hi);
|
||||
|
||||
__m512i prod_hi = _mm512_madd_epi16(vx_hi_16, vy_hi_16);
|
||||
sum = _mm512_add_epi32(sum, prod_hi);
|
||||
}
|
||||
|
||||
/* Horizontal sum of the 16 int32 elements in sum */
|
||||
int32_t dot = _mm512_reduce_add_epi32(sum);
|
||||
|
||||
/* Handle remaining elements */
|
||||
for (; i < dim; i++) {
|
||||
dot += ((int32_t)x[i]) * ((int32_t)y[i]);
|
||||
}
|
||||
|
||||
/* Convert to original range */
|
||||
float dotf = dot * scale_product;
|
||||
float distance = 1.0f - dotf;
|
||||
|
||||
/* Clamp distance to [0, 2] */
|
||||
if (distance < 0) distance = 0;
|
||||
else if (distance > 2) distance = 2;
|
||||
return distance;
|
||||
}
|
||||
#endif /* HAVE_AVX512 */
|
||||
|
||||
#if defined(HAVE_AVX2)
|
||||
/* AVX2 optimized dot product for Q8 vectors */
|
||||
ATTRIBUTE_TARGET_AVX2
|
||||
float vectors_distance_q8_avx2(const int8_t *x, const int8_t *y, uint32_t dim,
|
||||
float range_a, float range_b) {
|
||||
// Handle zero vectors special case.
|
||||
if (range_a == 0 || range_b == 0) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
const float scale_product = (range_a/127) * (range_b/127);
|
||||
__m256i sum = _mm256_setzero_si256();
|
||||
uint32_t i;
|
||||
|
||||
/* Process 32 int8 elements at a time with AVX2 */
|
||||
for (i = 0; i + 31 < dim; i += 32) {
|
||||
/* Load 32 int8 values */
|
||||
__m256i vx = _mm256_loadu_si256((__m256i*)&x[i]);
|
||||
__m256i vy = _mm256_loadu_si256((__m256i*)&y[i]);
|
||||
|
||||
/* Split into lower and upper 16 bytes */
|
||||
__m128i vx_lo = _mm256_extracti128_si256(vx, 0);
|
||||
__m128i vy_lo = _mm256_extracti128_si256(vy, 0);
|
||||
__m128i vx_hi = _mm256_extracti128_si256(vx, 1);
|
||||
__m128i vy_hi = _mm256_extracti128_si256(vy, 1);
|
||||
|
||||
/* Extend int8 to int16 for lower half */
|
||||
__m256i vx_lo_16 = _mm256_cvtepi8_epi16(vx_lo);
|
||||
__m256i vy_lo_16 = _mm256_cvtepi8_epi16(vy_lo);
|
||||
|
||||
/* Multiply and accumulate (madd does multiply adjacent pairs and add) */
|
||||
__m256i prod_lo = _mm256_madd_epi16(vx_lo_16, vy_lo_16);
|
||||
sum = _mm256_add_epi32(sum, prod_lo);
|
||||
|
||||
/* Extend int8 to int16 for upper half */
|
||||
__m256i vx_hi_16 = _mm256_cvtepi8_epi16(vx_hi);
|
||||
__m256i vy_hi_16 = _mm256_cvtepi8_epi16(vy_hi);
|
||||
|
||||
__m256i prod_hi = _mm256_madd_epi16(vx_hi_16, vy_hi_16);
|
||||
sum = _mm256_add_epi32(sum, prod_hi);
|
||||
}
|
||||
|
||||
/* Horizontal sum of the 8 int32 elements in sum */
|
||||
__m128i sum_hi = _mm256_extracti128_si256(sum, 1);
|
||||
__m128i sum_lo = _mm256_castsi256_si128(sum);
|
||||
__m128i sum_128 = _mm_add_epi32(sum_hi, sum_lo);
|
||||
|
||||
sum_128 = _mm_hadd_epi32(sum_128, sum_128);
|
||||
sum_128 = _mm_hadd_epi32(sum_128, sum_128);
|
||||
|
||||
int32_t dot = _mm_cvtsi128_si32(sum_128);
|
||||
|
||||
/* Handle remaining elements */
|
||||
for (; i < dim; i++) {
|
||||
dot += ((int32_t)x[i]) * ((int32_t)y[i]);
|
||||
}
|
||||
|
||||
/* Convert to original range */
|
||||
float dotf = dot * scale_product;
|
||||
float distance = 1.0f - dotf;
|
||||
|
||||
/* Clamp distance to [0, 2] */
|
||||
if (distance < 0) distance = 0;
|
||||
else if (distance > 2) distance = 2;
|
||||
return distance;
|
||||
}
|
||||
#endif /* HAVE_AVX2 */
|
||||
|
||||
/* Q8 dot product: automatically selects best available implementation */
|
||||
float vectors_distance_q8(const int8_t *x, const int8_t *y, uint32_t dim,
|
||||
float range_a, float range_b) {
|
||||
#if defined(HAVE_AVX512)
|
||||
if (dim >= 64 && VSET_USE_AVX512) {
|
||||
return vectors_distance_q8_avx512(x, y, dim, range_a, range_b);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_AVX2)
|
||||
if (dim >= 32 && VSET_USE_AVX2) {
|
||||
return vectors_distance_q8_avx2(x, y, dim, range_a, range_b);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Fallback to scalar implementation */
|
||||
// Handle zero vectors special case.
|
||||
if (range_a == 0 || range_b == 0) {
|
||||
/* Zero vector distance from anything is 1.0
|
||||
@@ -389,25 +611,103 @@ float vectors_distance_q8(const int8_t *x, const int8_t *y, uint32_t dim,
|
||||
return distance;
|
||||
}
|
||||
|
||||
static inline int popcount64(uint64_t x) {
|
||||
x = (x & 0x5555555555555555) + ((x >> 1) & 0x5555555555555555);
|
||||
x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
|
||||
x = (x & 0x0F0F0F0F0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F0F0F0F0F);
|
||||
x = (x & 0x00FF00FF00FF00FF) + ((x >> 8) & 0x00FF00FF00FF00FF);
|
||||
x = (x & 0x0000FFFF0000FFFF) + ((x >> 16) & 0x0000FFFF0000FFFF);
|
||||
x = (x & 0x00000000FFFFFFFF) + ((x >> 32) & 0x00000000FFFFFFFF);
|
||||
return x;
|
||||
}
|
||||
|
||||
/* Binary vectors distance. */
|
||||
float vectors_distance_bin(const uint64_t *x, const uint64_t *y, uint32_t dim) {
|
||||
#if defined(HAVE_AVX512) && defined(HAVE_POPCNT)
|
||||
/* AVX-512 vectorized binary distance calculation using VPOPCNTDQ.
|
||||
* Processes 8 uint64_t (512 bits) per iteration.
|
||||
*
|
||||
* Uses _mm512_popcnt_epi64 hardware popcount instruction which requires
|
||||
* AVX512VPOPCNTDQ extension
|
||||
*/
|
||||
ATTRIBUTE_TARGET_AVX512_VPOPCNT
|
||||
static float vectors_distance_bin_avx512_vpopcnt(const uint64_t *x, const uint64_t *y, uint32_t dim) {
|
||||
uint32_t len = (dim+63)/64;
|
||||
uint32_t opposite = 0;
|
||||
for (uint32_t j = 0; j < len; j++) {
|
||||
uint64_t xor = x[j]^y[j];
|
||||
opposite += popcount64(xor);
|
||||
uint32_t j = 0;
|
||||
|
||||
/* Process 8 uint64_t (512 bits) at a time with hardware popcount */
|
||||
if (len >= 8) {
|
||||
__m512i sum = _mm512_setzero_si512();
|
||||
|
||||
for (; j + 7 < len; j += 8) {
|
||||
__m512i vx = _mm512_loadu_si512((__m512i*)&x[j]);
|
||||
__m512i vy = _mm512_loadu_si512((__m512i*)&y[j]);
|
||||
__m512i vxor = _mm512_xor_si512(vx, vy);
|
||||
|
||||
/* Hardware popcount for 64-bit integers (AVX512VPOPCNTDQ) */
|
||||
__m512i popcnt = _mm512_popcnt_epi64(vxor);
|
||||
sum = _mm512_add_epi64(sum, popcnt);
|
||||
}
|
||||
|
||||
/* Horizontal sum: reduce 8x 64-bit integers to scalar */
|
||||
opposite = _mm512_reduce_add_epi64(sum);
|
||||
}
|
||||
return (float)opposite*2/dim;
|
||||
|
||||
/* Handle remaining elements */
|
||||
for (; j < len; j++) {
|
||||
uint64_t xor = x[j] ^ y[j];
|
||||
opposite += __builtin_popcountll(xor);
|
||||
}
|
||||
|
||||
return (float)opposite * 2.0f / dim;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_AVX2) && defined(HAVE_POPCNT)
|
||||
/* AVX2 vectorized binary distance calculation.
|
||||
* Processes 4 uint64_t (256 bits) per iteration. */
|
||||
ATTRIBUTE_TARGET_AVX2_POPCNT
|
||||
static float vectors_distance_bin_avx2(const uint64_t *x, const uint64_t *y, uint32_t dim) {
|
||||
uint32_t len = (dim+63)/64;
|
||||
uint32_t opposite = 0;
|
||||
uint32_t j = 0;
|
||||
|
||||
/* Process 4 uint64_t (256 bits) at a time */
|
||||
if (len >= 4) {
|
||||
for (; j + 3 < len; j += 4) {
|
||||
__m256i vx = _mm256_loadu_si256((__m256i*)&x[j]);
|
||||
__m256i vy = _mm256_loadu_si256((__m256i*)&y[j]);
|
||||
__m256i vxor = _mm256_xor_si256(vx, vy);
|
||||
|
||||
/* Extract and use hardware POPCNT instruction */
|
||||
uint64_t xor_vals[4];
|
||||
_mm256_storeu_si256((__m256i*)xor_vals, vxor);
|
||||
|
||||
opposite += __builtin_popcountll(xor_vals[0]);
|
||||
opposite += __builtin_popcountll(xor_vals[1]);
|
||||
opposite += __builtin_popcountll(xor_vals[2]);
|
||||
opposite += __builtin_popcountll(xor_vals[3]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle remaining elements */
|
||||
for (; j < len; j++) {
|
||||
uint64_t xor = x[j] ^ y[j];
|
||||
opposite += __builtin_popcountll(xor);
|
||||
}
|
||||
|
||||
return (float)opposite * 2.0f / dim;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Binary vectors distance with SIMD dispatch. */
|
||||
ATTRIBUTE_TARGET_POPCNT
|
||||
float vectors_distance_bin(const uint64_t *x, const uint64_t *y, uint32_t dim) {
|
||||
#if defined(HAVE_AVX512) && defined(HAVE_POPCNT)
|
||||
/* AVX-512 with VPOPCNTDQ */
|
||||
if (dim >= 512 && VSET_USE_AVX512_VPOPCNT) {
|
||||
return vectors_distance_bin_avx512_vpopcnt(x, y, dim);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_AVX2) && defined(HAVE_POPCNT)
|
||||
/* AVX2 path: processes 4 uint64_t (256 bits) per iteration */
|
||||
if (dim >= 256 && VSET_USE_AVX2 && VSET_USE_POPCNT) {
|
||||
return vectors_distance_bin_avx2(x, y, dim);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Fallback to scalar implementation with runtime POPCNT detection */
|
||||
return hnsw_vectors_distance_bin(x, y, dim);
|
||||
}
|
||||
|
||||
/* Dot product between nodes. Will call the right version depending on the
|
||||
@@ -2582,26 +2882,27 @@ hnswCursor *hnsw_cursor_init(HNSW *index) {
|
||||
/* Free the cursor. Can be called both at the end of the iteration, when
|
||||
* hnsw_cursor_next() returned NULL, or before. */
|
||||
void hnsw_cursor_free(hnswCursor *cursor) {
|
||||
if (pthread_rwlock_wrlock(&cursor->index->global_lock) != 0) {
|
||||
HNSW *index = cursor->index;
|
||||
if (pthread_rwlock_wrlock(&index->global_lock) != 0) {
|
||||
// No easy way to recover from that. We will leak memory.
|
||||
return;
|
||||
}
|
||||
|
||||
hnswCursor *x = cursor->index->cursors;
|
||||
hnswCursor *x = index->cursors;
|
||||
hnswCursor *prev = NULL;
|
||||
while(x) {
|
||||
if (x == cursor) {
|
||||
if (prev)
|
||||
prev->next = cursor->next;
|
||||
else
|
||||
cursor->index->cursors = cursor->next;
|
||||
index->cursors = cursor->next;
|
||||
hfree(cursor);
|
||||
break;
|
||||
}
|
||||
prev = x;
|
||||
x = x->next;
|
||||
}
|
||||
pthread_rwlock_unlock(&cursor->index->global_lock);
|
||||
pthread_rwlock_unlock(&index->global_lock);
|
||||
}
|
||||
|
||||
/* Acquire a lock to use the cursor. Returns 1 if the lock was acquired
|
||||
|
||||
@@ -96,10 +96,10 @@ class TestCase:
|
||||
self.error_details = None
|
||||
self.test_key = f"test:{self.__class__.__name__.lower()}"
|
||||
# Primary Redis instance
|
||||
self.redis = redis.Redis(port=primary_port,db=9)
|
||||
self.redis = redis.Redis(port=primary_port,protocol=2,db=9)
|
||||
self.redis3 = redis.Redis(port=primary_port,protocol=3,db=9)
|
||||
# Replica Redis instance
|
||||
self.replica = redis.Redis(port=replica_port,db=9)
|
||||
self.replica = redis.Redis(port=replica_port,protocol=2,db=9)
|
||||
# Replication status
|
||||
self.replication_setup = False
|
||||
# Ports
|
||||
@@ -174,7 +174,8 @@ class TestCase:
|
||||
|
||||
def find_test_classes(primary_port, replica_port):
|
||||
test_classes = []
|
||||
tests_dir = 'tests'
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
tests_dir = os.path.join(script_dir, 'tests')
|
||||
|
||||
if not os.path.exists(tests_dir):
|
||||
return []
|
||||
@@ -285,6 +286,7 @@ def run_tests():
|
||||
else:
|
||||
if total-skipped-passed > 0:
|
||||
print(colored(f"{total-skipped-passed} TESTS FAILED!", "red"))
|
||||
sys.exit(1)
|
||||
if skipped > 0:
|
||||
print(colored(f"{skipped} TESTS SKIPPED!", "yellow"))
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
from test import TestCase
|
||||
|
||||
class BinVectorization(TestCase):
|
||||
def getname(self):
|
||||
return "Binary quantization: verify vectorized vs scalar paths produce consistent results"
|
||||
|
||||
def test(self):
|
||||
# Test with different dimensions to exercise different code paths:
|
||||
# - dim=1: Edge case for minimal valid dimension (scalar path)
|
||||
# - dim=64: Exact alignment boundary, one uint64_t word (scalar path)
|
||||
# - dim=128: Scalar path (< 256)
|
||||
# - dim=384: AVX2 path if available (>= 256, < 512)
|
||||
# - dim=768: AVX512 path if available (>= 512)
|
||||
# Note: dim=0 is not tested as it's invalid input (division by zero)
|
||||
|
||||
test_dims = [1, 64, 128, 384, 768]
|
||||
|
||||
for dim in test_dims:
|
||||
# Add two very similar vectors, one different
|
||||
vec1 = [1.0] * dim
|
||||
vec2 = [0.99] * dim # Very similar to vec1
|
||||
vec3 = [-1.0] * dim # Opposite direction - should have low similarity
|
||||
|
||||
# Add vectors with binary quantization
|
||||
self.redis.execute_command('VADD', f'{self.test_key}:dim{dim}', 'VALUES', dim,
|
||||
*[str(x) for x in vec1], f'{self.test_key}:dim{dim}:item:1', 'BIN')
|
||||
self.redis.execute_command('VADD', f'{self.test_key}:dim{dim}', 'VALUES', dim,
|
||||
*[str(x) for x in vec2], f'{self.test_key}:dim{dim}:item:2', 'BIN')
|
||||
self.redis.execute_command('VADD', f'{self.test_key}:dim{dim}', 'VALUES', dim,
|
||||
*[str(x) for x in vec3], f'{self.test_key}:dim{dim}:item:3', 'BIN')
|
||||
|
||||
# Query similarity
|
||||
result = self.redis.execute_command('VSIM', f'{self.test_key}:dim{dim}', 'VALUES', dim,
|
||||
*[str(x) for x in vec1], 'WITHSCORES')
|
||||
|
||||
# Convert results to dictionary
|
||||
results_dict = {}
|
||||
for i in range(0, len(result), 2):
|
||||
key = result[i].decode()
|
||||
score = float(result[i+1])
|
||||
results_dict[key] = score
|
||||
|
||||
# Verify results are consistent across dimensions
|
||||
# Self-similarity should be very high (binary quantization is less precise)
|
||||
assert results_dict[f'{self.test_key}:dim{dim}:item:1'] > 0.99, \
|
||||
f"Dim {dim}: Self-similarity too low: {results_dict[f'{self.test_key}:dim{dim}:item:1']}"
|
||||
|
||||
# Similar vector should have high similarity (binary quant loses some precision)
|
||||
assert results_dict[f'{self.test_key}:dim{dim}:item:2'] > 0.95, \
|
||||
f"Dim {dim}: Similar vector similarity too low: {results_dict[f'{self.test_key}:dim{dim}:item:2']}"
|
||||
|
||||
# Opposite vector should have very low similarity
|
||||
assert results_dict[f'{self.test_key}:dim{dim}:item:3'] < 0.1, \
|
||||
f"Dim {dim}: Opposite vector similarity too high: {results_dict[f'{self.test_key}:dim{dim}:item:3']}"
|
||||
@@ -0,0 +1,129 @@
|
||||
from test import TestCase, generate_random_vector
|
||||
import struct
|
||||
import redis.exceptions
|
||||
|
||||
MAX_DIM = 65536
|
||||
|
||||
|
||||
class DimensionMaxLimitVaddAtLimit(TestCase):
|
||||
def getname(self):
|
||||
return "[regression] VADD VALUES dim == MAX_DIM accepted"
|
||||
|
||||
def estimated_runtime(self):
|
||||
return 0.5
|
||||
|
||||
def test(self):
|
||||
dim = MAX_DIM
|
||||
vec = generate_random_vector(dim)
|
||||
|
||||
result = self.redis.execute_command(
|
||||
'VADD', self.test_key,
|
||||
'VALUES', dim,
|
||||
*[str(x) for x in vec],
|
||||
f"{self.test_key}:item:maxdim")
|
||||
assert result == 1, "VADD with dimension at the limit should succeed"
|
||||
|
||||
|
||||
class DimensionMaxLimitVaddAboveLimit(TestCase):
|
||||
def getname(self):
|
||||
return "[regression] VADD VALUES dim > MAX_DIM rejected"
|
||||
|
||||
def estimated_runtime(self):
|
||||
return 0.1
|
||||
|
||||
def test(self):
|
||||
too_big_dim = MAX_DIM + 1
|
||||
too_big_vec = generate_random_vector(16)
|
||||
try:
|
||||
self.redis.execute_command(
|
||||
'VADD', self.test_key,
|
||||
'VALUES', too_big_dim,
|
||||
*[str(x) for x in too_big_vec],
|
||||
f"{self.test_key}:item:toolarge")
|
||||
assert False, "VADD with dimension above the limit should fail"
|
||||
except redis.exceptions.ResponseError as e:
|
||||
# parseVector returns NULL so caller uses the generic invalid spec error
|
||||
assert "invalid vector specification" in str(e), (
|
||||
f"Expected invalid vector specification error, got: {e}")
|
||||
|
||||
|
||||
class DimensionMaxLimitVsimAtLimit(TestCase):
|
||||
def getname(self):
|
||||
return "[regression] VSIM VALUES dim == MAX_DIM accepted"
|
||||
|
||||
def estimated_runtime(self):
|
||||
return 0.5
|
||||
|
||||
def test(self):
|
||||
# Insert a vector at the maximum allowed dimension, then query at the same dimension.
|
||||
dim = MAX_DIM
|
||||
base_vec = generate_random_vector(dim)
|
||||
|
||||
result = self.redis.execute_command(
|
||||
'VADD', self.test_key,
|
||||
'VALUES', dim,
|
||||
*[str(x) for x in base_vec],
|
||||
f"{self.test_key}:item:1")
|
||||
assert result == 1, "VADD with dimension at the limit should succeed"
|
||||
|
||||
query = generate_random_vector(dim)
|
||||
res = self.redis.execute_command(
|
||||
'VSIM', self.test_key,
|
||||
'VALUES', dim,
|
||||
*[str(x) for x in query],
|
||||
'COUNT', 1)
|
||||
assert isinstance(res, list), "VSIM with dimension at the limit should return a list"
|
||||
|
||||
|
||||
class DimensionMaxLimitVsimAboveLimit(TestCase):
|
||||
def getname(self):
|
||||
return "[regression] VSIM VALUES dim > MAX_DIM rejected"
|
||||
|
||||
def estimated_runtime(self):
|
||||
return 0.1
|
||||
|
||||
def test(self):
|
||||
# Create a small index, then issue a VSIM with an over-limit dimension.
|
||||
base_dim = 16
|
||||
base_vec = generate_random_vector(base_dim)
|
||||
result = self.redis.execute_command(
|
||||
'VADD', self.test_key,
|
||||
'VALUES', base_dim,
|
||||
*[str(x) for x in base_vec],
|
||||
f"{self.test_key}:item:1")
|
||||
assert result == 1, "VADD with base_dim should succeed"
|
||||
|
||||
too_big_dim = MAX_DIM + 1
|
||||
too_big_vec = generate_random_vector(16)
|
||||
try:
|
||||
self.redis.execute_command(
|
||||
'VSIM', self.test_key,
|
||||
'VALUES', too_big_dim,
|
||||
*[str(x) for x in too_big_vec],
|
||||
'COUNT', 1)
|
||||
assert False, "VSIM with dimension above the limit should fail"
|
||||
except redis.exceptions.ResponseError as e:
|
||||
assert "invalid vector specification" in str(e), (
|
||||
f"Expected invalid vector specification error in VSIM, got: {e}")
|
||||
|
||||
|
||||
class DimensionMaxLimitHugeDimension(TestCase):
|
||||
def getname(self):
|
||||
return "[regression] VADD VALUES absurdly large dim rejected"
|
||||
|
||||
def estimated_runtime(self):
|
||||
return 0.1
|
||||
|
||||
def test(self):
|
||||
# Extremely large dimension close to LLONG_MAX should also be rejected safely.
|
||||
huge_dim = 9223372036854775807 # LLONG_MAX from the original report
|
||||
try:
|
||||
self.redis.execute_command(
|
||||
'VADD', self.test_key,
|
||||
'VALUES', huge_dim,
|
||||
'0') # Just a dummy value; parseVector should reject based on dimension alone
|
||||
assert False, "VADD with absurdly large dimension should fail"
|
||||
except redis.exceptions.ResponseError as e:
|
||||
assert "invalid vector specification" in str(e), (
|
||||
f"Expected invalid vector specification error for huge dim, got: {e}")
|
||||
|
||||
@@ -65,3 +65,33 @@ class DimensionValidation(TestCase):
|
||||
assert False, "VSIM with wrong dimension should fail"
|
||||
except redis.exceptions.ResponseError as e:
|
||||
assert "Input dimension mismatch for projection" in str(e), f"Expected dimension mismatch error in VSIM, got: {e}"
|
||||
|
||||
class ReduceDimConstraintValidation(TestCase):
|
||||
def getname(self):
|
||||
return "[regression] VADD enforces reduce_dim <= dim"
|
||||
|
||||
def estimated_runtime(self):
|
||||
return 0.1
|
||||
|
||||
def test(self):
|
||||
import struct
|
||||
|
||||
dim = 16
|
||||
reduce_dim = dim + 1 # Intentionally larger than dim
|
||||
|
||||
# Build a simple FP32 vector of the given dimension.
|
||||
vec = [0.0] * dim
|
||||
vec_bytes = struct.pack(f'{dim}f', *vec)
|
||||
|
||||
try:
|
||||
self.redis.execute_command(
|
||||
'VADD', self.test_key,
|
||||
'REDUCE', reduce_dim,
|
||||
'FP32', vec_bytes,
|
||||
f'{self.test_key}:item:reducemismatch')
|
||||
assert False, "VADD with reduce_dim > dim should fail"
|
||||
except redis.exceptions.ResponseError as e:
|
||||
# Same generic validation error path as other vector spec problems.
|
||||
assert "invalid vector specification" in str(e), (
|
||||
f"Expected invalid vector error, got: {e}")
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
from test import TestCase
|
||||
|
||||
class Q8Similarity(TestCase):
|
||||
def getname(self):
|
||||
return "Q8 quantization: VSIM reported distance makes sense with 4D vectors"
|
||||
|
||||
def test(self):
|
||||
# Add two very similar vectors, one different
|
||||
# Using same test vectors as basic_similarity.py for comparison
|
||||
vec1 = [1, 0, 0, 0]
|
||||
vec2 = [0.99, 0.01, 0, 0]
|
||||
vec3 = [0.1, 1, -1, 0.5]
|
||||
|
||||
# Add vectors using VALUES format with Q8 quantization
|
||||
self.redis.execute_command('VADD', self.test_key, 'VALUES', 4,
|
||||
*[str(x) for x in vec1], f'{self.test_key}:item:1', 'Q8')
|
||||
self.redis.execute_command('VADD', self.test_key, 'VALUES', 4,
|
||||
*[str(x) for x in vec2], f'{self.test_key}:item:2', 'Q8')
|
||||
self.redis.execute_command('VADD', self.test_key, 'VALUES', 4,
|
||||
*[str(x) for x in vec3], f'{self.test_key}:item:3', 'Q8')
|
||||
|
||||
# Query similarity with vec1
|
||||
result = self.redis.execute_command('VSIM', self.test_key, 'VALUES', 4,
|
||||
*[str(x) for x in vec1], 'WITHSCORES')
|
||||
|
||||
# Convert results to dictionary
|
||||
results_dict = {}
|
||||
for i in range(0, len(result), 2):
|
||||
key = result[i].decode()
|
||||
score = float(result[i+1])
|
||||
results_dict[key] = score
|
||||
|
||||
# Verify results (same expectations as float32, allowing for quantization error)
|
||||
assert results_dict[f'{self.test_key}:item:1'] > 0.99, "Self-similarity should be very high (Q8)"
|
||||
assert results_dict[f'{self.test_key}:item:2'] > 0.99, "Similar vector should have high similarity (Q8)"
|
||||
assert results_dict[f'{self.test_key}:item:3'] < 0.80, "Not very similar vector should have low similarity (Q8)"
|
||||
|
||||
# Test extreme values with 512 dimensions to stress-test overflow safety
|
||||
vec4 = [1.0] * 512 # All +127 after quantization
|
||||
vec5 = [-1.0] * 512 # All -127 after quantization
|
||||
vec6 = [1.0, -1.0] * 256 # Alternating +127, -127
|
||||
|
||||
# Add vectors using VALUES format with Q8 quantization
|
||||
self.redis.execute_command('VADD', f'{self.test_key}:extreme', 'VALUES', 512,
|
||||
*[str(x) for x in vec4], f'{self.test_key}:extreme:vec4', 'Q8')
|
||||
self.redis.execute_command('VADD', f'{self.test_key}:extreme', 'VALUES', 512,
|
||||
*[str(x) for x in vec5], f'{self.test_key}:extreme:vec5', 'Q8')
|
||||
self.redis.execute_command('VADD', f'{self.test_key}:extreme', 'VALUES', 512,
|
||||
*[str(x) for x in vec6], f'{self.test_key}:extreme:vec6', 'Q8')
|
||||
|
||||
# Query vec4 against itself - worst-case positive accumulation (512 * 127 * 127 = 8,258,048)
|
||||
result_vec4 = self.redis.execute_command('VSIM', f'{self.test_key}:extreme', 'VALUES', 512,
|
||||
*[str(x) for x in vec4], 'WITHSCORES')
|
||||
results_vec4 = {}
|
||||
for i in range(0, len(result_vec4), 2):
|
||||
key = result_vec4[i].decode()
|
||||
score = float(result_vec4[i+1])
|
||||
results_vec4[key] = score
|
||||
|
||||
# Verify extreme value handling
|
||||
# VSIM returns similarity = 1.0 - distance/2.0, so:
|
||||
# - Distance 0 (identical) → similarity 1.0
|
||||
# - Distance 2 (opposite) → similarity 0.0
|
||||
assert results_vec4[f'{self.test_key}:extreme:vec4'] > 0.999, \
|
||||
f"vec4 self-similarity should be very high, got {results_vec4[f'{self.test_key}:extreme:vec4']}"
|
||||
assert results_vec4[f'{self.test_key}:extreme:vec5'] < 0.01, \
|
||||
f"vec4 vs vec5 (opposite extremes) should be near 0, got {results_vec4[f'{self.test_key}:extreme:vec5']}"
|
||||
|
||||
# Alternating pattern should result in mid-range similarity (perpendicular)
|
||||
assert 0.4 < results_vec4[f'{self.test_key}:extreme:vec6'] < 0.6, \
|
||||
f"vec4 vs vec6 (alternating) should be near 0.5, got {results_vec4[f'{self.test_key}:extreme:vec6']}"
|
||||
@@ -0,0 +1,85 @@
|
||||
from test import TestCase
|
||||
|
||||
class Q8Vectorization(TestCase):
|
||||
def getname(self):
|
||||
return "Q8 quantization: verify vectorized vs scalar paths produce consistent results"
|
||||
|
||||
def test(self):
|
||||
# Test with different dimensions to exercise different code paths and boundaries:
|
||||
# - dim=16: Scalar path (< 32)
|
||||
# - dim=31: Largest scalar-only dimension (boundary)
|
||||
# - dim=32: Smallest AVX2 dimension, no remainder (boundary)
|
||||
# - dim=33: AVX2 with 1-element remainder
|
||||
# - dim=63: AVX2 with 31-element remainder (largest AVX2-only)
|
||||
# - dim=64: Smallest AVX512 dimension, no remainder (boundary)
|
||||
# - dim=65: AVX512 with 1-element remainder
|
||||
# - dim=128: AVX512 path with no remainder
|
||||
# - dim=256, dim=512: Large dimensions to test overflow prevention
|
||||
|
||||
test_dims = [16, 31, 32, 33, 63, 64, 65, 128, 256, 512]
|
||||
|
||||
for dim in test_dims:
|
||||
key = f'{self.test_key}:dim{dim}'
|
||||
|
||||
# Test vectors with extreme values to verify overflow prevention:
|
||||
# vec1: all +1.0 -> quantizes to +127 (max positive int8)
|
||||
# vec2: all +0.99 -> quantizes to ~+126 (similar to vec1)
|
||||
# vec3: all -1.0 -> quantizes to -127/-128 (max negative int8)
|
||||
# vec4: alternating +1.0/-1.0 -> alternating +127/-127 (tests mixed signs)
|
||||
vec1 = [1.0] * dim # All max positive
|
||||
vec2 = [0.99] * dim # Similar to vec1
|
||||
vec3 = [-1.0] * dim # All max negative (opposite direction)
|
||||
vec4 = [1.0 if i % 2 == 0 else -1.0 for i in range(dim)] # Alternating extreme values
|
||||
|
||||
# Add vectors with Q8 quantization
|
||||
self.redis.execute_command('VADD', key, 'VALUES', dim,
|
||||
*[str(x) for x in vec1], f'{key}:item:1', 'Q8')
|
||||
self.redis.execute_command('VADD', key, 'VALUES', dim,
|
||||
*[str(x) for x in vec2], f'{key}:item:2', 'Q8')
|
||||
self.redis.execute_command('VADD', key, 'VALUES', dim,
|
||||
*[str(x) for x in vec3], f'{key}:item:3', 'Q8')
|
||||
self.redis.execute_command('VADD', key, 'VALUES', dim,
|
||||
*[str(x) for x in vec4], f'{key}:item:4', 'Q8')
|
||||
|
||||
# Query similarity using vec1 (all max positive values)
|
||||
# This exercises worst-case positive accumulation: dim * 127 * 127
|
||||
result = self.redis.execute_command('VSIM', key, 'VALUES', dim,
|
||||
*[str(x) for x in vec1], 'WITHSCORES')
|
||||
|
||||
# Convert results to dictionary
|
||||
results_dict = {}
|
||||
for i in range(0, len(result), 2):
|
||||
k = result[i].decode()
|
||||
score = float(result[i+1])
|
||||
results_dict[k] = score
|
||||
|
||||
# Verify results - these would be wrong if overflow occurred
|
||||
# Self-similarity should be ~1.0 (identical vectors)
|
||||
assert results_dict[f'{key}:item:1'] > 0.99, \
|
||||
f"Dim {dim}: Self-similarity too low: {results_dict[f'{key}:item:1']}"
|
||||
|
||||
# Similar vector should have high similarity
|
||||
assert results_dict[f'{key}:item:2'] > 0.99, \
|
||||
f"Dim {dim}: Similar vector similarity too low: {results_dict[f'{key}:item:2']}"
|
||||
|
||||
# Opposite vector should have very low similarity (~0.0)
|
||||
# With overflow bug, this could give incorrect positive values
|
||||
assert results_dict[f'{key}:item:3'] < 0.1, \
|
||||
f"Dim {dim}: Opposite vector similarity too high: {results_dict[f'{key}:item:3']}"
|
||||
|
||||
# Alternating vector: dot product sums to ~0, so similarity ~0.5
|
||||
# (127*127) + (127*-127) + ... = 0, normalized gives ~0.5
|
||||
assert 0.4 < results_dict[f'{key}:item:4'] < 0.6, \
|
||||
f"Dim {dim}: Alternating vector similarity unexpected: {results_dict[f'{key}:item:4']}"
|
||||
|
||||
# Also query with the alternating pattern to verify its self-similarity
|
||||
result_alt = self.redis.execute_command('VSIM', key, 'VALUES', dim,
|
||||
*[str(x) for x in vec4], 'WITHSCORES')
|
||||
results_alt = {}
|
||||
for i in range(0, len(result_alt), 2):
|
||||
k = result_alt[i].decode()
|
||||
score = float(result_alt[i+1])
|
||||
results_alt[k] = score
|
||||
|
||||
assert results_alt[f'{key}:item:4'] > 0.99, \
|
||||
f"Dim {dim}: Alternating self-similarity too low: {results_alt[f'{key}:item:4']}"
|
||||
@@ -0,0 +1,19 @@
|
||||
from test import TestCase
|
||||
|
||||
class VSIMDuplicateFilterLeak(TestCase):
|
||||
def getname(self):
|
||||
return "[regression] VSIM duplicate FILTER should not leak memory"
|
||||
|
||||
def test(self):
|
||||
self.redis.execute_command('VADD', self.test_key, 'VALUES', 3, 0.5774, 0.5774, 0.5774, 'elem1')
|
||||
self.redis.execute_command('VADD', self.test_key, 'VALUES', 3, 0.7071, 0.7071, 0.0, 'elem2')
|
||||
self.redis.execute_command('VSETATTR', self.test_key, 'elem1', '{"a": 1, "b": 2}')
|
||||
self.redis.execute_command('VSETATTR', self.test_key, 'elem2', '{"a": 2, "b": 3}')
|
||||
|
||||
# Duplicate FILTER: before the fix the first exprstate was
|
||||
# overwritten without exprFree(), leaking ~760 bytes per call.
|
||||
# Under ASAN/valgrind this shows up as a leak at server exit.
|
||||
for _ in range(100):
|
||||
self.redis.execute_command(
|
||||
'VSIM', self.test_key, 'VALUES', 3, 0.5774, 0.5774, 0.5774,
|
||||
'FILTER', '.a == 1', 'FILTER', '.b >= 1')
|
||||
@@ -0,0 +1,32 @@
|
||||
from test import TestCase
|
||||
import redis as redis_module
|
||||
|
||||
class VSIMFilterLeakOnOptionError(TestCase):
|
||||
def getname(self):
|
||||
return "[regression] VSIM FILTER expr freed on option parse error"
|
||||
|
||||
def test(self):
|
||||
self.redis.execute_command('VADD', self.test_key, 'VALUES', 3, 1, 0, 0, 'elem1')
|
||||
|
||||
# Valid FILTER followed by invalid option values. Before the fix,
|
||||
# error paths freed vec but not filter_expr, leaking the compiled
|
||||
# exprstate. Under ASAN/valgrind this shows up at server exit.
|
||||
error_cmds = [
|
||||
# invalid COUNT (0)
|
||||
['VSIM', self.test_key, 'VALUES', 3, 0, 0, 0, 'FILTER', '.a > 0', 'COUNT', 0],
|
||||
# invalid EF (0)
|
||||
['VSIM', self.test_key, 'VALUES', 3, 0, 0, 0, 'FILTER', '.a > 0', 'EF', 0],
|
||||
# invalid EPSILON (0)
|
||||
['VSIM', self.test_key, 'VALUES', 3, 0, 0, 0, 'FILTER', '.a > 0', 'EPSILON', 0],
|
||||
# invalid FILTER-EF (0)
|
||||
['VSIM', self.test_key, 'VALUES', 3, 0, 0, 0, 'FILTER', '.a > 0', 'FILTER-EF', 0],
|
||||
# unknown option
|
||||
['VSIM', self.test_key, 'VALUES', 3, 0, 0, 0, 'FILTER', '.a > 0', 'BADOPT', 1],
|
||||
]
|
||||
|
||||
for cmd in error_cmds:
|
||||
for _ in range(20):
|
||||
try:
|
||||
self.redis.execute_command(*cmd)
|
||||
except redis_module.exceptions.ResponseError:
|
||||
pass
|
||||
@@ -134,6 +134,9 @@ static uint64_t VectorSetTypeNextId = 0;
|
||||
// Default num elements returned by VSIM.
|
||||
#define VSET_DEFAULT_COUNT 10
|
||||
|
||||
// Maximum allowed vector dimension for input vectors and sets.
|
||||
#define VSET_MAX_VECTOR_DIM (1<<16)
|
||||
|
||||
/* ========================== Internal data structure ====================== */
|
||||
|
||||
/* Our abstract data type needs a dual representation similar to Redis
|
||||
@@ -408,6 +411,7 @@ float *parseVector(RedisModuleString **argv, int argc, int start_idx,
|
||||
// Must be 4 bytes per component.
|
||||
if (vec_raw_len % 4 || vec_raw_len < 4) return NULL;
|
||||
*dim = vec_raw_len/4;
|
||||
if (*dim > VSET_MAX_VECTOR_DIM) return NULL;
|
||||
|
||||
vec = RedisModule_Alloc(vec_raw_len);
|
||||
if (!vec) return NULL;
|
||||
@@ -417,7 +421,7 @@ float *parseVector(RedisModuleString **argv, int argc, int start_idx,
|
||||
if (argc < start_idx + 2) return NULL; // Need at least the dimension.
|
||||
long long vdim; // Vector dimension passed by the user.
|
||||
if (RedisModule_StringToLongLong(argv[start_idx+1],&vdim)
|
||||
!= REDISMODULE_OK || vdim < 1) return NULL;
|
||||
!= REDISMODULE_OK || vdim < 1 || vdim > VSET_MAX_VECTOR_DIM) return NULL;
|
||||
|
||||
// Check that all the arguments are available.
|
||||
if (argc < start_idx + 2 + vdim) return NULL;
|
||||
@@ -441,6 +445,12 @@ float *parseVector(RedisModuleString **argv, int argc, int start_idx,
|
||||
return NULL; // Unknown format.
|
||||
}
|
||||
|
||||
// reduce_dim must be <= dim
|
||||
if (reduce_dim && *reduce_dim && *reduce_dim > *dim) {
|
||||
if (vec) RedisModule_Free(vec);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (consumed_args) *consumed_args = consumed;
|
||||
return vec;
|
||||
}
|
||||
@@ -1064,6 +1074,7 @@ int VSIM_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
!= REDISMODULE_OK || count <= 0)
|
||||
{
|
||||
RedisModule_Free(vec);
|
||||
if (filter_expr) exprFree(filter_expr);
|
||||
return RedisModule_ReplyWithError(ctx, "ERR invalid COUNT");
|
||||
}
|
||||
j += 2;
|
||||
@@ -1072,6 +1083,7 @@ int VSIM_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_OK || epsilon <= 0)
|
||||
{
|
||||
RedisModule_Free(vec);
|
||||
if (filter_expr) exprFree(filter_expr);
|
||||
return RedisModule_ReplyWithError(ctx, "ERR invalid EPSILON");
|
||||
}
|
||||
j += 2;
|
||||
@@ -1080,6 +1092,7 @@ int VSIM_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_OK || ef <= 0 || ef > 1000000)
|
||||
{
|
||||
RedisModule_Free(vec);
|
||||
if (filter_expr) exprFree(filter_expr);
|
||||
return RedisModule_ReplyWithError(ctx, "ERR invalid EF");
|
||||
}
|
||||
j += 2;
|
||||
@@ -1088,6 +1101,7 @@ int VSIM_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
REDISMODULE_OK || filter_ef <= 0)
|
||||
{
|
||||
RedisModule_Free(vec);
|
||||
if (filter_expr) exprFree(filter_expr);
|
||||
return RedisModule_ReplyWithError(ctx, "ERR invalid FILTER-EF");
|
||||
}
|
||||
j += 2;
|
||||
@@ -1096,6 +1110,7 @@ int VSIM_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
size_t exprlen;
|
||||
char *exprstr = (char*)RedisModule_StringPtrLen(exprarg,&exprlen);
|
||||
int errpos;
|
||||
if (filter_expr) exprFree(filter_expr);
|
||||
filter_expr = exprCompile(exprstr,&errpos);
|
||||
if (filter_expr == NULL) {
|
||||
if ((size_t)errpos >= exprlen) errpos = 0;
|
||||
@@ -1107,6 +1122,7 @@ int VSIM_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
|
||||
j += 2;
|
||||
} else {
|
||||
RedisModule_Free(vec);
|
||||
if (filter_expr) exprFree(filter_expr);
|
||||
return RedisModule_ReplyWithError(ctx,
|
||||
"ERR syntax error in VSIM command");
|
||||
}
|
||||
@@ -1960,6 +1976,15 @@ void *VectorSetRdbLoad(RedisModuleIO *rdb, int encver) {
|
||||
uint32_t quant_type = hnsw_config & 0xff;
|
||||
uint32_t hnsw_m = (hnsw_config >> 8) & 0xffff;
|
||||
|
||||
/* Validate dimension loaded from RDB to enforce invariants and
|
||||
* avoid absurd allocations or inconsistent state. */
|
||||
if (dim == 0 || dim > VSET_MAX_VECTOR_DIM) {
|
||||
RedisModule_LogIOError(rdb, "warning",
|
||||
"Invalid vector dimension in RDB: dim=%u (max allowed %u)",
|
||||
(unsigned)dim, (unsigned)VSET_MAX_VECTOR_DIM);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Check that the quantization type is correct. Otherwise
|
||||
* return ASAP signaling the error. */
|
||||
if (quant_type != HNSW_QUANT_NONE &&
|
||||
@@ -1981,14 +2006,44 @@ void *VectorSetRdbLoad(RedisModuleIO *rdb, int encver) {
|
||||
uint32_t input_dim = RedisModule_LoadUnsigned(rdb);
|
||||
if (RedisModule_IsIOError(rdb)) goto ioerr;
|
||||
uint32_t output_dim = dim;
|
||||
size_t matrix_size = sizeof(float) * input_dim * output_dim;
|
||||
|
||||
/* Sanity check projection dimensions. */
|
||||
if (input_dim == 0 || output_dim == 0 || input_dim > VSET_MAX_VECTOR_DIM || output_dim > input_dim) {
|
||||
RedisModule_LogIOError(rdb, "warning",
|
||||
"Invalid projection matrix dimensions: input_dim=%u, output_dim=%u (max allowed %u)",
|
||||
(unsigned)input_dim, (unsigned)output_dim,
|
||||
(unsigned)VSET_MAX_VECTOR_DIM);
|
||||
goto ioerr;
|
||||
}
|
||||
|
||||
/* Check for overflow in matrix_size = sizeof(float) * input_dim * output_dim. */
|
||||
#if SIZE_MAX == UINT32_MAX
|
||||
uint64_t product = (uint64_t) output_dim * (uint64_t) input_dim * sizeof(float);
|
||||
if (product > SIZE_MAX) {
|
||||
RedisModule_LogIOError(rdb, "warning",
|
||||
"Projection matrix size overflow (output_dim too large): input_dim=%u, output_dim=%u",
|
||||
(unsigned)input_dim, (unsigned)output_dim);
|
||||
goto ioerr;
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t matrix_size = sizeof(float) * (size_t)input_dim * (size_t)output_dim;
|
||||
|
||||
/* Load projection matrix as a binary blob and validate length. */
|
||||
size_t blob_len = 0;
|
||||
char *matrix_blob = RedisModule_LoadStringBuffer(rdb, &blob_len);
|
||||
if (matrix_blob == NULL) goto ioerr;
|
||||
|
||||
if (blob_len != matrix_size) {
|
||||
RedisModule_LogIOError(rdb, "warning",
|
||||
"Mismatching projection matrix length: expected=%zu, got=%zu",
|
||||
matrix_size, blob_len);
|
||||
RedisModule_Free(matrix_blob);
|
||||
goto ioerr;
|
||||
}
|
||||
|
||||
vset->proj_matrix = RedisModule_Alloc(matrix_size);
|
||||
vset->proj_input_size = input_dim;
|
||||
|
||||
// Load projection matrix as a binary blob
|
||||
char *matrix_blob = RedisModule_LoadStringBuffer(rdb, NULL);
|
||||
if (matrix_blob == NULL) goto ioerr;
|
||||
memcpy(vset->proj_matrix, matrix_blob, matrix_size);
|
||||
RedisModule_Free(matrix_blob);
|
||||
}
|
||||
|
||||
+127
-2
@@ -247,6 +247,23 @@ tcp-keepalive 300
|
||||
# tls-auth-clients no
|
||||
# tls-auth-clients optional
|
||||
|
||||
# Automatically authenticate TLS clients as Redis users based on their
|
||||
# certificates.
|
||||
#
|
||||
# If set to a field like "CN", the server will extract the corresponding field
|
||||
# from the client's TLS certificate and attempt to find a Redis user with the
|
||||
# same name. If a matching user is found, the client is automatically
|
||||
# authenticated as that user during the TLS handshake. If no matching user is
|
||||
# found, the client is connected as the unauthenticated default user. Set to
|
||||
# "off" to disable automatic user authentication via certificate fields.
|
||||
#
|
||||
# Supported values: CN, off. Default: off.
|
||||
#
|
||||
# Matches certificate CN to Redis username (exact match only).
|
||||
# Example: Cert CN=myapp -> authenticates as user "myapp"
|
||||
#
|
||||
# tls-auth-clients-user CN
|
||||
|
||||
# By default, a Redis replica does not attempt to establish a TLS connection
|
||||
# with its master.
|
||||
#
|
||||
@@ -662,6 +679,9 @@ repl-diskless-sync-max-replicas 0
|
||||
# replication history.
|
||||
# Note that this requires sufficient memory, if you don't have it,
|
||||
# you risk an OOM kill.
|
||||
# "flushdb" - Always flush the entire dataset before diskless load.
|
||||
# Note that if the diskless load fails, the replica will lose all
|
||||
# existing data.
|
||||
# "on-empty-db" - Use diskless load only when current dataset is empty. This is
|
||||
# safer and avoid having old and new dataset loaded side by side
|
||||
# during replication.
|
||||
@@ -1157,6 +1177,8 @@ acllog-max-len 128
|
||||
# allkeys-lru -> Evict any key using approximated LRU.
|
||||
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
|
||||
# allkeys-lfu -> Evict any key using approximated LFU.
|
||||
# volatile-lrm -> Evict using approximated LRM, only keys with an expire set.
|
||||
# allkeys-lrm -> Evict any key using approximated LRM.
|
||||
# volatile-random -> Remove a random key having an expire set.
|
||||
# allkeys-random -> Remove a random key, any key.
|
||||
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
|
||||
@@ -1164,10 +1186,17 @@ acllog-max-len 128
|
||||
#
|
||||
# LRU means Least Recently Used
|
||||
# LFU means Least Frequently Used
|
||||
# LRM means Least Recently Modified (only write operations update the timestamp)
|
||||
#
|
||||
# Both LRU, LFU and volatile-ttl are implemented using approximated
|
||||
# LRU, LFU, LRM and volatile-ttl are implemented using approximated
|
||||
# randomized algorithms.
|
||||
#
|
||||
# LRU vs LRM: Both use similar eviction logic based on access time, but:
|
||||
# - LRU updates the timestamp on both read (GET) and write (SET) operations
|
||||
# - LRM only updates the timestamp on write (SET, INCR, etc.) operations
|
||||
# This makes LRM useful when you want to evict keys that haven't been updated
|
||||
# recently, regardless of how often they are read.
|
||||
#
|
||||
# Note: with any of the above policies, when there are no suitable keys for
|
||||
# eviction, Redis will return an error on write operations that require
|
||||
# more memory. These are usually commands that create new keys, add data or
|
||||
@@ -1825,6 +1854,20 @@ aof-timestamp-enabled no
|
||||
# These statistics can be leveraged to assess general slot usage trends, identify hot / cold slots,
|
||||
# migrate slots for a balanced cluster workload, and / or re-write application logic to better utilize slots.
|
||||
#
|
||||
# The config accepts multiple values as a space-separated list:
|
||||
# - cpu: Track CPU usage per slot (cpu-usec metric)
|
||||
# - net: Track network bytes per slot (network-bytes-in, network-bytes-out metrics)
|
||||
# - mem: Track memory usage per slot (memory-bytes metric)
|
||||
# - yes: Enable all tracking (equivalent to "cpu net mem")
|
||||
# - no: Disable all tracking (default)
|
||||
#
|
||||
# Example: cluster-slot-stats-enabled "cpu net"
|
||||
#
|
||||
# Note: Memory tracking (mem) can ONLY be enabled at startup. If you try to enable
|
||||
# memory tracking via CONFIG SET when it wasn't enabled at startup, the command will
|
||||
# fail. However, you can disable memory tracking at runtime by removing the 'mem' flag.
|
||||
# Once disabled, memory tracking cannot be re-enabled without restarting the server.
|
||||
#
|
||||
# cluster-slot-stats-enabled no
|
||||
|
||||
# Slot migration write pause timeout controls how long the source node will
|
||||
@@ -1928,6 +1971,17 @@ slowlog-log-slower-than 10000
|
||||
# You can reclaim memory used by the slow log with SLOWLOG RESET.
|
||||
slowlog-max-len 128
|
||||
|
||||
# When a command is written to the slowlog we check how many arguments it has
|
||||
# and if it has more than slowlog-entry-max-argc we trim the excess ones. The
|
||||
# last of the non-trimmed arguments is overwritten with an info string about
|
||||
# how many args were trimmed. That's why slowlog-entry-max-argc has minimum
|
||||
# value of 2, so we can always preserve the command name.
|
||||
# Moreover, each individual argument string is also trimmed depending on
|
||||
# slowlog-entry-max-string-len. Default values:
|
||||
#
|
||||
# slowlog-entry-max-argc 32
|
||||
# slowlog-entry-max-string-len 128
|
||||
|
||||
################################ LATENCY MONITOR ##############################
|
||||
|
||||
# The Redis latency monitoring subsystem samples different operations
|
||||
@@ -1990,13 +2044,20 @@ latency-monitor-threshold 0
|
||||
# e Evicted events (events generated when a key is evicted for maxmemory)
|
||||
# n New key events (Note: not included in the 'A' class)
|
||||
# t Stream commands
|
||||
# a Array commands
|
||||
# d Module key type events
|
||||
# m Key-miss events (Note: It is not included in the 'A' class)
|
||||
# o Overwritten events generated every time a key is overwritten.
|
||||
# (Note: not included in the 'A' class)
|
||||
# c Type-changed events generated every time a key's type changes
|
||||
# (Note: not included in the 'A' class)
|
||||
# A Alias for g$lshzxetd, so that the "AKE" string means all the events
|
||||
# S Subkeyspace events, published with __subkeyspace@<db>__:<key> prefix.
|
||||
# T Subkeyevent events, published with __subkeyevent@<db>__:<event> prefix.
|
||||
# I Subkeyspaceitem events, published per subkey with
|
||||
# __subkeyspaceitem@<db>__:<key>\n<subkey> prefix.
|
||||
# V Subkeyspaceevent events, published with
|
||||
# __subkeyspaceevent@<db>__:<event>|<key> prefix.
|
||||
# A Alias for g$lshzxetad, so that the "AKE" string means all the events
|
||||
# except key-miss, new key, overwritten and type-changed.
|
||||
#
|
||||
# The "notify-keyspace-events" takes as argument a string that is composed
|
||||
@@ -2102,6 +2163,61 @@ hll-sparse-max-bytes 3000
|
||||
stream-node-max-bytes 4096
|
||||
stream-node-max-entries 100
|
||||
|
||||
# Redis Streams support Idempotent Message Producer (IDMP) tracking to prevent
|
||||
# duplicate message delivery. When producers send messages with IDMP identifiers
|
||||
# (using XADD with IDMP or IDMPAUTO parameters), Redis tracks these identifiers to
|
||||
# detect and reject duplicates within a configurable time window.
|
||||
#
|
||||
# stream-idmp-duration: Specifies how long (in seconds) Redis should remember
|
||||
# IDMP identifiers for duplicate detection. After this duration expires, old
|
||||
# identifiers are automatically removed. This prevents unbounded memory growth
|
||||
# while allowing reasonable duplicate detection windows.
|
||||
# Valid range: 1 to 86400 seconds (1 second to 24 hours)
|
||||
# Default: 100 seconds
|
||||
#
|
||||
# stream-idmp-maxsize: Maximum number of IDMP identifiers to track per producer
|
||||
# per stream. Once this limit is reached, the oldest identifiers are evicted to
|
||||
# make room for new ones. This caps memory usage for IDMP tracking.
|
||||
# Valid range: 1 to 10000 entries
|
||||
# Default: 100 entries
|
||||
#
|
||||
# Note: These are default values for new streams. Individual streams can override
|
||||
# these settings using the XCFGSET command.
|
||||
#
|
||||
# stream-idmp-duration 100
|
||||
# stream-idmp-maxsize 100
|
||||
|
||||
# Arrays use a sliced directory structure for O(1) access. The slice size
|
||||
# controls the granularity of memory allocation - each slice covers a range
|
||||
# of indices. Must be a power of two between 256 and 65536.
|
||||
#
|
||||
# Smaller slices (1024-2048): Better for sparse data with large gaps between
|
||||
# indices, or many small arrays. Uses less memory per slice but more directory
|
||||
# entries.
|
||||
#
|
||||
# Larger slices (8192-16384): Better for dense/contiguous data. Fewer directory
|
||||
# entries but may waste memory if data is sparse within slices.
|
||||
#
|
||||
# Default 4096 works well for mixed workloads. If you change this setting via
|
||||
# CONFIG SET, existing arrays retain their original slice size.
|
||||
#
|
||||
# IMPORTANT CONSIDERATION: Redis arrays, for slices with very few elements, are
|
||||
# able to use a sparse representation, where the slice is not really
|
||||
# materialized into an actual contiguous allocation. See the next configuration
|
||||
# parameters for more information.
|
||||
array-slice-size 4096
|
||||
|
||||
# Arrays start with sparse slices (sorted key-value pairs) for memory efficiency
|
||||
# when elements are scattered. When a sparse slice exceeds array-sparse-kmax
|
||||
# entries, it promotes to a dense slice (direct array). When a dense slice's
|
||||
# element count drops below array-sparse-kmin and demotion would save memory,
|
||||
# it demotes back to sparse. Set kmax to 0 to disable sparse encoding entirely.
|
||||
# Set kmin to 0 if you never want dense slices to be demoted to sparse (useful
|
||||
# when in your work load arrays reach an almost empty state to be filled again
|
||||
# and so forth).
|
||||
array-sparse-kmax 10
|
||||
array-sparse-kmin 5
|
||||
|
||||
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
|
||||
# order to help rehashing the main Redis hash table (the one mapping top-level
|
||||
# keys to values). The hash table implementation Redis uses (see dict.c)
|
||||
@@ -2416,3 +2532,12 @@ jemalloc-bg-thread yes
|
||||
# to suppress
|
||||
#
|
||||
# ignore-warnings ARM64-COW-BUG
|
||||
|
||||
# When enabled, 'key-memory-histograms' adds per-type memory allocation histograms
|
||||
# (distrib_*_sizes fields in bytes) to INFO keysizes output, complementing the
|
||||
# existing size/item histograms with actual memory usage data.
|
||||
#
|
||||
# This setting must be enabled at startup; later config changes have no effect.
|
||||
# It is also implicitly enabled when 'cluster-slot-stats-enabled' is set.
|
||||
#
|
||||
# key-memory-histograms no
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
# Raise open files limit (macOS default 256 is too low for tests).
|
||||
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
|
||||
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
|
||||
ulimit -n 1024 2>/dev/null || true
|
||||
fi
|
||||
TCL_VERSIONS="8.5 8.6 8.7 9.0"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
|
||||
+6
-1
@@ -1,5 +1,10 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
# Raise open files limit (macOS default 256 is too low for tests).
|
||||
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
|
||||
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
|
||||
ulimit -n 1024 2>/dev/null || true
|
||||
fi
|
||||
TCL_VERSIONS="8.5 8.6 8.7 9.0"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
|
||||
+8
-1
@@ -1,5 +1,10 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
# Raise open files limit (macOS default 256 is too low for tests).
|
||||
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
|
||||
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
|
||||
ulimit -n 1024 2>/dev/null || true
|
||||
fi
|
||||
TCL_VERSIONS="8.5 8.6 8.7 9.0"
|
||||
TCLSH=""
|
||||
[ -z "$MAKE" ] && MAKE=make
|
||||
|
||||
@@ -59,4 +64,6 @@ $TCLSH tests/test_helper.tcl \
|
||||
--single unit/moduleapi/crash \
|
||||
--single unit/moduleapi/internalsecret \
|
||||
--single unit/moduleapi/configaccess \
|
||||
--single unit/moduleapi/keymeta \
|
||||
--single unit/moduleapi/ksn_notify_side_effect \
|
||||
"${@}"
|
||||
|
||||
+6
-1
@@ -1,5 +1,10 @@
|
||||
#!/bin/sh
|
||||
TCL_VERSIONS="8.5 8.6 8.7"
|
||||
# Raise open files limit (macOS default 256 is too low for tests).
|
||||
OPEN_FILE_LIMIT=$(ulimit -n 2>/dev/null)
|
||||
if [ -n "$OPEN_FILE_LIMIT" ] && [ "$OPEN_FILE_LIMIT" != "unlimited" ] && [ "$OPEN_FILE_LIMIT" -lt 1024 ]; then
|
||||
ulimit -n 1024 2>/dev/null || true
|
||||
fi
|
||||
TCL_VERSIONS="8.5 8.6 8.7 9.0"
|
||||
TCLSH=""
|
||||
|
||||
for VERSION in $TCL_VERSIONS; do
|
||||
|
||||
+10
-8
@@ -25,17 +25,19 @@ CLANG := $(findstring clang,$(shell sh -c '$(CC) --version | head -1'))
|
||||
# some automatic defaults are added to it. To specify optimization flags
|
||||
# explicitly without any defaults added, pass the OPT variable instead.
|
||||
OPTIMIZATION?=-O3
|
||||
ENABLE_LTO?=
|
||||
ifeq ($(OPTIMIZATION),-O3)
|
||||
ifeq (clang,$(CLANG))
|
||||
OPTIMIZATION+=-flto
|
||||
ENABLE_LTO=-flto
|
||||
else
|
||||
OPTIMIZATION+=-flto=auto
|
||||
ENABLE_LTO=-flto=auto -ffat-lto-objects
|
||||
endif
|
||||
OPTIMIZATION+=$(ENABLE_LTO)
|
||||
endif
|
||||
ifneq ($(OPTIMIZATION),-O0)
|
||||
OPTIMIZATION+=-fno-omit-frame-pointer
|
||||
endif
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv fast_float xxhash
|
||||
DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram fpconv xxhash tre
|
||||
NODEPS:=clean distclean
|
||||
|
||||
# Default settings
|
||||
@@ -149,7 +151,7 @@ endif
|
||||
|
||||
FINAL_CFLAGS=$(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) $(REDIS_CFLAGS)
|
||||
FINAL_LDFLAGS=$(LDFLAGS) $(OPT) $(REDIS_LDFLAGS) $(DEBUG)
|
||||
FINAL_LIBS=-lm -lstdc++
|
||||
FINAL_LIBS=-lm
|
||||
DEBUG=-g -ggdb
|
||||
|
||||
# Linux ARM32 needs -latomic at linking time
|
||||
@@ -257,7 +259,7 @@ ifdef OPENSSL_PREFIX
|
||||
endif
|
||||
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/fpconv -I../deps/fast_float -I../deps/xxhash
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram -I../deps/fpconv -I../deps/xxhash
|
||||
|
||||
# Determine systemd support and/or build preference (defaulting to auto-detection)
|
||||
BUILD_WITH_SYSTEMD=no
|
||||
@@ -382,7 +384,7 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server$(PROG_SUFFIX)
|
||||
REDIS_SENTINEL_NAME=redis-sentinel$(PROG_SUFFIX)
|
||||
REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o
|
||||
REDIS_SERVER_OBJ=threads_mngr.o memory_prefetch.o adlist.o quicklist.o ae.o anet.o dict.o ebuckets.o eventnotifier.o iothread.o mstr.o entry.o kvstore.o fwtree.o estore.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o t_array.o sparsearray.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o cluster_asm.o cluster_legacy.o cluster_slot_stats.o crc16.o endianconv.o slowlog.o eval.o bio.o rio.o rand.o memtest.o syscheck.o crcspeed.o crccombine.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o lolwut8.o acl.o tracking.o socket.o tls.o sha256.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o resp_parser.o call_reply.o script_lua.o script.o functions.o function_lua.o commands.o strl.o connection.o unix.o logreqres.o keymeta.o chk.o hotkeys.o gcra.o vector.o fast_float_strtod.o
|
||||
REDIS_CLI_NAME=redis-cli$(PROG_SUFFIX)
|
||||
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o ae.o redisassert.o crcspeed.o crccombine.o crc64.o siphash.o crc16.o monotonic.o cli_common.o mt19937-64.o strl.o cli_commands.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark$(PROG_SUFFIX)
|
||||
@@ -423,7 +425,7 @@ persist-settings: distclean
|
||||
echo REDIS_LDFLAGS=$(REDIS_LDFLAGS) >> .make-settings
|
||||
echo PREV_FINAL_CFLAGS=$(FINAL_CFLAGS) >> .make-settings
|
||||
echo PREV_FINAL_LDFLAGS=$(FINAL_LDFLAGS) >> .make-settings
|
||||
-(cd ../deps && $(MAKE) $(DEPENDENCY_TARGETS))
|
||||
-(cd ../deps && $(MAKE) $(DEPENDENCY_TARGETS) ENABLE_LTO="$(ENABLE_LTO)")
|
||||
|
||||
.PHONY: persist-settings
|
||||
|
||||
@@ -442,7 +444,7 @@ endif
|
||||
|
||||
# redis-server
|
||||
$(REDIS_SERVER_NAME): $(REDIS_SERVER_OBJ) $(REDIS_VEC_SETS_OBJ)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a ../deps/fast_float/libfast_float.a ../deps/xxhash/libxxhash.a $(FINAL_LIBS)
|
||||
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/lua/src/liblua.a ../deps/hdr_histogram/libhdrhistogram.a ../deps/fpconv/libfpconv.a ../deps/xxhash/libxxhash.a ../deps/tre/libtre.a $(FINAL_LIBS)
|
||||
|
||||
# redis-sentinel
|
||||
$(REDIS_SENTINEL_NAME): $(REDIS_SERVER_NAME)
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
* Copyright (c) 2018-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2024-present, Valkey contributors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of (a) the Redis Source Available License 2.0
|
||||
* (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
|
||||
* GNU Affero General Public License v3 (AGPLv3).
|
||||
@@ -54,6 +57,7 @@ struct ACLCategoryItem {
|
||||
{"list", ACL_CATEGORY_LIST},
|
||||
{"hash", ACL_CATEGORY_HASH},
|
||||
{"string", ACL_CATEGORY_STRING},
|
||||
{"array", ACL_CATEGORY_ARRAY},
|
||||
{"bitmap", ACL_CATEGORY_BITMAP},
|
||||
{"hyperloglog", ACL_CATEGORY_HYPERLOGLOG},
|
||||
{"geo", ACL_CATEGORY_GEO},
|
||||
@@ -67,6 +71,9 @@ struct ACLCategoryItem {
|
||||
{"connection", ACL_CATEGORY_CONNECTION},
|
||||
{"transaction", ACL_CATEGORY_TRANSACTION},
|
||||
{"scripting", ACL_CATEGORY_SCRIPTING},
|
||||
#ifdef ENABLE_GCRA
|
||||
{"ratelimit", ACL_CATEGORY_RATE_LIMIT},
|
||||
#endif
|
||||
{NULL,0} /* Terminator. */
|
||||
};
|
||||
|
||||
@@ -802,7 +809,7 @@ sds ACLDescribeSelectorCommandRules(aclSelector *selector) {
|
||||
{
|
||||
serverLog(LL_WARNING,
|
||||
"CRITICAL ERROR: User ACLs don't match final bitmap: '%s'",
|
||||
rules);
|
||||
redactLogCstr(rules));
|
||||
serverPanic("No bitmap match in ACLDescribeSelectorCommandRules()");
|
||||
}
|
||||
ACLFreeSelector(fake_selector);
|
||||
@@ -1181,7 +1188,7 @@ int ACLSetSelector(aclSelector *selector, const char* op, size_t oplen) {
|
||||
/* Add the first-arg to the list of valid ones. */
|
||||
serverLog(LL_WARNING, "Deprecation warning: Allowing a first arg of an otherwise "
|
||||
"blocked command is a misuse of ACL and may get disabled "
|
||||
"in the future (offender: +%s)", op+1);
|
||||
"in the future (offender: +%s)", redactLogCstr(op+1));
|
||||
ACLAddAllowedFirstArg(selector,cmd->id,sub);
|
||||
}
|
||||
ACLUpdateCommandRules(selector,op+1,1);
|
||||
@@ -1854,6 +1861,15 @@ int ACLCheckAllUserCommandPerm(user *u, struct redisCommand *cmd, robj **argv, i
|
||||
/* If there is no associated user, the connection can run anything. */
|
||||
if (u == NULL) return ACL_OK;
|
||||
|
||||
/* Quick check if the user has all permissions, return early if so. */
|
||||
if (likely(listFirst(u->selectors) != NULL)) {
|
||||
aclSelector *s = listNodeValue(listFirst(u->selectors));
|
||||
const uint32_t all_perms = SELECTOR_FLAG_ALLCOMMANDS |
|
||||
SELECTOR_FLAG_ALLKEYS |
|
||||
SELECTOR_FLAG_ALLCHANNELS;
|
||||
if ((s->flags & all_perms) == all_perms) return ACL_OK;
|
||||
}
|
||||
|
||||
/* We have to pick a single error to log, the logic for picking is as follows:
|
||||
* 1) If no selector can execute the command, return the command.
|
||||
* 2) Return the last key or channel that no selector could match. */
|
||||
@@ -2247,7 +2263,7 @@ int ACLLoadConfiguredUsers(void) {
|
||||
const char *errmsg = ACLSetUserStringError();
|
||||
serverLog(LL_WARNING,"Error loading ACL rule '%s' for "
|
||||
"the user named '%s': %s",
|
||||
aclrules[j],aclrules[0],errmsg);
|
||||
redactLogCstr(aclrules[j]),redactLogCstr(aclrules[0]),errmsg);
|
||||
return C_ERR;
|
||||
}
|
||||
}
|
||||
@@ -2258,22 +2274,22 @@ int ACLLoadConfiguredUsers(void) {
|
||||
serverLog(LL_NOTICE, "The user '%s' is disabled (there is no "
|
||||
"'on' modifier in the user description). Make "
|
||||
"sure this is not a configuration error.",
|
||||
aclrules[0]);
|
||||
redactLogCstr(aclrules[0]));
|
||||
}
|
||||
}
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
/* This function loads the ACL from the specified filename: every line
|
||||
* is validated and should be either empty or in the format used to specify
|
||||
* users in the redis.conf configuration or in the ACL file, that is:
|
||||
* is validated and should be either empty, a comment, or in the format
|
||||
* used to specify users in the redis.conf configuration or in the ACL file,
|
||||
* that is:
|
||||
*
|
||||
* user <username> ... rules ...
|
||||
*
|
||||
* Note that this function considers comments starting with '#' as errors
|
||||
* because the ACL file is meant to be rewritten, and comments would be
|
||||
* lost after the rewrite. Yet empty lines are allowed to avoid being too
|
||||
* strict.
|
||||
* Lines starting with '#' are treated as comments and ignored. Note that
|
||||
* comments will be lost after ACL SAVE rewrites the file. Empty lines are
|
||||
* also allowed.
|
||||
*
|
||||
* One important part of implementing ACL LOAD, that uses this function, is
|
||||
* to avoid ending with broken rules if the ACL file is invalid for some
|
||||
@@ -2325,8 +2341,8 @@ sds ACLLoadFromFile(const char *filename) {
|
||||
|
||||
lines[i] = sdstrim(lines[i]," \t\r\n");
|
||||
|
||||
/* Skip blank lines */
|
||||
if (lines[i][0] == '\0') continue;
|
||||
/* Skip blank lines and comments */
|
||||
if (lines[i][0] == '\0' || lines[i][0] == '#') continue;
|
||||
|
||||
/* Split into arguments */
|
||||
argv = sdssplitlen(lines[i],sdslen(lines[i])," ",1,&argc);
|
||||
@@ -2647,6 +2663,8 @@ void ACLUpdateInfoMetrics(int reason){
|
||||
server.acl_info.invalid_key_accesses++;
|
||||
} else if (reason == ACL_DENIED_CHANNEL) {
|
||||
server.acl_info.invalid_channel_accesses++;
|
||||
} else if (reason == ACL_INVALID_TLS_CERT_AUTH) {
|
||||
server.acl_info.acl_access_denied_tls_cert++;
|
||||
} else {
|
||||
serverPanic("Unknown ACL_DENIED encoding");
|
||||
}
|
||||
@@ -3091,6 +3109,7 @@ void aclCommand(client *c) {
|
||||
case ACL_DENIED_KEY: reasonstr="key"; break;
|
||||
case ACL_DENIED_CHANNEL: reasonstr="channel"; break;
|
||||
case ACL_DENIED_AUTH: reasonstr="auth"; break;
|
||||
case ACL_INVALID_TLS_CERT_AUTH: reasonstr = "tls-cert"; break;
|
||||
default: reasonstr="unknown";
|
||||
}
|
||||
addReplyBulkCString(c,reasonstr);
|
||||
|
||||
+1
-10
@@ -196,22 +196,13 @@ void listUnlinkNode(list *list, listNode *node) {
|
||||
* call to listNext() will return the next element of the list.
|
||||
*
|
||||
* This function can't fail. */
|
||||
listIter *listGetIterator(list *list, int direction)
|
||||
void listInitIterator(listIter *iter, list *list, int direction)
|
||||
{
|
||||
listIter *iter;
|
||||
|
||||
if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
|
||||
if (direction == AL_START_HEAD)
|
||||
iter->next = list->head;
|
||||
else
|
||||
iter->next = list->tail;
|
||||
iter->direction = direction;
|
||||
return iter;
|
||||
}
|
||||
|
||||
/* Release the iterator memory */
|
||||
void listReleaseIterator(listIter *iter) {
|
||||
zfree(iter);
|
||||
}
|
||||
|
||||
/* Create an iterator in the list private iterator structure */
|
||||
|
||||
+1
-2
@@ -58,9 +58,8 @@ list *listAddNodeHead(list *list, void *value);
|
||||
list *listAddNodeTail(list *list, void *value);
|
||||
list *listInsertNode(list *list, listNode *old_node, void *value, int after);
|
||||
void listDelNode(list *list, listNode *node);
|
||||
listIter *listGetIterator(list *list, int direction);
|
||||
void listInitIterator(listIter *iter, list *list, int direction);
|
||||
listNode *listNext(listIter *iter);
|
||||
void listReleaseIterator(listIter *iter);
|
||||
list *listDup(list *orig);
|
||||
listNode *listSearchKey(list *list, void *key);
|
||||
listNode *listIndex(list *list, long index);
|
||||
|
||||
+12
-19
@@ -101,31 +101,24 @@ static void aeApiFree(aeEventLoop *eventLoop) {
|
||||
|
||||
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
struct kevent ke;
|
||||
struct kevent evs[2];
|
||||
int nch = 0;
|
||||
|
||||
if (mask & AE_READABLE) {
|
||||
EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
|
||||
if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1;
|
||||
}
|
||||
if (mask & AE_WRITABLE) {
|
||||
EV_SET(&ke, fd, EVFILT_WRITE, EV_ADD, 0, 0, NULL);
|
||||
if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1;
|
||||
}
|
||||
return 0;
|
||||
if (mask & AE_READABLE) EV_SET(evs + nch++, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
|
||||
if (mask & AE_WRITABLE) EV_SET(evs + nch++, fd, EVFILT_WRITE, EV_ADD, 0, 0, NULL);
|
||||
|
||||
return kevent(state->kqfd, evs, nch, NULL, 0, NULL);
|
||||
}
|
||||
|
||||
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
struct kevent ke;
|
||||
struct kevent evs[2];
|
||||
int nch = 0;
|
||||
|
||||
if (mask & AE_READABLE) {
|
||||
EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
|
||||
kevent(state->kqfd, &ke, 1, NULL, 0, NULL);
|
||||
}
|
||||
if (mask & AE_WRITABLE) {
|
||||
EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
|
||||
kevent(state->kqfd, &ke, 1, NULL, 0, NULL);
|
||||
}
|
||||
if (mask & AE_READABLE) EV_SET(evs + nch++, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
|
||||
if (mask & AE_WRITABLE) EV_SET(evs + nch++, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
|
||||
|
||||
kevent(state->kqfd, evs, nch, NULL, 0, NULL);
|
||||
}
|
||||
|
||||
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
||||
|
||||
+3
-4
@@ -174,6 +174,7 @@ int anetKeepAlive(char *err, int fd, int interval)
|
||||
}
|
||||
|
||||
intvl = idle/3;
|
||||
if (intvl < 10) intvl = 10; /* kernel expects at least 10 seconds */
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &intvl, sizeof(intvl))) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPINTVL: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
@@ -196,9 +197,7 @@ int anetKeepAlive(char *err, int fd, int interval)
|
||||
|
||||
/* Note that the consequent probes will not be sent at equal intervals on Solaris,
|
||||
* but will be sent using the exponential backoff algorithm. */
|
||||
intvl = idle/3;
|
||||
cnt = 3;
|
||||
int time_to_abort = intvl * cnt;
|
||||
int time_to_abort = idle;
|
||||
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE_ABORT_THRESHOLD, &time_to_abort, sizeof(time_to_abort))) {
|
||||
anetSetError(err, "setsockopt TCP_KEEPCNT: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
@@ -716,7 +715,7 @@ error:
|
||||
* and one of the use cases is O_CLOEXEC|O_NONBLOCK. */
|
||||
int anetPipe(int fds[2], int read_flags, int write_flags) {
|
||||
int pipe_flags = 0;
|
||||
#if defined(__linux__) || defined(__FreeBSD__)
|
||||
#ifdef HAVE_PIPE2
|
||||
/* When possible, try to leverage pipe2() to apply flags that are common to both ends.
|
||||
* There is no harm to set O_CLOEXEC to prevent fd leaks. */
|
||||
pipe_flags = O_CLOEXEC | (read_flags & write_flags);
|
||||
|
||||
@@ -1920,9 +1920,10 @@ int rioWriteBulkObject(rio *r, robj *obj) {
|
||||
int rewriteListObject(rio *r, robj *key, robj *o) {
|
||||
long long count = 0, items = listTypeLength(o);
|
||||
|
||||
listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL);
|
||||
listTypeIterator li;
|
||||
listTypeEntry entry;
|
||||
while (listTypeNext(li,&entry)) {
|
||||
listTypeInitIterator(&li, o, 0, LIST_TAIL);
|
||||
while (listTypeNext(&li, &entry)) {
|
||||
if (count == 0) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
@@ -1930,7 +1931,7 @@ int rewriteListObject(rio *r, robj *key, robj *o) {
|
||||
!rioWriteBulkString(r,"RPUSH",5) ||
|
||||
!rioWriteBulkObject(r,key))
|
||||
{
|
||||
listTypeReleaseIterator(li);
|
||||
listTypeResetIterator(&li);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1941,19 +1942,19 @@ int rewriteListObject(rio *r, robj *key, robj *o) {
|
||||
vstr = listTypeGetValue(&entry,&vlen,&lval);
|
||||
if (vstr) {
|
||||
if (!rioWriteBulkString(r,(char*)vstr,vlen)) {
|
||||
listTypeReleaseIterator(li);
|
||||
listTypeResetIterator(&li);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (!rioWriteBulkLongLong(r,lval)) {
|
||||
listTypeReleaseIterator(li);
|
||||
listTypeResetIterator(&li);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||
items--;
|
||||
}
|
||||
listTypeReleaseIterator(li);
|
||||
listTypeResetIterator(&li);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -1961,11 +1962,12 @@ int rewriteListObject(rio *r, robj *key, robj *o) {
|
||||
* The function returns 0 on error, 1 on success. */
|
||||
int rewriteSetObject(rio *r, robj *key, robj *o) {
|
||||
long long count = 0, items = setTypeSize(o);
|
||||
setTypeIterator *si = setTypeInitIterator(o);
|
||||
setTypeIterator si;
|
||||
char *str;
|
||||
size_t len;
|
||||
int64_t llval;
|
||||
while (setTypeNext(si, &str, &len, &llval) != -1) {
|
||||
setTypeInitIterator(&si, o);
|
||||
while (setTypeNext(&si, &str, &len, &llval) != -1) {
|
||||
if (count == 0) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
@@ -1973,20 +1975,20 @@ int rewriteSetObject(rio *r, robj *key, robj *o) {
|
||||
!rioWriteBulkString(r,"SADD",4) ||
|
||||
!rioWriteBulkObject(r,key))
|
||||
{
|
||||
setTypeReleaseIterator(si);
|
||||
setTypeResetIterator(&si);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
size_t written = str ?
|
||||
rioWriteBulkString(r, str, len) : rioWriteBulkLongLong(r, llval);
|
||||
if (!written) {
|
||||
setTypeReleaseIterator(si);
|
||||
setTypeResetIterator(&si);
|
||||
return 0;
|
||||
}
|
||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||
items--;
|
||||
}
|
||||
setTypeReleaseIterator(si);
|
||||
setTypeResetIterator(&si);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -2040,8 +2042,9 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
|
||||
|
||||
dictInitIterator(&di, zs->dict);
|
||||
while((de = dictNext(&di)) != NULL) {
|
||||
sds ele = dictGetKey(de);
|
||||
double *score = dictGetVal(de);
|
||||
zskiplistNode *znode = dictGetKey(de);
|
||||
sds ele = zslGetNodeElement(znode);
|
||||
double score = znode->score;
|
||||
|
||||
if (count == 0) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
@@ -2055,7 +2058,7 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!rioWriteBulkDouble(r,*score) ||
|
||||
if (!rioWriteBulkDouble(r,score) ||
|
||||
!rioWriteBulkString(r,ele,sdslen(ele)))
|
||||
{
|
||||
dictResetIterator(&di);
|
||||
@@ -2104,14 +2107,14 @@ static int rioWriteHashIteratorCursor(rio *r, hashTypeIterator *hi, int what) {
|
||||
int rewriteHashObject(rio *r, robj *key, robj *o) {
|
||||
int res = 0; /*fail*/
|
||||
|
||||
hashTypeIterator *hi;
|
||||
hashTypeIterator hi;
|
||||
long long count = 0, items = hashTypeLength(o, 0);
|
||||
|
||||
int isHFE = hashTypeGetMinExpire(o, 0) != EB_EXPIRE_TIME_INVALID;
|
||||
hi = hashTypeInitIterator(o);
|
||||
hashTypeInitIterator(&hi, o);
|
||||
|
||||
if (!isHFE) {
|
||||
while (hashTypeNext(hi, 0) != C_ERR) {
|
||||
while (hashTypeNext(&hi, 0) != C_ERR) {
|
||||
if (count == 0) {
|
||||
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD : items;
|
||||
@@ -2121,31 +2124,31 @@ int rewriteHashObject(rio *r, robj *key, robj *o) {
|
||||
goto reHashEnd;
|
||||
}
|
||||
|
||||
if (!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) ||
|
||||
!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE))
|
||||
if (!rioWriteHashIteratorCursor(r, &hi, OBJ_HASH_KEY) ||
|
||||
!rioWriteHashIteratorCursor(r, &hi, OBJ_HASH_VALUE))
|
||||
goto reHashEnd;
|
||||
|
||||
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
|
||||
items--;
|
||||
}
|
||||
} else {
|
||||
while (hashTypeNext(hi, 0) != C_ERR) {
|
||||
while (hashTypeNext(&hi, 0) != C_ERR) {
|
||||
|
||||
char hmsetCmd[] = "*4\r\n$5\r\nHMSET\r\n";
|
||||
if ( (!rioWrite(r, hmsetCmd, sizeof(hmsetCmd) - 1)) ||
|
||||
(!rioWriteBulkObject(r, key)) ||
|
||||
(!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY)) ||
|
||||
(!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE)) )
|
||||
(!rioWriteHashIteratorCursor(r, &hi, OBJ_HASH_KEY)) ||
|
||||
(!rioWriteHashIteratorCursor(r, &hi, OBJ_HASH_VALUE)) )
|
||||
goto reHashEnd;
|
||||
|
||||
if (hi->expire_time != EB_EXPIRE_TIME_INVALID) {
|
||||
if (hi.expire_time != EB_EXPIRE_TIME_INVALID) {
|
||||
char cmd[] = "*6\r\n$10\r\nHPEXPIREAT\r\n";
|
||||
if ( (!rioWrite(r, cmd, sizeof(cmd) - 1)) ||
|
||||
(!rioWriteBulkObject(r, key)) ||
|
||||
(!rioWriteBulkLongLong(r, hi->expire_time)) ||
|
||||
(!rioWriteBulkLongLong(r, hi.expire_time)) ||
|
||||
(!rioWriteBulkString(r, "FIELDS", 6)) ||
|
||||
(!rioWriteBulkString(r, "1", 1)) ||
|
||||
(!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY)) )
|
||||
(!rioWriteHashIteratorCursor(r, &hi, OBJ_HASH_KEY)) )
|
||||
goto reHashEnd;
|
||||
}
|
||||
}
|
||||
@@ -2154,7 +2157,7 @@ int rewriteHashObject(rio *r, robj *key, robj *o) {
|
||||
res = 1; /* success */
|
||||
|
||||
reHashEnd:
|
||||
hashTypeReleaseIterator(hi);
|
||||
hashTypeResetIterator(&hi);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -2194,6 +2197,35 @@ int rioWriteStreamPendingEntry(rio *r, robj *key, const char *groupname, size_t
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Helper for rewriteStreamObject(): emit a single XNACK FORCE command that
|
||||
* reconstructs one or more NACKed (unowned) PEL entries sharing the same
|
||||
* delivery_count. `ids` points to an array of `count` streamIDs (at most
|
||||
* AOF_REWRITE_ITEMS_PER_CMD). Returns 0 on error, 1 on success. */
|
||||
int rioWriteStreamNackedEntries(rio *r, robj *key, const char *groupname,
|
||||
size_t groupname_len, streamID *ids,
|
||||
int count, uint64_t delivery_count) {
|
||||
serverAssert(count > 0 && count <= AOF_REWRITE_ITEMS_PER_CMD);
|
||||
|
||||
/* XNACK <key> <group> FAIL IDS <n> <id..> RETRYCOUNT <cnt> FORCE
|
||||
* 6 fixed tokens before IDs + count IDs + 3 fixed tokens after. */
|
||||
if (rioWriteBulkCount(r,'*',6+count+3) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"XNACK",5) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (rioWriteBulkString(r,groupname,groupname_len) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"FAIL",4) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"IDS",3) == 0) return 0;
|
||||
if (rioWriteBulkLongLong(r,count) == 0) return 0;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (rioWriteBulkStreamID(r,&ids[i]) == 0) return 0;
|
||||
}
|
||||
|
||||
if (rioWriteBulkString(r,"RETRYCOUNT",10) == 0) return 0;
|
||||
if (rioWriteBulkLongLong(r,delivery_count) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"FORCE",5) == 0) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Helper for rewriteStreamObject(): emit the XGROUP CREATECONSUMER is
|
||||
* needed in order to create consumers that do not have any pending entries.
|
||||
* All this in the context of the specified key and group. */
|
||||
@@ -2208,17 +2240,31 @@ int rioWriteStreamEmptyConsumer(rio *r, robj *key, const char *groupname, size_t
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Helper for rewriteStreamObject(): emit the XIDMPRECORD needed to
|
||||
* restore an IDMP entry for the given producer in the context of the
|
||||
* specified key. */
|
||||
int rioWriteStreamIdmpEntry(rio *r, robj *key, const char *pid, size_t pid_len, idmpEntry *entry) {
|
||||
/* XIDMPRECORD <key> <pid> <iid> <streamID> */
|
||||
if (rioWriteBulkCount(r,'*',5) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"XIDMPRECORD",11) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (rioWriteBulkString(r,pid,pid_len) == 0) return 0;
|
||||
if (rioWriteBulkString(r,entry->iid,entry->iid_len) == 0) return 0;
|
||||
if (rioWriteBulkStreamID(r,&entry->id) == 0) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Emit the commands needed to rebuild a stream object.
|
||||
* The function returns 0 on error, 1 on success. */
|
||||
int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
stream *s = o->ptr;
|
||||
streamIterator si;
|
||||
streamIteratorStart(&si,s,NULL,NULL,0);
|
||||
streamID id;
|
||||
int64_t numfields;
|
||||
|
||||
if (s->length) {
|
||||
/* Reconstruct the stream data using XADD commands. */
|
||||
streamIterator si;
|
||||
int64_t numfields;
|
||||
streamIteratorStart(&si,s,NULL,NULL,0);
|
||||
while(streamIteratorGetID(&si,&id,&numfields)) {
|
||||
/* Emit a two elements array for each item. The first is
|
||||
* the ID, the second is an array of field-value pairs. */
|
||||
@@ -2244,6 +2290,7 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
}
|
||||
}
|
||||
}
|
||||
streamIteratorStop(&si);
|
||||
} else {
|
||||
/* Use the XADD MAXLEN 0 trick to generate an empty stream if
|
||||
* the key we are serializing is an empty string, which is possible
|
||||
@@ -2258,7 +2305,6 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
!rioWriteBulkString(r,"x",1) ||
|
||||
!rioWriteBulkString(r,"y",1))
|
||||
{
|
||||
streamIteratorStop(&si);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -2274,11 +2320,9 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
!rioWriteBulkString(r,"MAXDELETEDID",12) ||
|
||||
!rioWriteBulkStreamID(r,&s->max_deleted_entry_id))
|
||||
{
|
||||
streamIteratorStop(&si);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Create all the stream consumer groups. */
|
||||
if (s->cgroups) {
|
||||
raxIterator ri;
|
||||
@@ -2297,7 +2341,6 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
!rioWriteBulkLongLong(r,group->entries_read))
|
||||
{
|
||||
raxStop(&ri);
|
||||
streamIteratorStop(&si);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2316,7 +2359,6 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
{
|
||||
raxStop(&ri_cons);
|
||||
raxStop(&ri);
|
||||
streamIteratorStop(&si);
|
||||
return 0;
|
||||
}
|
||||
continue;
|
||||
@@ -2335,21 +2377,110 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
|
||||
raxStop(&ri_pel);
|
||||
raxStop(&ri_cons);
|
||||
raxStop(&ri);
|
||||
streamIteratorStop(&si);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
raxStop(&ri_pel);
|
||||
}
|
||||
raxStop(&ri_cons);
|
||||
|
||||
/* Emit XNACK FORCE for NACKed (unowned) entries from the
|
||||
* NACK zone of the PEL time-ordered list
|
||||
* (pel_time_head..pel_nack_tail). Consecutive entries with
|
||||
* the same delivery_count are batched into a single command.
|
||||
*
|
||||
* nack_stop is the first node outside the NACK zone (or NULL
|
||||
* when the zone extends to the end of the PEL). When
|
||||
* pel_nack_tail is NULL (no NACKed entries) the guard below
|
||||
* skips the whole block. */
|
||||
streamNACK *nack_end = group->pel_nack_tail;
|
||||
if (nack_end != NULL) {
|
||||
streamID batch_ids[AOF_REWRITE_ITEMS_PER_CMD];
|
||||
streamNACK *nack_stop = nack_end->pel_next;
|
||||
streamNACK *nack = group->pel_time_head;
|
||||
int batch_count = 0;
|
||||
uint64_t batch_dc = 0;
|
||||
while (nack && nack != nack_stop) {
|
||||
if (batch_count == 0) batch_dc = nack->delivery_count;
|
||||
batch_ids[batch_count++] = nack->id;
|
||||
streamNACK *next = nack->pel_next;
|
||||
if (batch_count >= AOF_REWRITE_ITEMS_PER_CMD ||
|
||||
!next || next == nack_stop ||
|
||||
next->delivery_count != batch_dc)
|
||||
{
|
||||
if (rioWriteStreamNackedEntries(r,key,(char*)ri.key,
|
||||
ri.key_len,batch_ids,
|
||||
batch_count,batch_dc) == 0)
|
||||
{
|
||||
raxStop(&ri);
|
||||
return 0;
|
||||
}
|
||||
batch_count = 0;
|
||||
}
|
||||
nack = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
raxStop(&ri);
|
||||
}
|
||||
|
||||
streamIteratorStop(&si);
|
||||
/* Emit XCFGSET to restore per-stream IDMP configuration if it differs
|
||||
* from the server defaults, so that AOF rewrite preserves custom settings. */
|
||||
if (s->idmp_duration != (uint64_t)server.stream_idmp_duration ||
|
||||
s->idmp_max_entries != (uint64_t)server.stream_idmp_maxsize)
|
||||
{
|
||||
if (!rioWriteBulkCount(r,'*',6) ||
|
||||
!rioWriteBulkString(r,"XCFGSET",7) ||
|
||||
!rioWriteBulkObject(r,key) ||
|
||||
!rioWriteBulkString(r,"IDMP-DURATION",13) ||
|
||||
!rioWriteBulkLongLong(r,s->idmp_duration) ||
|
||||
!rioWriteBulkString(r,"IDMP-MAXSIZE",12) ||
|
||||
!rioWriteBulkLongLong(r,s->idmp_max_entries))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Emit XIDMPRECORD for each IDMP entry. Entries whose stream ID no
|
||||
* longer exists (removed by XDEL/trim) are skipped, since
|
||||
* xidmprecordCommand() rejects references to missing IDs and would
|
||||
* cause AOF replay errors. */
|
||||
if (s->idmp_producers) {
|
||||
raxIterator ri_idmp;
|
||||
raxStart(&ri_idmp,s->idmp_producers);
|
||||
raxSeek(&ri_idmp,"^",NULL,0);
|
||||
while(raxNext(&ri_idmp)) {
|
||||
idmpProducer *producer = ri_idmp.data;
|
||||
for (idmpEntry *entry = producer->idmp_head; entry != NULL; entry = entry->next) {
|
||||
if (!streamEntryExists(s, &entry->id)) continue;
|
||||
if (rioWriteStreamIdmpEntry(r,key,(char*)ri_idmp.key,
|
||||
ri_idmp.key_len,entry) == 0)
|
||||
{
|
||||
raxStop(&ri_idmp);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
raxStop(&ri_idmp);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_GCRA
|
||||
int rewriteGCRAObject(rio *r, robj *key, robj *o) {
|
||||
long long val;
|
||||
getLongLongFromGCRAObject(o, &val);
|
||||
|
||||
/* GCRASETVALUE <key> <tat> */
|
||||
if (rioWriteBulkCount(r,'*',3) == 0) return 0;
|
||||
if (rioWriteBulkString(r,"GCRASETVALUE",12) == 0) return 0;
|
||||
if (rioWriteBulkObject(r,key) == 0) return 0;
|
||||
if (rioWriteBulkLongLong(r,val) == 0) return 0;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Call the module type callback in order to rewrite a data type
|
||||
* that is exported by a module and is not handled by Redis itself.
|
||||
* The function returns 0 on error, 1 on success. */
|
||||
@@ -2357,7 +2488,7 @@ int rewriteModuleObject(rio *r, robj *key, robj *o, int dbid) {
|
||||
RedisModuleIO io;
|
||||
moduleValue *mv = o->ptr;
|
||||
moduleType *mt = mv->type;
|
||||
moduleInitIOContext(io,mt,r,key,dbid);
|
||||
moduleInitIOContext(&io, &mt->entity, r, key, dbid);
|
||||
mt->aof_rewrite(&io,key,mv->value);
|
||||
if (io.ctx) {
|
||||
moduleFreeContext(io.ctx);
|
||||
@@ -2386,6 +2517,116 @@ werr:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Write unsigned 64-bit integer as bulk string.
|
||||
* Unlike rioWriteBulkLongLong which uses signed representation,
|
||||
* this correctly handles values >= 2^63 (e.g., array indices). */
|
||||
static int rioWriteBulkUnsignedLongLong(rio *r, uint64_t value) {
|
||||
char buf[24];
|
||||
int len = ull2string(buf, sizeof(buf), value);
|
||||
return rioWriteBulkString(r, buf, len);
|
||||
}
|
||||
|
||||
/* Helper to emit a single array element for AOF rewrite.
|
||||
* Returns 0 on error, 1 on success. Updates count and items. */
|
||||
static int aofEmitArrayElement(rio *r, robj *key, uint64_t idx, void *v,
|
||||
long long *count, long long *items) {
|
||||
if (*count == 0) {
|
||||
int cmd_items = (*items > AOF_REWRITE_ITEMS_PER_CMD/2) ?
|
||||
AOF_REWRITE_ITEMS_PER_CMD/2 : *items; /* pairs of idx+val */
|
||||
if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
|
||||
!rioWriteBulkString(r,"ARMSET",6) ||
|
||||
!rioWriteBulkObject(r,key))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write index (unsigned to handle indices >= 2^63) */
|
||||
if (!rioWriteBulkUnsignedLongLong(r, idx)) return 0;
|
||||
|
||||
/* Write value - inline types use scratch space, arString aliases directly. */
|
||||
char buf[AR_INLINE_BUFSIZE];
|
||||
size_t len;
|
||||
const char *data = arDecode(v, buf, sizeof(buf), &len);
|
||||
if (!rioWriteBulkString(r, data, len)) return 0;
|
||||
|
||||
if (++(*count) == AOF_REWRITE_ITEMS_PER_CMD/2) *count = 0;
|
||||
(*items)--;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Helper to emit all elements from a slice for AOF rewrite. */
|
||||
static int aofEmitSliceElements(rio *r, robj *key, arSlice *s, uint64_t slice_id,
|
||||
uint32_t slice_size, long long *count, long long *items) {
|
||||
if (s->encoding == AR_SLICE_DENSE) {
|
||||
for (uint32_t i = 0; i < s->layout.dense.winsize; i++) {
|
||||
void *v = s->layout.dense.items[i];
|
||||
if (arIsEmpty(v)) continue;
|
||||
uint64_t idx = arMakeIdx(slice_id, s->layout.dense.offset + i, slice_size);
|
||||
if (!aofEmitArrayElement(r, key, idx, v, count, items)) return 0;
|
||||
}
|
||||
} else {
|
||||
/* Sparse slice */
|
||||
uint16_t *offsets = s->layout.sparse.offsets;
|
||||
void **values = s->layout.sparse.values;
|
||||
for (uint32_t i = 0; i < s->count; i++) {
|
||||
uint64_t idx = arMakeIdx(slice_id, offsets[i], slice_size);
|
||||
if (!aofEmitArrayElement(r, key, idx, values[i], count, items)) return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Emit the commands needed to rebuild an array object.
|
||||
* The function returns 0 on error, 1 on success. */
|
||||
int rewriteArrayObject(rio *r, robj *key, robj *o) {
|
||||
redisArray *ar = o->ptr;
|
||||
long long count = 0, items = ar->count;
|
||||
if (items == 0) return 1;
|
||||
|
||||
/* Iterate through all slices, handling both flat directory mode and
|
||||
* superdir mode. This mirrors the iteration logic in rdb.c. */
|
||||
if (ar->superdir) {
|
||||
/* Superdir mode: iterate through blocks */
|
||||
for (uint32_t bi = 0; bi < ar->sdir_len; bi++) {
|
||||
arSDirEntry *e = ar->superdir + bi;
|
||||
uint64_t block_base = e->block_id * AR_SUPER_BLOCK_SLOTS;
|
||||
|
||||
for (uint32_t si = 0; si < AR_SUPER_BLOCK_SLOTS; si++) {
|
||||
arSlice *s = e->slots[si];
|
||||
if (!s) continue;
|
||||
uint64_t slice_id = block_base + si;
|
||||
if (!aofEmitSliceElements(r, key, s, slice_id, ar->slice_size,
|
||||
&count, &items)) return 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Flat directory mode */
|
||||
for (uint64_t slice_id = 0; slice_id <= ar->dir_highest_used && slice_id < ar->dir_alloc; slice_id++) {
|
||||
arSlice *s = ar->dir[slice_id];
|
||||
if (!s) continue;
|
||||
if (!aofEmitSliceElements(r, key, s, slice_id, ar->slice_size,
|
||||
&count, &items)) return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* If insert_idx is set, emit ARSEEK command to restore it.
|
||||
* When insert_idx == UINT64_MAX-1, we emit ARSEEK UINT64_MAX which
|
||||
* correctly sets insert_idx back to UINT64_MAX-1 (terminal state). */
|
||||
if (ar->insert_idx != AR_INSERT_IDX_NONE) {
|
||||
/* ARSEEK key insert_idx+1 (ARSEEK sets position for next insert) */
|
||||
if (!rioWriteBulkCount(r,'*',3) ||
|
||||
!rioWriteBulkString(r,"ARSEEK",6) ||
|
||||
!rioWriteBulkObject(r,key) ||
|
||||
!rioWriteBulkUnsignedLongLong(r, ar->insert_idx + 1))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int rewriteObject(rio *r, robj *key, robj *o, int dbid, long long expiretime) {
|
||||
/* Save the key and associated value */
|
||||
if (o->type == OBJ_STRING) {
|
||||
@@ -2405,6 +2646,12 @@ int rewriteObject(rio *r, robj *key, robj *o, int dbid, long long expiretime) {
|
||||
if (rewriteHashObject(r,key,o) == 0) return C_ERR;
|
||||
} else if (o->type == OBJ_STREAM) {
|
||||
if (rewriteStreamObject(r,key,o) == 0) return C_ERR;
|
||||
#ifdef ENABLE_GCRA
|
||||
} else if (o->type == OBJ_GCRA) {
|
||||
if (rewriteGCRAObject(r,key,o) == 0) return C_ERR;
|
||||
#endif
|
||||
} else if (o->type == OBJ_ARRAY) {
|
||||
if (rewriteArrayObject(r,key,o) == 0) return C_ERR;
|
||||
} else if (o->type == OBJ_MODULE) {
|
||||
if (rewriteModuleObject(r,key,o,dbid) == 0) return C_ERR;
|
||||
} else {
|
||||
@@ -2419,6 +2666,10 @@ int rewriteObject(rio *r, robj *key, robj *o, int dbid, long long expiretime) {
|
||||
if (rioWriteBulkLongLong(r,expiretime) == 0) return C_ERR;
|
||||
}
|
||||
|
||||
/* If modules metadata is available */
|
||||
if ((getModuleMetaBits(o->metabits)) && (keyMetaOnAof(r, key, o, dbid) == 0))
|
||||
return C_ERR;
|
||||
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
@@ -2428,7 +2679,7 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
long key_count = 0;
|
||||
long long updated_time = 0;
|
||||
unsigned long long skipped = 0;
|
||||
kvstoreIterator *kvs_it = NULL;
|
||||
kvstoreIterator kvs_it;
|
||||
|
||||
/* Record timestamp at the beginning of rewriting AOF. */
|
||||
if (server.aof_timestamp_enabled) {
|
||||
@@ -2448,11 +2699,21 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
if (rioWrite(aof,selectcmd,sizeof(selectcmd)-1) == 0) goto werr;
|
||||
if (rioWriteBulkLongLong(aof,j) == 0) goto werr;
|
||||
|
||||
kvs_it = kvstoreIteratorInit(db->keys);
|
||||
kvstoreIteratorInit(&kvs_it, db->keys);
|
||||
int last_slot = -1;
|
||||
/* Iterate this DB writing every entry */
|
||||
while((de = kvstoreIteratorNext(kvs_it)) != NULL) {
|
||||
while((de = kvstoreIteratorNext(&kvs_it)) != NULL) {
|
||||
long long expiretime;
|
||||
size_t aof_bytes_before_key = aof->processed_bytes;
|
||||
int curr_slot = kvstoreIteratorGetCurrentDictIndex(&kvs_it);
|
||||
|
||||
/* In cluster mode, dismiss bucket arrays of the previous slot
|
||||
* which won't be accessed again, to avoid CoW. */
|
||||
if (server.cluster_enabled && curr_slot != last_slot) {
|
||||
if (server.in_fork_child && last_slot != -1)
|
||||
dismissDictBucketsMemory(kvstoreGetDict(db->keys, last_slot));
|
||||
last_slot = curr_slot;
|
||||
}
|
||||
|
||||
/* Get the value object (of type kvobj) */
|
||||
kvobj *o = dictGetKV(de);
|
||||
@@ -2461,25 +2722,23 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
expiretime = kvobjGetExpire(o);
|
||||
|
||||
/* Skip keys that are being trimmed */
|
||||
if (server.cluster_enabled) {
|
||||
int curr_slot = kvstoreIteratorGetCurrentDictIndex(kvs_it);
|
||||
if (isSlotInTrimJob(curr_slot)) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
if (server.cluster_enabled && isSlotInTrimJob(curr_slot)) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Set on stack string object for key */
|
||||
robj key;
|
||||
initStaticStringObject(key, kvobjGetKey(o));
|
||||
|
||||
if (rewriteObject(aof, &key, o, j, expiretime) == C_ERR) goto werr;
|
||||
if (rewriteObject(aof, &key, o, j, expiretime) == C_ERR) goto werr2;
|
||||
|
||||
/* In fork child process, we can try to release memory back to the
|
||||
* OS and possibly avoid or decrease COW. We give the dismiss
|
||||
* mechanism a hint about an estimated size of the object we stored. */
|
||||
size_t dump_size = aof->processed_bytes - aof_bytes_before_key;
|
||||
if (server.in_fork_child) dismissObject(o, dump_size);
|
||||
if (server.in_fork_child && dump_size > server.page_size/2)
|
||||
dismissObject(o, dump_size);
|
||||
|
||||
/* Update info every 1 second (approximately).
|
||||
* in order to avoid calling mstime() on each iteration, we will
|
||||
@@ -2496,13 +2755,18 @@ int rewriteAppendOnlyFileRio(rio *aof) {
|
||||
if (server.rdb_key_save_delay)
|
||||
debugDelay(server.rdb_key_save_delay);
|
||||
}
|
||||
kvstoreIteratorRelease(kvs_it);
|
||||
kvstoreIteratorReset(&kvs_it);
|
||||
|
||||
/* Dismiss bucket arrays of kvstore in standalone mode. */
|
||||
if (server.in_fork_child && !server.cluster_enabled)
|
||||
dismissKvstoreBucketsMemory(db->keys);
|
||||
}
|
||||
serverLog(LL_NOTICE, "AOF rewrite done, %ld keys saved, %llu keys skipped.", key_count, skipped);
|
||||
return C_OK;
|
||||
|
||||
werr2:
|
||||
kvstoreIteratorReset(&kvs_it);
|
||||
werr:
|
||||
if (kvs_it) kvstoreIteratorRelease(kvs_it);
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
* atomicSet(var,value) -- Set the atomic counter value
|
||||
* atomicGetWithSync(var,value) -- 'atomicGet' with inter-thread synchronization
|
||||
* atomicSetWithSync(var,value) -- 'atomicSet' with inter-thread synchronization
|
||||
* atomicCompareExchange(type,var,expected_var,desired) -- Compare and exchange (CAS) operation
|
||||
*
|
||||
* Atomic operations on flags.
|
||||
* Flag type can be int, long, long long or their unsigned counterparts.
|
||||
@@ -110,6 +111,8 @@
|
||||
} while(0)
|
||||
#define atomicSetWithSync(var,value) \
|
||||
atomic_store_explicit(&var,value,memory_order_seq_cst)
|
||||
#define atomicCompareExchange(type,var,expected_var,desired) \
|
||||
atomic_compare_exchange_weak_explicit(&var,&expected_var,desired,memory_order_relaxed,memory_order_relaxed)
|
||||
#define atomicFlagGetSet(var,oldvalue_var) \
|
||||
oldvalue_var = atomic_exchange_explicit(&var,1,memory_order_relaxed)
|
||||
#define REDIS_ATOMIC_API "c11-builtin"
|
||||
@@ -135,6 +138,8 @@
|
||||
} while(0)
|
||||
#define atomicSetWithSync(var,value) \
|
||||
__atomic_store_n(&var,value,__ATOMIC_SEQ_CST)
|
||||
#define atomicCompareExchange(type,var,expected_var,desired) \
|
||||
__atomic_compare_exchange_n(&var,&expected_var,desired,1,__ATOMIC_RELAXED,__ATOMIC_RELAXED)
|
||||
#define atomicFlagGetSet(var,oldvalue_var) \
|
||||
oldvalue_var = __atomic_exchange_n(&var,1,__ATOMIC_RELAXED)
|
||||
#define REDIS_ATOMIC_API "atomic-builtin"
|
||||
@@ -164,6 +169,12 @@
|
||||
ANNOTATE_HAPPENS_BEFORE(&var); \
|
||||
while(!__sync_bool_compare_and_swap(&var,var,value,__sync_synchronize)); \
|
||||
} while(0)
|
||||
#define atomicCompareExchange(type,var,expected_var,desired) ({ \
|
||||
type _old = __sync_val_compare_and_swap(&var,expected_var,desired); \
|
||||
int _success = (_old == expected_var); \
|
||||
if (!_success) expected_var = _old; \
|
||||
_success; \
|
||||
})
|
||||
#define atomicFlagGetSet(var,oldvalue_var) \
|
||||
oldvalue_var = __sync_val_compare_and_swap(&var,0,1)
|
||||
#define REDIS_ATOMIC_API "sync-builtin"
|
||||
@@ -172,4 +183,23 @@
|
||||
#error "Unable to determine atomic operations for your platform"
|
||||
|
||||
#endif
|
||||
|
||||
/* atomicIncrGetSingleWriter(var, delta, newvalue_var)
|
||||
*
|
||||
* Adds `delta` to `var` and writes the resulting value to `newvalue_var`.
|
||||
* Same end result as atomicIncrGet() but implemented as load+add+store instead
|
||||
* of an atomic read-modify-write. This avoids the `lock` prefix on x86
|
||||
* (~20-40 cycles vs ~2-3 for plain load+store).
|
||||
*
|
||||
* SAFETY: the caller MUST guarantee that no other thread ever writes to `var`
|
||||
* (no atomicIncr, no atomicSet, no other call to this macro from a different
|
||||
* thread). Concurrent writers cause silent lost updates. Readers on other
|
||||
* threads using atomicGet are fine: they will observe either the pre or
|
||||
* post update value. */
|
||||
#define atomicIncrGetSingleWriter(var, delta, newvalue_var) do { \
|
||||
atomicGet((var), (newvalue_var)); \
|
||||
(newvalue_var) += (delta); \
|
||||
atomicSet((var), (newvalue_var)); \
|
||||
} while(0)
|
||||
|
||||
#endif /* __ATOMIC_VAR_H */
|
||||
|
||||
+202
-44
@@ -37,9 +37,11 @@
|
||||
|
||||
/* AArch64 NEON support is determined at compile time via HAVE_AARCH64_NEON */
|
||||
#ifdef HAVE_AVX512
|
||||
#define BITOP_USE_AVX512 (__builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512vpopcntdq"))
|
||||
#define BITOP_USE_AVX512 (__builtin_cpu_supports("avx512f"))
|
||||
#define BITOPS_USE_AVX512_POPCOUNT (__builtin_cpu_supports("avx512f") && __builtin_cpu_supports("avx512vpopcntdq"))
|
||||
#else
|
||||
#define BITOP_USE_AVX512 0
|
||||
#define BITOPS_USE_AVX512_POPCOUNT 0
|
||||
#endif
|
||||
|
||||
|
||||
@@ -364,7 +366,7 @@ long long redisPopCountAvx2(void *s, long count) {
|
||||
/* Automatically select the best available popcount implementation */
|
||||
static inline long long redisPopcountAuto(const unsigned char *p, long count) {
|
||||
#ifdef HAVE_AVX512
|
||||
if (BITOP_USE_AVX512) {
|
||||
if (BITOPS_USE_AVX512_POPCOUNT) {
|
||||
return redisPopCountAvx512((void*)p, count);
|
||||
}
|
||||
#endif
|
||||
@@ -796,11 +798,11 @@ static kvobj *lookupStringForBitCommand(client *c, uint64_t maxbit,
|
||||
} else {
|
||||
o = dbUnshareStringValue(c->db,c->argv[1],o);
|
||||
*strOldSize = sdslen(o->ptr);
|
||||
if (server.memory_tracking_per_slot)
|
||||
oldAllocSize = stringObjectAllocSize(o);
|
||||
if (server.memory_tracking_enabled)
|
||||
oldAllocSize = kvobjAllocSize(o);
|
||||
o->ptr = sdsgrowzero(o->ptr,byte+1);
|
||||
if (server.memory_tracking_per_slot)
|
||||
updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), oldAllocSize, stringObjectAllocSize(o));
|
||||
if (server.memory_tracking_enabled)
|
||||
updateSlotAllocSize(c->db, getKeySlot(c->argv[1]->ptr), o, oldAllocSize, kvobjAllocSize(o));
|
||||
*strGrowSize = sdslen(o->ptr) - *strOldSize;
|
||||
}
|
||||
return o;
|
||||
@@ -875,7 +877,7 @@ void setbitCommand(client *c) {
|
||||
byteval &= ~(1 << bit);
|
||||
byteval |= ((on & 0x1) << bit);
|
||||
((uint8_t*)o->ptr)[byte] = byteval;
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
keyModified(c,c->db,c->argv[1],o,1);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
@@ -883,8 +885,7 @@ void setbitCommand(client *c) {
|
||||
* update the keysizes histogram. Otherwise, the histogram already
|
||||
* updated in lookupStringForBitCommand() by calling dbAdd(). */
|
||||
if ((strOldSize > 0) && (strGrowSize != 0))
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_STRING,
|
||||
strOldSize, strOldSize + strGrowSize);
|
||||
updateKeysizesHist(c->db, OBJ_STRING, strOldSize, strOldSize + strGrowSize);
|
||||
}
|
||||
|
||||
/* Return original value. */
|
||||
@@ -923,7 +924,7 @@ void getbitCommand(client *c) {
|
||||
* 256-bit registers so if `minlen` is not a multiple of 32 some of the bytes
|
||||
* will be skipped. They will be taken care for in the unoptimized loop in the
|
||||
* main bitopCommand function. */
|
||||
ATTRIBUTE_TARGET_AVX2_POPCOUNT
|
||||
ATTRIBUTE_TARGET_AVX2
|
||||
unsigned long bitopCommandAVX(unsigned char **keys, unsigned char *res,
|
||||
unsigned long op, unsigned long numkeys,
|
||||
unsigned long minlen)
|
||||
@@ -939,21 +940,13 @@ unsigned long bitopCommandAVX(unsigned char **keys, unsigned char *res,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Unlike other operations that do the same with all source keys
|
||||
* DIFF, DIFF1 and ANDOR all compute the disjunction of all the source keys
|
||||
* but the first one. We first store that disjunction in `lres` and later
|
||||
* compute the final operation using the first source key. */
|
||||
if (op != BITOP_DIFF && op != BITOP_DIFF1 && op != BITOP_ANDOR) {
|
||||
memcpy(res, keys[0], minlen);
|
||||
}
|
||||
|
||||
const __m256i max256 = _mm256_set1_epi64x(-1);
|
||||
const __m256i zero256 = _mm256_set1_epi64x(0);
|
||||
|
||||
switch (op) {
|
||||
case BITOP_AND:
|
||||
while (minlen >= step) {
|
||||
__m256i lres = _mm256_lddqu_si256((__m256i*)res);
|
||||
__m256i lres = _mm256_lddqu_si256((__m256i*)(keys[0]+processed));
|
||||
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
__m256i lkey = _mm256_lddqu_si256((__m256i*)(keys[i]+processed));
|
||||
@@ -965,12 +958,18 @@ unsigned long bitopCommandAVX(unsigned char **keys, unsigned char *res,
|
||||
minlen -= step;
|
||||
}
|
||||
break;
|
||||
/* Unlike other operations that do the same with all source keys
|
||||
* DIFF, DIFF1 and ANDOR all compute the disjunction of all the source keys
|
||||
* but the first one. We first store that disjunction in `lres` and later
|
||||
* compute the final operation using the first source key. */
|
||||
case BITOP_DIFF:
|
||||
case BITOP_DIFF1:
|
||||
case BITOP_ANDOR:
|
||||
case BITOP_OR:
|
||||
while (minlen >= step) {
|
||||
__m256i lres = _mm256_lddqu_si256((__m256i*)res);
|
||||
__m256i lres = (op == BITOP_OR) ?
|
||||
_mm256_lddqu_si256((__m256i*)(keys[0]+processed)) :
|
||||
zero256;
|
||||
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
__m256i lkey = _mm256_lddqu_si256((__m256i*)(keys[i]+processed));
|
||||
@@ -984,7 +983,7 @@ unsigned long bitopCommandAVX(unsigned char **keys, unsigned char *res,
|
||||
break;
|
||||
case BITOP_XOR:
|
||||
while (minlen >= step) {
|
||||
__m256i lres = _mm256_lddqu_si256((__m256i*)res);
|
||||
__m256i lres = _mm256_lddqu_si256((__m256i*)(keys[0]+processed));
|
||||
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
__m256i lkey = _mm256_lddqu_si256((__m256i*)(keys[i]+processed));
|
||||
@@ -998,7 +997,7 @@ unsigned long bitopCommandAVX(unsigned char **keys, unsigned char *res,
|
||||
break;
|
||||
case BITOP_NOT:
|
||||
while (minlen >= step) {
|
||||
__m256i lres = _mm256_lddqu_si256((__m256i*)res);
|
||||
__m256i lres = _mm256_lddqu_si256((__m256i*)(keys[0]+processed));
|
||||
lres = _mm256_xor_si256(lres, max256);
|
||||
_mm256_storeu_si256((__m256i*)res, lres);
|
||||
res += step;
|
||||
@@ -1008,7 +1007,7 @@ unsigned long bitopCommandAVX(unsigned char **keys, unsigned char *res,
|
||||
break;
|
||||
case BITOP_ONE:
|
||||
while (minlen >= step) {
|
||||
__m256i lres = _mm256_lddqu_si256((__m256i*)res);
|
||||
__m256i lres = _mm256_lddqu_si256((__m256i*)(keys[0]+processed));
|
||||
__m256i common_bits = zero256;
|
||||
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
@@ -1075,6 +1074,162 @@ unsigned long bitopCommandAVX(unsigned char **keys, unsigned char *res,
|
||||
}
|
||||
#endif /* HAVE_AVX2 */
|
||||
|
||||
#ifdef HAVE_AVX512
|
||||
/* Compute the given bitop operation using AVX512 intrinsics.
|
||||
* Return how many bytes were successfully processed, as AVX512 operates on
|
||||
* 512-bit registers so if `minlen` is not a multiple of 64 some of the bytes
|
||||
* will be skipped. They will be taken care for in the unoptimized loop in the
|
||||
* main bitopCommand function. */
|
||||
ATTRIBUTE_TARGET_AVX512
|
||||
unsigned long bitopCommandAVX512(unsigned char **keys, unsigned char *res,
|
||||
unsigned long op, unsigned long numkeys,
|
||||
unsigned long minlen)
|
||||
{
|
||||
const unsigned long step = sizeof(__m512i); /* 64 bytes */
|
||||
|
||||
unsigned long i;
|
||||
unsigned long processed = 0;
|
||||
unsigned char *res_start = res;
|
||||
unsigned char *fst_key = keys[0];
|
||||
|
||||
if (minlen < step) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const __m512i max512 = _mm512_set1_epi64(-1);
|
||||
const __m512i zero512 = _mm512_set1_epi64(0);
|
||||
switch (op) {
|
||||
case BITOP_AND:
|
||||
while (minlen >= step) {
|
||||
__m512i lres = _mm512_loadu_si512((__m512i*)(keys[0]+processed));
|
||||
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
__m512i lkey = _mm512_loadu_si512((__m512i*)(keys[i]+processed));
|
||||
lres = _mm512_and_si512(lres, lkey);
|
||||
}
|
||||
_mm512_storeu_si512((__m512i*)res, lres);
|
||||
res += step;
|
||||
processed += step;
|
||||
minlen -= step;
|
||||
}
|
||||
break;
|
||||
/* Unlike other operations that do the same with all source keys
|
||||
* DIFF, DIFF1 and ANDOR all compute the disjunction of all the source keys
|
||||
* but the first one. We first store that disjunction in `lres` and later
|
||||
* compute the final operation using the first source key. */
|
||||
case BITOP_DIFF:
|
||||
case BITOP_DIFF1:
|
||||
case BITOP_ANDOR:
|
||||
case BITOP_OR:
|
||||
while (minlen >= step) {
|
||||
__m512i lres = (op == BITOP_OR) ?
|
||||
_mm512_loadu_si512((__m512i*)(keys[0]+processed)) :
|
||||
zero512;
|
||||
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
__m512i lkey = _mm512_loadu_si512((__m512i*)(keys[i]+processed));
|
||||
lres = _mm512_or_si512(lres, lkey);
|
||||
}
|
||||
_mm512_storeu_si512((__m512i*)res, lres);
|
||||
res += step;
|
||||
processed += step;
|
||||
minlen -= step;
|
||||
}
|
||||
break;
|
||||
case BITOP_XOR:
|
||||
while (minlen >= step) {
|
||||
__m512i lres = _mm512_loadu_si512((__m512i*)(keys[0]+processed));
|
||||
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
__m512i lkey = _mm512_loadu_si512((__m512i*)(keys[i]+processed));
|
||||
lres = _mm512_xor_si512(lres, lkey);
|
||||
}
|
||||
_mm512_storeu_si512((__m512i*)res, lres);
|
||||
res += step;
|
||||
processed += step;
|
||||
minlen -= step;
|
||||
}
|
||||
break;
|
||||
case BITOP_NOT:
|
||||
while (minlen >= step) {
|
||||
__m512i lres = _mm512_loadu_si512((__m512i*)(keys[0]+processed));
|
||||
lres = _mm512_xor_si512(lres, max512);
|
||||
_mm512_storeu_si512((__m512i*)res, lres);
|
||||
res += step;
|
||||
processed += step;
|
||||
minlen -= step;
|
||||
}
|
||||
break;
|
||||
case BITOP_ONE:
|
||||
while (minlen >= step) {
|
||||
__m512i lres = _mm512_loadu_si512((__m512i*)(keys[0]+processed));
|
||||
__m512i common_bits = zero512;
|
||||
|
||||
for (i = 1; i < numkeys; i++) {
|
||||
__m512i lkey = _mm512_loadu_si512((__m512i*)(keys[i]+processed));
|
||||
/* common_bits |= (lres & lkey): ternary-logic with imm8 0xEA == c|(a&b)
|
||||
* (a=lres, b=lkey, c=common_bits), replacing a separate AND+OR. */
|
||||
common_bits = _mm512_ternarylogic_epi32(lres, lkey, common_bits, 0xEA);
|
||||
|
||||
lres = _mm512_xor_si512(lres, lkey);
|
||||
}
|
||||
lres = _mm512_andnot_si512(common_bits, lres);
|
||||
_mm512_storeu_si512((__m512i*)res, lres);
|
||||
res += step;
|
||||
processed += step;
|
||||
minlen -= step;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
res = res_start;
|
||||
switch (op) {
|
||||
case BITOP_DIFF:
|
||||
for (i = 0; i < processed; i += step) {
|
||||
__m512i lres = _mm512_loadu_si512((__m512i*)res);
|
||||
__m512i fkey = _mm512_loadu_si512((__m512i*)fst_key);
|
||||
|
||||
lres = _mm512_andnot_si512(lres, fkey);
|
||||
_mm512_storeu_si512((__m512i*)res, lres);
|
||||
|
||||
res += step;
|
||||
fst_key += step;
|
||||
}
|
||||
break;
|
||||
case BITOP_DIFF1:
|
||||
for (i = 0; i < processed; i += step) {
|
||||
__m512i lres = _mm512_loadu_si512((__m512i*)res);
|
||||
__m512i fkey = _mm512_loadu_si512((__m512i*)fst_key);
|
||||
|
||||
lres = _mm512_andnot_si512(fkey, lres);
|
||||
_mm512_storeu_si512((__m512i*)res, lres);
|
||||
|
||||
res += step;
|
||||
fst_key += step;
|
||||
}
|
||||
break;
|
||||
case BITOP_ANDOR:
|
||||
for (i = 0; i < processed; i += step) {
|
||||
__m512i lres = _mm512_loadu_si512((__m512i*)res);
|
||||
__m512i fkey = _mm512_loadu_si512((__m512i*)fst_key);
|
||||
|
||||
lres = _mm512_and_si512(fkey, lres);
|
||||
_mm512_storeu_si512((__m512i*)res, lres);
|
||||
|
||||
res += step;
|
||||
fst_key += step;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
#endif /* HAVE_AVX512 */
|
||||
|
||||
/* BITOP op_name target_key src_key1 src_key2 src_key3 ... src_keyN */
|
||||
REDIS_NO_SANITIZE("alignment")
|
||||
void bitopCommand(client *c) {
|
||||
@@ -1163,36 +1318,40 @@ void bitopCommand(client *c) {
|
||||
res = (unsigned char*) sdsnewlen(NULL,maxlen);
|
||||
unsigned char output, byte, disjunction, common_bits;
|
||||
unsigned long i;
|
||||
int useAVX2 = 0;
|
||||
int useAVX = 0;
|
||||
|
||||
/* Number of bytes processed from each source key */
|
||||
j = 0;
|
||||
|
||||
#if defined(HAVE_AVX512)
|
||||
if (BITOP_USE_AVX512 && (minlen >= 10000) && (numkeys >= 8)) {
|
||||
j = bitopCommandAVX512(src, res, op, numkeys, minlen);
|
||||
|
||||
serverAssert(minlen >= j);
|
||||
minlen -= j;
|
||||
|
||||
useAVX = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_AVX2)
|
||||
if (BITOP_USE_AVX2) {
|
||||
if (!useAVX && BITOP_USE_AVX2) {
|
||||
j = bitopCommandAVX(src, res, op, numkeys, minlen);
|
||||
|
||||
serverAssert(minlen >= j);
|
||||
minlen -= j;
|
||||
|
||||
useAVX2 = 1;
|
||||
useAVX = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(USE_ALIGNED_ACCESS)
|
||||
/* We don't have AVX2 but we still have fast path:
|
||||
* as far as we have data for all the input bitmaps we
|
||||
* can take a fast path that performs much better than the
|
||||
* vanilla algorithm. On ARM we skip the fast path since it will
|
||||
* result in GCC compiling the code using multiple-words load/store
|
||||
* operations that are not supported even in ARM >= v6. */
|
||||
if (minlen >= sizeof(unsigned long)*4) {
|
||||
/* We can't have entered the AVX2 path since minlen >= sizeof(unsigned long)*4
|
||||
* AVX2 path operates on steps of sizeof(__m256i) which for 64-bit
|
||||
* machines (the only ones supporting AVX2) is equal to
|
||||
* sizeof(unsigned long)*4. That means after the AVX2
|
||||
* path minlen will necessarily be < sizeof(unsigned long)*4. */
|
||||
serverAssert(!useAVX2);
|
||||
/* If no SIMD path was used (no AVX2/AVX512), fall back
|
||||
* to a word-at-a-time fast path that is still much better
|
||||
* than the byte-by-byte loop below. On ARM we skip this since
|
||||
* it would cause GCC to emit multiple-word load/store ops
|
||||
* not supported even on ARM >= v6. */
|
||||
if (!useAVX && minlen >= sizeof(unsigned long)*4) {
|
||||
|
||||
unsigned long **lp = (unsigned long**)src;
|
||||
unsigned long *lres = (unsigned long*) res;
|
||||
@@ -1447,7 +1606,7 @@ void bitopCommand(client *c) {
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"set",targetkey,c->db->id);
|
||||
server.dirty++;
|
||||
} else if (dbDelete(c->db,targetkey)) {
|
||||
signalModifiedKey(c,c->db,targetkey);
|
||||
keyModified(c,c->db,targetkey,NULL,1);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",targetkey,c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
@@ -1722,7 +1881,7 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
kvobj *o;
|
||||
uint64_t bitoffset;
|
||||
int j, numops = 0, changes = 0;
|
||||
size_t strOldSize, strGrowSize = 0;
|
||||
size_t strOldSize = 0, strGrowSize = 0;
|
||||
struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
|
||||
int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
|
||||
int readonly = 1;
|
||||
@@ -1948,10 +2107,9 @@ void bitfieldGeneric(client *c, int flags) {
|
||||
* update the keysizes histogram. Otherwise, the histogram already
|
||||
* updated in lookupStringForBitCommand() by calling dbAdd(). */
|
||||
if ((strOldSize > 0) && (strGrowSize != 0))
|
||||
updateKeysizesHist(c->db, getKeySlot(c->argv[1]->ptr), OBJ_STRING,
|
||||
strOldSize, strOldSize + strGrowSize);
|
||||
updateKeysizesHist(c->db, OBJ_STRING, strOldSize, strOldSize + strGrowSize);
|
||||
|
||||
signalModifiedKey(c,c->db,c->argv[1]);
|
||||
keyModified(c,c->db,c->argv[1],o,1);
|
||||
notifyKeyspaceEvent(NOTIFY_STRING,"setbit",c->argv[1],c->db->id);
|
||||
server.dirty += changes;
|
||||
}
|
||||
|
||||
+17
-3
@@ -237,7 +237,11 @@ int blockedClientMayTimeout(client *c) {
|
||||
* unblockClient() will be called with the same client as argument. */
|
||||
void replyToBlockedClientTimedOut(client *c) {
|
||||
if (c->bstate.btype == BLOCKED_LAZYFREE) {
|
||||
addReply(c, shared.ok); /* No reason lazy-free to fail */
|
||||
/* SFLUSH: reply with empty array, FLUSH*: reply with OK */
|
||||
if (c->cmd && c->cmd->proc == sflushCommand)
|
||||
addReplyArrayLen(c, 0);
|
||||
else
|
||||
addReply(c, shared.ok); /* No reason lazy-free to fail */
|
||||
} else if (c->bstate.btype == BLOCKED_LIST ||
|
||||
c->bstate.btype == BLOCKED_ZSET ||
|
||||
c->bstate.btype == BLOCKED_STREAM) {
|
||||
@@ -297,7 +301,11 @@ void disconnectAllBlockedClients(void) {
|
||||
continue;
|
||||
|
||||
if (c->bstate.btype == BLOCKED_LAZYFREE) {
|
||||
addReply(c, shared.ok); /* No reason lazy-free to fail */
|
||||
/* SFLUSH: reply with empty array, FLUSH*: reply with OK */
|
||||
if (c->cmd && c->cmd->proc == sflushCommand)
|
||||
addReplyArrayLen(c, 0);
|
||||
else
|
||||
addReply(c, shared.ok);
|
||||
updateStatsOnUnblock(c, 0, 0, 0);
|
||||
c->flags &= ~CLIENT_PENDING_COMMAND;
|
||||
unblockClient(c, 1);
|
||||
@@ -691,7 +699,13 @@ static void unblockClientOnKey(client *c, robj *key) {
|
||||
client *old_client = server.current_client;
|
||||
server.current_client = c;
|
||||
enterExecutionUnit(1, 0);
|
||||
processCommandAndResetClient(c);
|
||||
if (processCommandAndResetClient(c) == C_ERR) {
|
||||
/* Client was freed during command processing, exit immediately */
|
||||
exitExecutionUnit();
|
||||
server.current_client = old_client;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(c->flags & CLIENT_BLOCKED)) {
|
||||
if (c->flags & CLIENT_MODULE) {
|
||||
moduleCallCommandUnblockedHandler(c);
|
||||
|
||||
@@ -0,0 +1,822 @@
|
||||
/* Implementation of a topK structure using CuckooHeavyKeeper algorithm
|
||||
*
|
||||
* Implementation is based on the paper "Cuckoo Heavy Keeper and the balancing
|
||||
* act of maintaining heavy hitters in stream processing" by Vinh Quang Ngo and
|
||||
* Marina Papatriantafilou. Also, the accompanying C++ implementation was used
|
||||
* as a reference point: https://github.com/vinhqngo5/Cuckoo_Heavy_Keeper
|
||||
* Main changes are addition of a min-heap so we can keep names of the top K
|
||||
* elements - idea comes from RedisBloom's TopK structure.
|
||||
*
|
||||
* Copyright (c) 2026-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of (a) the Redis Source Available License 2.0
|
||||
* (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
|
||||
* GNU Affero General Public License v3 (AGPLv3).
|
||||
*/
|
||||
|
||||
#include "chk.h"
|
||||
#include "redisassert.h"
|
||||
#include "zmalloc.h"
|
||||
#include "xxhash.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Lobby to heavy item promotion threshold */
|
||||
#define LOBBY_PROMOTION_THRESHOLD 16
|
||||
|
||||
#ifndef static_assert
|
||||
#define static_assert(expr, lit) extern char __static_assert_failure[(expr) ? 1:-1]
|
||||
#endif
|
||||
|
||||
static_assert(LOBBY_PROMOTION_THRESHOLD < CHK_LUT_SIZE,
|
||||
"Lobby promotion threshold should be less then the LUT size to "
|
||||
"ensure constant operations during decayCounter!");
|
||||
|
||||
/* After a heavy item is demoted is starts recursively kicking out other heavy
|
||||
* items in the case it should stay heavy (defined by isHeavyHitter). In
|
||||
* principle this process could go over all the items in the chkTopK's tables
|
||||
* so it's artificially limited by this constant. */
|
||||
#define MAX_KICKS 16
|
||||
|
||||
/* An item is defined as heavy hitter if its count is more or equal to x * N
|
||||
* where x is a threshold constant (HEAVY_RATIO) and N is the total count the
|
||||
* chkTopK structure has accumulated. See the paper for more info. */
|
||||
#define HEAVY_RATIO 0.008
|
||||
|
||||
/* A unique seed for the items when storing them in the heap so it's not related
|
||||
* to the cuckoo's hashes. Also, we don't need the less-bit hash here as the
|
||||
* heap does not take much memory so we avoid needless possible collisions. */
|
||||
#define HEAP_SEED 1919
|
||||
|
||||
typedef struct {
|
||||
size_t idx[CHK_NUM_TABLES];
|
||||
fingerprint_t fp;
|
||||
} fpAndIdx;
|
||||
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
/* Heap operations */
|
||||
static chkHeapBucket *chkCheckExistInHeap(chkTopK *topk, const char *item, int itemlen, uint64_t fp) {
|
||||
for (int32_t i = topk->k - 1; i >= 0; --i) {
|
||||
chkHeapBucket *bucket = topk->heap + i;
|
||||
if (bucket->fp == fp && bucket->item &&
|
||||
sdslen(bucket->item) == (size_t)itemlen &&
|
||||
memcmp(bucket->item, item, itemlen) == 0)
|
||||
{
|
||||
return bucket;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void chkHeapifyDown(chkHeapBucket *array, size_t len, size_t start) {
|
||||
size_t child = start;
|
||||
|
||||
if (len < 2 || (len - 2) / 2 < child) {
|
||||
return;
|
||||
}
|
||||
child = 2 * child + 1;
|
||||
if ((child + 1) < len && (array[child].count > array[child + 1].count)) {
|
||||
++child;
|
||||
}
|
||||
if (array[child].count > array[start].count) {
|
||||
return;
|
||||
}
|
||||
|
||||
chkHeapBucket top = {0};
|
||||
top = array[start];
|
||||
do {
|
||||
memcpy(&array[start], &array[child], sizeof(chkHeapBucket));
|
||||
start = child;
|
||||
|
||||
if ((len - 2) / 2 < child) {
|
||||
break;
|
||||
}
|
||||
child = 2 * child + 1;
|
||||
|
||||
if ((child + 1) < len && (array[child].count > array[child + 1].count)) {
|
||||
++child;
|
||||
}
|
||||
} while (array[child].count < top.count);
|
||||
memcpy(&array[start], &top, sizeof(chkHeapBucket));
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* chkTopK operations
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* Create the chkTopK structure. Note, CHK paper recommends decay=1.08.
|
||||
* numbuckets must be a power of 2. Recommended size for numbuckets is at least
|
||||
* 7 or 8 times k. */
|
||||
chkTopK *chkTopKCreate(int k, int numbuckets, double decay) {
|
||||
/* Number of buckets need to be a power of 2 for better performance - we
|
||||
* have better cache locality of the tables and faster table indices
|
||||
* calculations. */
|
||||
assert(k > 0 && (numbuckets & (numbuckets - 1)) == 0);
|
||||
|
||||
size_t usable = 0;
|
||||
chkTopK *topk = zcalloc_usable(sizeof(chkTopK), &usable);
|
||||
topk->alloc_size += usable;
|
||||
|
||||
for (int i = 0; i < CHK_NUM_TABLES; ++i) {
|
||||
topk->tables[i] = zcalloc_usable(sizeof(chkBucket) * numbuckets, &usable);
|
||||
topk->alloc_size += usable;
|
||||
}
|
||||
|
||||
topk->heap = zcalloc_usable(sizeof(chkHeapBucket) * k, &usable);
|
||||
topk->alloc_size += usable;
|
||||
|
||||
topk->decay = decay;
|
||||
topk->inv_decay = 1. / decay;
|
||||
topk->k = k;
|
||||
topk->numbuckets = numbuckets;
|
||||
|
||||
topk->lut_decay_exp[0] = 0;
|
||||
topk->lut_min_decay[0] = 0;
|
||||
topk->lut_decay_prob[0] = 0;
|
||||
for (int i = 1; i < CHK_LUT_SIZE + 1; ++i) {
|
||||
topk->lut_decay_exp[i] = topk->lut_decay_exp[i - 1] + pow(topk->decay, i - 1);
|
||||
topk->lut_min_decay[i] = topk->lut_decay_exp[i] - topk->lut_decay_exp[i - 1];
|
||||
topk->lut_decay_prob[i] = pow(topk->inv_decay, i);
|
||||
}
|
||||
|
||||
return topk;
|
||||
}
|
||||
|
||||
/* Release chkTopK resources */
|
||||
void chkTopKRelease(chkTopK *topk) {
|
||||
size_t usable;
|
||||
for (int i = 0; i < CHK_NUM_TABLES; ++i) {
|
||||
zfree_usable(topk->tables[i], &usable);
|
||||
topk->alloc_size -= usable;
|
||||
}
|
||||
for (int i = 0; i < topk->k; ++i) {
|
||||
if (topk->heap[i].item) {
|
||||
topk->alloc_size -= sdsAllocSize(topk->heap[i].item);
|
||||
sdsfree(topk->heap[i].item);
|
||||
}
|
||||
}
|
||||
zfree_usable(topk->heap, &usable);
|
||||
topk->alloc_size -= usable;
|
||||
debugAssert(topk->alloc_size == zmalloc_usable_size(topk));
|
||||
|
||||
zfree(topk);
|
||||
}
|
||||
|
||||
static inline int generateAltIdx(fingerprint_t fp, int idx, int numbuckets) {
|
||||
return (idx ^ (0x5bd1e995 * (size_t)fp)) & (numbuckets - 1);
|
||||
}
|
||||
|
||||
fpAndIdx generateItemFpAndIdxs(chkTopK *topk, char *item, int itemlen) {
|
||||
uint64_t hash = XXH3_64bits_withSeed(item, itemlen, 0);
|
||||
|
||||
fpAndIdx res;
|
||||
res.fp = (hash & 0xFFFF); /* Only use 16 bits for fingerprint */
|
||||
|
||||
/* Note numbuckets are a power of 2 so we don't use modulo for index calc */
|
||||
res.idx[0] = (hash >> 32) & (topk->numbuckets - 1);
|
||||
for (int i = 1; i < CHK_NUM_TABLES; ++i) {
|
||||
res.idx[i] = generateAltIdx(res.fp, res.idx[i-1], topk->numbuckets);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int table_idx;
|
||||
int pos;
|
||||
} checkEntryRes;
|
||||
|
||||
/* Check if `item` is a heavy entry. If so we bump its count. If not - we make
|
||||
* it a heavy entry immediately if there is an empty spot, thus skipping the
|
||||
* lobby as an optimization. */
|
||||
checkEntryRes checkHeavyEntries(chkTopK *topk, fpAndIdx item, counter_t weight) {
|
||||
int empty_table_idx = -1;
|
||||
int empty_pos = -1;
|
||||
|
||||
for (int i = 0; i < CHK_NUM_TABLES; ++i) {
|
||||
int idx = item.idx[i];
|
||||
|
||||
chkBucket *bucket = &topk->tables[i][idx];
|
||||
for (int j = 0; j < CHK_HEAVY_ENTRIES_PER_BUCKET; ++j) {
|
||||
chkHeavyEntry *e = &bucket->heavy_entries[j];
|
||||
if (e->count > 0) {
|
||||
if (e->fp == item.fp) {
|
||||
e->count += weight;
|
||||
|
||||
checkEntryRes res = { i, j };
|
||||
return res;
|
||||
}
|
||||
} else if (empty_table_idx == -1) {
|
||||
empty_table_idx = i;
|
||||
empty_pos = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty_table_idx == -1) {
|
||||
checkEntryRes res = { -1, -1 };
|
||||
return res;
|
||||
}
|
||||
|
||||
/* If there is an empty slot in the heavy entries just put the item there
|
||||
* instead of going through the lobby first (optimization as per the paper) */
|
||||
int idx = item.idx[empty_table_idx];
|
||||
chkHeavyEntry *e = &topk->tables[empty_table_idx][idx].heavy_entries[empty_pos];
|
||||
e->fp = item.fp;
|
||||
e->count = weight;
|
||||
|
||||
checkEntryRes res = {empty_table_idx, empty_pos};
|
||||
return res;
|
||||
}
|
||||
|
||||
/* A heavy hitter is defined by the paper as an item with counter more or equal
|
||||
* to phi * N, where phi is a constant and N is the total count the structure
|
||||
* has recorded up to that point */
|
||||
int isHeavyHitter(chkTopK *topk, counter_t cnt) {
|
||||
return cnt >= (topk->total * HEAVY_RATIO);
|
||||
}
|
||||
|
||||
/* After a lobby item is promoted it may be placed on a heavy item's spot. The
|
||||
* latter is kicked out, but it may recursively kick out another heavy item.
|
||||
* The process is limited by MAX_KICKS and also by the fact that during updates
|
||||
* one of the kicked out items may have its counter decayed so much - it's not
|
||||
* passing the heavy item threshold (see isHeavyHitter). */
|
||||
void kickout(chkTopK *topk, chkHeavyEntry entry, int idx, int table_idx) {
|
||||
for (int i = 0; i < MAX_KICKS; ++i) {
|
||||
/* Do not try to swap with any entries if we don't reach the heavy
|
||||
* hitter threshold */
|
||||
if (!isHeavyHitter(topk, entry.count)) return;
|
||||
|
||||
/* Find the heavy entry in the alt bucket in the other table with
|
||||
* minimum count. If there is empty entry there just occupy it, else
|
||||
* recursively kick the minimal one out.
|
||||
* To find the alt bucket we need to compute the alt index from the
|
||||
* fingerprint of the kicked-out entry. */
|
||||
table_idx = 1 - table_idx;
|
||||
idx = generateAltIdx(entry.fp, idx, topk->numbuckets);
|
||||
|
||||
chkBucket *bucket = &topk->tables[table_idx][idx];
|
||||
counter_t min = (counter_t)-1;
|
||||
int min_pos = -1;
|
||||
for (int j = 0; j < CHK_HEAVY_ENTRIES_PER_BUCKET; ++j) {
|
||||
chkHeavyEntry *e = &bucket->heavy_entries[j];
|
||||
if (e->count == 0) {
|
||||
*e = entry;
|
||||
return;
|
||||
}
|
||||
if (e->count < min) {
|
||||
min = e->count;
|
||||
min_pos = j;
|
||||
}
|
||||
}
|
||||
|
||||
chkHeavyEntry old_entry = bucket->heavy_entries[min_pos];
|
||||
bucket->heavy_entries[min_pos] = entry;
|
||||
entry = old_entry;
|
||||
}
|
||||
}
|
||||
|
||||
/* When a lobby entry's counter passes the promotion threshold we try to promote
|
||||
* it with some probability. See the paper for more details. If promotion is
|
||||
* successful the lobby entry may kick out a heavy one - see kickout() */
|
||||
int tryPromoteAndKickout(chkTopK *topk, fpAndIdx item, counter_t new_count,
|
||||
int table_idx)
|
||||
{
|
||||
int idx = item.idx[table_idx];
|
||||
chkBucket *bucket = &topk->tables[table_idx][idx];
|
||||
counter_t min = (counter_t)-1; /* counter_t is unsigned */
|
||||
int min_idx = -1;
|
||||
|
||||
/* We search for heavy item bucket of the promoted lobby entry. We may have
|
||||
* an empty space which we immediately occupy. Otherwise we choose the
|
||||
* bucket with lowest counter */
|
||||
for (int i = 0; i < CHK_HEAVY_ENTRIES_PER_BUCKET; ++i) {
|
||||
if (bucket->heavy_entries[i].count == 0) {
|
||||
bucket->heavy_entries[i].fp = item.fp;
|
||||
bucket->heavy_entries[i].count = new_count;
|
||||
return i;
|
||||
}
|
||||
if (bucket->heavy_entries[i].count < min) {
|
||||
min = bucket->heavy_entries[i].count;
|
||||
min_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the heavy entry that is going to be kicked out has a counter lower
|
||||
* than the lobby's one we always kick it out */
|
||||
if (min > new_count) {
|
||||
double prob = (new_count - LOBBY_PROMOTION_THRESHOLD) /
|
||||
(double)(min - LOBBY_PROMOTION_THRESHOLD);
|
||||
|
||||
if ((rand() / (double)RAND_MAX) >= prob) return -1;
|
||||
}
|
||||
|
||||
chkHeavyEntry to_kickout = bucket->heavy_entries[min_idx];
|
||||
/* Note, that here the promoted item keeps the old count as per the paper */
|
||||
bucket->heavy_entries[min_idx].fp = bucket->lobby_entry.fp;
|
||||
|
||||
bucket->lobby_entry.count = 0;
|
||||
bucket->lobby_entry.fp = 0;
|
||||
|
||||
kickout(topk, to_kickout, idx, table_idx);
|
||||
|
||||
return min_idx;
|
||||
}
|
||||
|
||||
/* Check if an item is a lobby entry */
|
||||
checkEntryRes checkLobbyEntries(chkTopK *topk, fpAndIdx item, counter_t weight) {
|
||||
for (int i = 0; i < CHK_NUM_TABLES; ++i) {
|
||||
int idx = item.idx[i];
|
||||
|
||||
chkBucket *bucket = &topk->tables[i][idx];
|
||||
chkLobbyEntry *e = &bucket->lobby_entry;
|
||||
|
||||
/* No match or empty lobby entry */
|
||||
if (e->fp != item.fp || e->count == 0) continue;
|
||||
|
||||
/* If we don't cross the threshold just update the counter */
|
||||
uint64_t new_count = (uint64_t)e->count + weight;
|
||||
if (new_count < LOBBY_PROMOTION_THRESHOLD) {
|
||||
e->count = (uint16_t)new_count;
|
||||
|
||||
checkEntryRes res = { i, -1 };
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Try to promote the entry to heavy entry if we crossed the threshold.
|
||||
* Else just set the counter to the value of the threshold */
|
||||
int kickout_pos = tryPromoteAndKickout(topk, item, new_count, i);
|
||||
if (kickout_pos != -1) {
|
||||
checkEntryRes res = {i, kickout_pos};
|
||||
return res;
|
||||
}
|
||||
|
||||
e->count = LOBBY_PROMOTION_THRESHOLD;
|
||||
checkEntryRes res = { i, -1 };
|
||||
return res;
|
||||
}
|
||||
|
||||
checkEntryRes res = { -1, -1 };
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Probability to decay cnt with 1.
|
||||
* Equal to pow(decay, -cnt) */
|
||||
static inline double getDecayProb(chkTopK *topk, counter_t cnt) {
|
||||
if (cnt < CHK_LUT_SIZE) {
|
||||
return topk->lut_decay_prob[cnt];
|
||||
}
|
||||
|
||||
return pow(topk->lut_decay_prob[CHK_LUT_SIZE],
|
||||
((double)cnt / (CHK_LUT_SIZE))) *
|
||||
topk->lut_decay_prob[cnt % (CHK_LUT_SIZE)];
|
||||
}
|
||||
|
||||
/* Expected decay steps to decay cnt to 0.
|
||||
* Equal to sum(pow(decay, i)) for i in [0; cnt] */
|
||||
static inline double getExpDecayCount(chkTopK *topk, lobby_counter_t cnt) {
|
||||
return topk->lut_decay_exp[cnt];
|
||||
}
|
||||
|
||||
/* Expected minimum decay steps to decay cnt with 1. Since probability is
|
||||
* pow(decay, -cnt) it's equal to pow(decay, cnt) */
|
||||
static inline double getMinDecayCount(chkTopK *topk, counter_t cnt) {
|
||||
if (cnt < CHK_LUT_SIZE) {
|
||||
return topk->lut_min_decay[cnt];
|
||||
}
|
||||
|
||||
return pow(topk->lut_min_decay[CHK_LUT_SIZE],
|
||||
((double)cnt / (CHK_LUT_SIZE))) *
|
||||
topk->lut_min_decay[cnt % (CHK_LUT_SIZE)];
|
||||
}
|
||||
|
||||
/* When there is a hash-collission between lobby entries we decay the existing
|
||||
* lobby entry with the weight of the new one. Return the counter after decaying. */
|
||||
lobby_counter_t chkDecayCounter(chkTopK *topk, lobby_counter_t cnt, counter_t weight) {
|
||||
if (weight == 0) return cnt;
|
||||
|
||||
/* Unweighted update - just decay with probability pow(decay, -cnt) */
|
||||
if (weight == 1) {
|
||||
double prob = getDecayProb(topk, (counter_t)cnt);
|
||||
if ((rand() / (double)RAND_MAX) < prob) {
|
||||
return cnt - 1;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/* For weighted updates we simulate multiple unweighted ones */
|
||||
|
||||
/* Weight is smaller than the minimum amount of decay steps required to
|
||||
* decay the counter with probability of 100% so again we roll the dice */
|
||||
double min_decay = getMinDecayCount(topk, cnt);
|
||||
if (weight < (counter_t)min_decay) {
|
||||
double prob = weight / min_decay;
|
||||
if ((rand() / (double)RAND_MAX) < prob) {
|
||||
return cnt - 1;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
/* Weight is more than the expected amount of decay steps to decay the
|
||||
* counter to 0. */
|
||||
double exp_decays = getExpDecayCount(topk, cnt);
|
||||
if (weight >= (counter_t)exp_decays)
|
||||
return 0;
|
||||
|
||||
/* Weight is large enough to decay the counter to cnt - X where 0 < X < cnt.
|
||||
* We binary search for the largest value `C` such that:
|
||||
*
|
||||
* (expected decay ops for `C`) >= (expected decay ops for `cnt`) - `weight`
|
||||
* i.e lut_decay_exp[C] + weight >= lut_decay_exp[cnt]
|
||||
*
|
||||
* Note that since cnt is a lobby counter it will necessarily be less or
|
||||
* equal than LOBBY_PROMOTION_THRESHOLD, so although we binary search this
|
||||
* is a O(1) operation */
|
||||
int left = 0;
|
||||
int right = cnt;
|
||||
while (left < right) {
|
||||
int mid = left + (right - left) / 2;
|
||||
|
||||
if (topk->lut_decay_exp[mid] + weight >= topk->lut_decay_exp[cnt]) {
|
||||
right = mid;
|
||||
} else {
|
||||
left = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return left;
|
||||
}
|
||||
|
||||
/* Update weighted item. If another one was expelled from the topK list -
|
||||
* return it. Caller is responsible for releasing it */
|
||||
sds chkTopKUpdate(chkTopK *topk, char *item, int itemlen, counter_t weight)
|
||||
{
|
||||
if (weight == 0) return NULL;
|
||||
|
||||
topk->total += weight;
|
||||
|
||||
/* Generate a fingerprint and indices for both cuckoo tables. */
|
||||
fpAndIdx itemFpIdx = generateItemFpAndIdxs(topk, item, itemlen);
|
||||
|
||||
/* Check if the item is amongst the heavy entries. If so we just update its
|
||||
* counter. */
|
||||
checkEntryRes res = checkHeavyEntries(topk, itemFpIdx, weight);
|
||||
if (res.table_idx != -1) {
|
||||
goto update_heap;
|
||||
}
|
||||
|
||||
/* If the item is not already heavy it may be in the lobby. If so we'll
|
||||
* increase its counter and promote it to a heavy entry if it passes the
|
||||
* threshold */
|
||||
res = checkLobbyEntries(topk, itemFpIdx, weight);
|
||||
if (res.table_idx != -1) {
|
||||
goto update_heap;
|
||||
}
|
||||
|
||||
/* Item is not tracked at all. Check for empty lobby entries - if there is
|
||||
* any - place the item there. The weight may be higher than the promotional
|
||||
* threshold in which case we'll try to promote it. */
|
||||
for (int i = 0; i < CHK_NUM_TABLES; ++i) {
|
||||
int idx = itemFpIdx.idx[i];
|
||||
chkBucket *bucket = &topk->tables[i][idx];
|
||||
if (bucket->lobby_entry.count == 0) {
|
||||
bucket->lobby_entry.fp = itemFpIdx.fp;
|
||||
|
||||
res.table_idx = i;
|
||||
res.pos = -1;
|
||||
|
||||
if (weight < LOBBY_PROMOTION_THRESHOLD) {
|
||||
bucket->lobby_entry.count = weight;
|
||||
} else {
|
||||
int kickout_pos = tryPromoteAndKickout(topk, itemFpIdx, weight, i);
|
||||
if (kickout_pos != -1) {
|
||||
res.pos = kickout_pos;
|
||||
} else {
|
||||
bucket->lobby_entry.count = LOBBY_PROMOTION_THRESHOLD;
|
||||
}
|
||||
}
|
||||
|
||||
goto update_heap;
|
||||
}
|
||||
}
|
||||
|
||||
/* If there are no empty lobby entries choose a table deterministically,
|
||||
* decay its lobby counter and update */
|
||||
int table_idx = itemFpIdx.fp & 1;
|
||||
int idx = itemFpIdx.idx[table_idx];
|
||||
|
||||
chkLobbyEntry *e = &topk->tables[table_idx][idx].lobby_entry;
|
||||
|
||||
/* new_count is the count of `e` after decaying it with weight */
|
||||
lobby_counter_t new_count = chkDecayCounter(topk, e->count, weight);
|
||||
|
||||
/* if the chosen lobby entry has decayed its counter to 0, it's replaced by
|
||||
* the new entry. Note, in that case the new entry has it's weight
|
||||
* decreased by the approximate amount of decay operations needed to decay
|
||||
* the old entry. */
|
||||
if (new_count == 0) {
|
||||
e->fp = itemFpIdx.fp;
|
||||
counter_t exp_decay_cnt = getExpDecayCount(topk, e->count);
|
||||
e->count = exp_decay_cnt >= weight ?
|
||||
1 : (lobby_counter_t)min(255, weight - exp_decay_cnt);
|
||||
} else {
|
||||
e->count = new_count;
|
||||
}
|
||||
|
||||
if (e->count >= LOBBY_PROMOTION_THRESHOLD) {
|
||||
int kickout_pos = tryPromoteAndKickout(topk, itemFpIdx, e->count, table_idx);
|
||||
if (kickout_pos != -1) {
|
||||
res.table_idx = table_idx;
|
||||
res.pos = kickout_pos;
|
||||
}
|
||||
}
|
||||
|
||||
/* After a change in the structure has occurred we check if we also need to
|
||||
* update the heap - i.e bump a new item in it, or reorder an old item if
|
||||
* it's counter went up. */
|
||||
update_heap:
|
||||
if (res.table_idx == -1 || res.pos == -1)
|
||||
return NULL;
|
||||
|
||||
table_idx = res.table_idx;
|
||||
idx = itemFpIdx.idx[table_idx];
|
||||
|
||||
counter_t heap_min = topk->heap[0].count;
|
||||
chkHeavyEntry *entry = &topk->tables[table_idx][idx].heavy_entries[res.pos];
|
||||
|
||||
if (entry->count < heap_min)
|
||||
return NULL;
|
||||
|
||||
/* Heap uses different hash than the cuckoo tables */
|
||||
uint64_t fp = XXH3_64bits_withSeed(item, itemlen, HEAP_SEED);
|
||||
chkHeapBucket *itemHeapPtr = chkCheckExistInHeap(topk, item, itemlen, fp);
|
||||
if (itemHeapPtr != NULL) {
|
||||
itemHeapPtr->count = entry->count;
|
||||
chkHeapifyDown(topk->heap, topk->k, itemHeapPtr - topk->heap);
|
||||
} else {
|
||||
/* We know the new entry has bigger count than the min-element so it's
|
||||
* safe to expel it. */
|
||||
sds expelled = topk->heap[0].item;
|
||||
if (expelled) topk->alloc_size -= sdsAllocSize(expelled);
|
||||
|
||||
topk->heap[0].count = entry->count;
|
||||
topk->heap[0].fp = fp;
|
||||
topk->heap[0].item = sdsnewlen(item, itemlen);
|
||||
topk->alloc_size += sdsAllocSize(topk->heap[0].item);
|
||||
|
||||
chkHeapifyDown(topk->heap, topk->k, 0);
|
||||
return expelled;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cmpchkHeapBucket(const void *tmp1, const void *tmp2) {
|
||||
const chkHeapBucket *res1 = tmp1;
|
||||
const chkHeapBucket *res2 = tmp2;
|
||||
return res1->count < res2->count ? 1 : res1->count > res2->count ? -1 : 0;
|
||||
}
|
||||
|
||||
/* Get an ordered by count list of topk->k elements inside the topk object.
|
||||
*
|
||||
* NOTE, the returned array is a copy of the internal heap stored by `topk`. The
|
||||
* caller is responsible for releasing it after use. The elements of the array
|
||||
* share their `item` pointers with the internal topk->heap buckets so one must
|
||||
* not use it after `topk` is released. */
|
||||
chkHeapBucket *chkTopKList(chkTopK *topk) {
|
||||
chkHeapBucket *list = zmalloc(sizeof(chkHeapBucket) * topk->k);
|
||||
memcpy(list, topk->heap, sizeof(chkHeapBucket) * topk->k);
|
||||
qsort(list, topk->k, sizeof(*list), cmpchkHeapBucket);
|
||||
return list;
|
||||
}
|
||||
|
||||
size_t chkTopKGetMemoryUsage(chkTopK *topk) {
|
||||
if (!topk) return 0;
|
||||
|
||||
return topk->alloc_size;
|
||||
}
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
|
||||
#include <stdio.h>
|
||||
#include "testhelp.h"
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
static int findItemInList(chkHeapBucket *list, int k, const char *item, int itemlen) {
|
||||
for (int i = 0; i < k; i++) {
|
||||
if (list[i].item != NULL &&
|
||||
sdslen(list[i].item) == (size_t)itemlen &&
|
||||
memcmp(list[i].item, item, itemlen) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int verifyListSorted(chkHeapBucket *list, int k) {
|
||||
for (int i = 0; i < k - 1; i++) {
|
||||
if (list[i].item == NULL) continue;
|
||||
if (list[i + 1].item == NULL) continue;
|
||||
if (list[i].count < list[i + 1].count) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void chkTopKUpdateAndFreeExpelled(chkTopK *topk, const char *item, int itemlen, counter_t weight) {
|
||||
sds expelled = chkTopKUpdate(topk, (char *)item, itemlen, weight);
|
||||
if (expelled) sdsfree(expelled);
|
||||
}
|
||||
|
||||
static void testBasicTopK(void) {
|
||||
int k = 5;
|
||||
int numbuckets = 64;
|
||||
double decay = 0.9;
|
||||
|
||||
chkTopK *topk = chkTopKCreate(k, numbuckets, decay);
|
||||
test_cond("Create topk structure", topk != NULL);
|
||||
|
||||
if (topk == NULL) return;
|
||||
|
||||
chkTopKUpdateAndFreeExpelled(topk, "item1", 5, 100);
|
||||
chkTopKUpdateAndFreeExpelled(topk, "item2", 5, 200);
|
||||
chkTopKUpdateAndFreeExpelled(topk, "item3", 5, 150);
|
||||
chkTopKUpdateAndFreeExpelled(topk, "item4", 5, 50);
|
||||
chkTopKUpdateAndFreeExpelled(topk, "item5", 5, 300);
|
||||
chkTopKUpdateAndFreeExpelled(topk, "item6", 5, 75);
|
||||
|
||||
chkHeapBucket *list = chkTopKList(topk);
|
||||
test_cond("chkTopKList returns non-NULL", list != NULL);
|
||||
|
||||
if (list == NULL) {
|
||||
chkTopKRelease(topk);
|
||||
return;
|
||||
}
|
||||
|
||||
test_cond("TopK list is sorted in descending order", verifyListSorted(list, k));
|
||||
|
||||
int idx1 = findItemInList(list, k, "item5", 5);
|
||||
int idx2 = findItemInList(list, k, "item2", 5);
|
||||
int idx3 = findItemInList(list, k, "item3", 5);
|
||||
|
||||
test_cond("Heaviest items are in the list", idx1 != -1 && idx2 != -1 && idx3 != -1);
|
||||
|
||||
test_cond("item5 has the highest count", idx1 == 0);
|
||||
|
||||
zfree(list);
|
||||
chkTopKRelease(topk);
|
||||
}
|
||||
|
||||
static void testHeavierElementsReplaceLighter(void) {
|
||||
int k = 5;
|
||||
int numbuckets = 64;
|
||||
double decay = 0.9;
|
||||
|
||||
chkTopK *topk = chkTopKCreate(k, numbuckets, decay);
|
||||
test_cond("Create topk structure for replacement test", topk != NULL);
|
||||
|
||||
if (topk == NULL) return;
|
||||
|
||||
chkTopKUpdateAndFreeExpelled(topk, "light1", 6, 50);
|
||||
chkTopKUpdateAndFreeExpelled(topk, "light2", 6, 60);
|
||||
chkTopKUpdateAndFreeExpelled(topk, "light3", 6, 70);
|
||||
chkTopKUpdateAndFreeExpelled(topk, "light4", 6, 80);
|
||||
chkTopKUpdateAndFreeExpelled(topk, "light5", 6, 90);
|
||||
|
||||
chkHeapBucket *list1 = chkTopKList(topk);
|
||||
test_cond("Initial topk list is not NULL", list1 != NULL);
|
||||
|
||||
if (list1 == NULL) {
|
||||
chkTopKRelease(topk);
|
||||
return;
|
||||
}
|
||||
|
||||
int light1_idx = findItemInList(list1, k, "light1", 6);
|
||||
int light2_idx = findItemInList(list1, k, "light2", 6);
|
||||
int light3_idx = findItemInList(list1, k, "light3", 6);
|
||||
int light4_idx = findItemInList(list1, k, "light4", 6);
|
||||
int light5_idx = findItemInList(list1, k, "light5", 6);
|
||||
|
||||
test_cond("light1 is in initial topk list", light1_idx != -1);
|
||||
test_cond("light2 is in initial topk list", light2_idx != -1);
|
||||
test_cond("light3 is in initial topk list", light3_idx != -1);
|
||||
test_cond("light4 is in initial topk list", light4_idx != -1);
|
||||
test_cond("light5 is in initial topk list", light5_idx != -1);
|
||||
|
||||
zfree(list1);
|
||||
|
||||
chkTopKUpdateAndFreeExpelled(topk, "heavy1", 6, 500);
|
||||
chkTopKUpdateAndFreeExpelled(topk, "heavy2", 6, 600);
|
||||
|
||||
chkHeapBucket *list2 = chkTopKList(topk);
|
||||
test_cond("Updated topk list is not NULL", list2 != NULL);
|
||||
|
||||
if (list2 == NULL) {
|
||||
chkTopKRelease(topk);
|
||||
return;
|
||||
}
|
||||
|
||||
int heavy1_idx = findItemInList(list2, k, "heavy1", 6);
|
||||
int heavy2_idx = findItemInList(list2, k, "heavy2", 6);
|
||||
|
||||
test_cond("heavy1 is in updated topk list", heavy1_idx != -1);
|
||||
test_cond("heavy2 is in updated topk list", heavy2_idx != -1);
|
||||
|
||||
light1_idx = findItemInList(list2, k, "light1", 6);
|
||||
light2_idx = findItemInList(list2, k, "light2", 6);
|
||||
light3_idx = findItemInList(list2, k, "light3", 6);
|
||||
light4_idx = findItemInList(list2, k, "light4", 6);
|
||||
light5_idx = findItemInList(list2, k, "light5", 6);
|
||||
|
||||
int light_items_remaining = (light1_idx != -1 ? 1 : 0) +
|
||||
(light2_idx != -1 ? 1 : 0) +
|
||||
(light3_idx != -1 ? 1 : 0) +
|
||||
(light4_idx != -1 ? 1 : 0) +
|
||||
(light5_idx != -1 ? 1 : 0);
|
||||
|
||||
test_cond("Some lighter items remain in the list after adding heavier ones",
|
||||
light_items_remaining > 0);
|
||||
|
||||
zfree(list2);
|
||||
chkTopKRelease(topk);
|
||||
}
|
||||
|
||||
static void testManySmallWeightUpdates(void) {
|
||||
int k = 2;
|
||||
int numbuckets = 64;
|
||||
double decay = 0.9;
|
||||
|
||||
chkTopK *topk = chkTopKCreate(k, numbuckets, decay);
|
||||
test_cond("Create topk structure for small weight updates test", topk != NULL);
|
||||
|
||||
if (topk == NULL) return;
|
||||
|
||||
chkTopKUpdateAndFreeExpelled(topk, "item0", 5, 50);
|
||||
chkTopKUpdateAndFreeExpelled(topk, "item1", 5, 100);
|
||||
|
||||
chkHeapBucket *list1 = chkTopKList(topk);
|
||||
test_cond("Topk list after adding item0 and item1 is not NULL", list1 != NULL);
|
||||
|
||||
if (list1 == NULL) {
|
||||
chkTopKRelease(topk);
|
||||
return;
|
||||
}
|
||||
|
||||
int item0_idx1 = findItemInList(list1, k, "item0", 5);
|
||||
int item1_idx1 = findItemInList(list1, k, "item1", 5);
|
||||
|
||||
test_cond("item0 and item1 are in topk after initial updates",
|
||||
item0_idx1 != -1 && item1_idx1 != -1);
|
||||
|
||||
zfree(list1);
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
chkTopKUpdateAndFreeExpelled(topk, "item2", 5, 1);
|
||||
}
|
||||
|
||||
chkHeapBucket *list2 = chkTopKList(topk);
|
||||
test_cond("Topk list after many small updates is not NULL", list2 != NULL);
|
||||
|
||||
if (list2 == NULL) {
|
||||
chkTopKRelease(topk);
|
||||
return;
|
||||
}
|
||||
|
||||
int item0_idx2 = findItemInList(list2, k, "item0", 5);
|
||||
int item1_idx2 = findItemInList(list2, k, "item1", 5);
|
||||
int item2_idx2 = findItemInList(list2, k, "item2", 5);
|
||||
|
||||
test_cond("item1 and item2 are in topk, item0 is not",
|
||||
item1_idx2 != -1 && item2_idx2 != -1 && item0_idx2 == -1);
|
||||
|
||||
counter_t item1_count = 0;
|
||||
counter_t item2_count = 0;
|
||||
if (item1_idx2 != -1) item1_count = list2[item1_idx2].count;
|
||||
if (item2_idx2 != -1) item2_count = list2[item2_idx2].count;
|
||||
|
||||
test_cond("item1 and item2 have similar weights", item1_count > 0 && item2_count > 0 &&
|
||||
(item1_count > item2_count ? item1_count - item2_count : item2_count - item1_count) < 5);
|
||||
|
||||
zfree(list2);
|
||||
chkTopKRelease(topk);
|
||||
}
|
||||
|
||||
int chkTopKTest(int argc, char *argv[], int flags) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
UNUSED(flags);
|
||||
|
||||
testBasicTopK();
|
||||
testHeavierElementsReplaceLighter();
|
||||
testManySmallWeightUpdates();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* REDIS_TEST */
|
||||
@@ -0,0 +1,89 @@
|
||||
/* Implementation of a topK structure using CuckooHeavyKeeper algorithm
|
||||
*
|
||||
* Implementation is based on the paper "Cuckoo Heavy Keeper and the balancing
|
||||
* act of maintaining heavy hitters in stream processing" by Vinh Quang Ngo and
|
||||
* Marina Papatriantafilou. Also, the accompanying C++ implementation was used
|
||||
* as a reference point: https://github.com/vinhqngo5/Cuckoo_Heavy_Keeper
|
||||
* Main changes are addition of a min-heap so we can keep names of the top K
|
||||
* elements - idea comes from RedisBloom's TopK structure.
|
||||
*
|
||||
* Copyright (c) 2026-Present, Redis Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Licensed under your choice of (a) the Redis Source Available License 2.0
|
||||
* (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the
|
||||
* GNU Affero General Public License v3 (AGPLv3).
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sds.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define CHK_LUT_SIZE 256
|
||||
#define CHK_HEAVY_ENTRIES_PER_BUCKET 2
|
||||
#define CHK_NUM_TABLES 2
|
||||
|
||||
typedef uint64_t counter_t;
|
||||
typedef uint16_t fingerprint_t;
|
||||
typedef uint8_t lobby_counter_t;
|
||||
|
||||
typedef struct {
|
||||
counter_t count;
|
||||
fingerprint_t fp;
|
||||
} chkHeavyEntry;
|
||||
|
||||
typedef struct {
|
||||
fingerprint_t fp;
|
||||
lobby_counter_t count;
|
||||
} chkLobbyEntry;
|
||||
|
||||
typedef struct {
|
||||
chkHeavyEntry heavy_entries[CHK_HEAVY_ENTRIES_PER_BUCKET];
|
||||
chkLobbyEntry lobby_entry;
|
||||
} chkBucket;
|
||||
|
||||
typedef struct {
|
||||
counter_t count;
|
||||
sds item;
|
||||
uint64_t fp; /* Fingerprint used to identify the item. Internal use only */
|
||||
} chkHeapBucket;
|
||||
|
||||
typedef struct chkTopK {
|
||||
chkBucket *tables[CHK_NUM_TABLES]; /* Cuckoo tables */
|
||||
chkHeapBucket *heap; /* Min-heap for storing top-K item's names */
|
||||
|
||||
size_t alloc_size; /* Used for memory tracking only */
|
||||
|
||||
/* Expected number of operations to decay count i to 0 */
|
||||
double lut_decay_exp[CHK_LUT_SIZE + 1];
|
||||
|
||||
/* Minimum number of decay operations to decay count i with 1 */
|
||||
double lut_min_decay[CHK_LUT_SIZE + 1];
|
||||
|
||||
/* Probability of decaying i with 1. As per paper probability is decay^-i
|
||||
* but we actually store (1/decay)^i for faster computation. */
|
||||
double lut_decay_prob[CHK_LUT_SIZE + 1];
|
||||
|
||||
double decay; /* Decay constant */
|
||||
double inv_decay; /* Cache 1/decay for faster computations */
|
||||
|
||||
counter_t total; /* Total recorded count for all updates */
|
||||
|
||||
int k;
|
||||
int numbuckets;
|
||||
} chkTopK;
|
||||
|
||||
chkTopK *chkTopKCreate(int k, int numbuckets, double decay);
|
||||
void chkTopKRelease(chkTopK *topk);
|
||||
sds chkTopKUpdate(chkTopK *topk, char *item, int itemlen, counter_t weight);
|
||||
chkHeapBucket *chkTopKList(chkTopK *topk);
|
||||
size_t chkTopKGetMemoryUsage(chkTopK *topk);
|
||||
|
||||
#ifdef REDIS_TEST
|
||||
|
||||
int chkTopKTest(int argc, char *argv[], int flags);
|
||||
|
||||
#endif /* REDIS_TEST */
|
||||
+10
-3
@@ -324,11 +324,18 @@ void parseRedisUri(const char *uri, const char* tool_name, cliConnInfo *connInfo
|
||||
/* Extract user info. */
|
||||
if ((userinfo = strchr(curr,'@'))) {
|
||||
if ((username = strchr(curr, ':')) && username < userinfo) {
|
||||
connInfo->user = percentDecode(curr, username - curr);
|
||||
/* Free any value previously set via --user / -a (later
|
||||
* parameters override earlier ones) and use NULL for an
|
||||
* explicitly empty component, so cliAuth() falls back to the
|
||||
* legacy single-argument AUTH (empty username) or skips AUTH
|
||||
* entirely (empty password) instead of sending an empty ACL
|
||||
* component, which the server rejects. */
|
||||
sdsfree(connInfo->user);
|
||||
connInfo->user = (username > curr) ? percentDecode(curr, username - curr) : NULL;
|
||||
curr = username + 1;
|
||||
}
|
||||
|
||||
connInfo->auth = percentDecode(curr, userinfo - curr);
|
||||
sdsfree(connInfo->auth);
|
||||
connInfo->auth = (userinfo > curr) ? percentDecode(curr, userinfo - curr) : NULL;
|
||||
curr = userinfo + 1;
|
||||
}
|
||||
if (curr == end) return;
|
||||
|
||||
+301
-68
@@ -24,6 +24,7 @@
|
||||
#include "cluster_slot_stats.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include "bio.h"
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Key space handling
|
||||
@@ -92,6 +93,10 @@ void createDumpPayload(rio *payload, robj *o, robj *key, int dbid, int skip_chec
|
||||
/* Serialize the object in an RDB-like format. It consist of an object type
|
||||
* byte followed by the serialized object. This is understood by RESTORE. */
|
||||
rioInitWithBuffer(payload,sdsempty());
|
||||
|
||||
/* Save key metadata if present without (handles TTL separately via command args) */
|
||||
if (getModuleMetaBits(o->metabits))
|
||||
serverAssert(rdbSaveKeyMetadata(payload, key, o, dbid) != -1);
|
||||
serverAssert(rdbSaveObjectType(payload,o));
|
||||
serverAssert(rdbSaveObject(payload,o,key,dbid));
|
||||
|
||||
@@ -240,9 +245,28 @@ void restoreCommand(client *c) {
|
||||
}
|
||||
|
||||
rioInitWithBuffer(&payload,c->argv[3]->ptr);
|
||||
if (((type = rdbLoadObjectType(&payload)) == -1) ||
|
||||
((obj = rdbLoadObject(type,&payload,key->ptr,c->db->id,NULL)) == NULL))
|
||||
|
||||
/* Initialize metadata spec to collect metadata+expiry from payload. */
|
||||
KeyMetaSpec keymeta;
|
||||
keyMetaSpecInit(&keymeta);
|
||||
|
||||
/* Compute TTL early so we can add it to metadata spec in correct order */
|
||||
if (ttl) {
|
||||
if (!absttl) ttl+=commandTimeSnapshot();
|
||||
keyMetaSpecAdd(&keymeta, KEY_META_ID_EXPIRE, ttl);
|
||||
}
|
||||
|
||||
/* With metadata, type = RDB_OPCODE_KEY_META. Layout: [<META>,]<TYPE>,<KEY>,<VALUE> */
|
||||
type = rdbLoadType(&payload);
|
||||
if (rdbResolveKeyType(&payload, &type, c->db->id, &keymeta) == -1) {
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Load the object */
|
||||
if ((obj = rdbLoadObject(type,&payload,key->ptr,c->db->id,NULL)) == NULL)
|
||||
{
|
||||
keyMetaSpecCleanup(&keymeta);
|
||||
addReplyError(c,"Bad data format");
|
||||
return;
|
||||
}
|
||||
@@ -252,31 +276,39 @@ void restoreCommand(client *c) {
|
||||
if (replace)
|
||||
deleted = dbDelete(c->db,key);
|
||||
|
||||
if (ttl && !absttl) ttl+=commandTimeSnapshot();
|
||||
if (ttl && checkAlreadyExpired(ttl)) {
|
||||
if (deleted) {
|
||||
robj *aux = server.lazyfree_lazy_server_del ? shared.unlink : shared.del;
|
||||
rewriteClientCommandVector(c, 2, aux, key);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
keyModified(c,c->db,key,NULL,1);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
|
||||
server.dirty++;
|
||||
}
|
||||
/* Update the stats, see setGenericCommand for details. */
|
||||
server.stat_expiredkeys++;
|
||||
keyMetaSpecCleanup(&keymeta);
|
||||
decrRefCount(obj);
|
||||
addReply(c, shared.ok);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create the key and set the TTL if any */
|
||||
kvobj *kv = dbAddInternal(c->db, key, &obj, NULL, ttl ? ttl : -1);
|
||||
kvobj *kv = dbAddInternal(c->db, key, &obj, NULL, &keymeta);
|
||||
|
||||
/* Save type: kv may be reallocated by module callbacks during notifyKeyspaceEvent below. */
|
||||
int kvtype = kv->type;
|
||||
|
||||
/* If minExpiredField was set, then the object is hash with expiration
|
||||
* on fields and need to register it in global HFE DS */
|
||||
if (kv->type == OBJ_HASH) {
|
||||
if (kvtype == OBJ_HASH) {
|
||||
uint64_t minExpiredField = hashTypeGetMinExpire(kv, 1);
|
||||
if (minExpiredField != EB_EXPIRE_TIME_INVALID)
|
||||
estoreAdd(c->db->subexpires, getKeySlot(key->ptr), kv, minExpiredField);
|
||||
}
|
||||
|
||||
if (kvtype == OBJ_STREAM)
|
||||
streamKeyLoaded(c->db, key, kv);
|
||||
|
||||
if (ttl) {
|
||||
if (!absttl) {
|
||||
/* Propagate TTL as absolute timestamp */
|
||||
@@ -287,14 +319,15 @@ void restoreCommand(client *c) {
|
||||
}
|
||||
}
|
||||
objectSetLRUOrLFU(kv, lfu_freq, lru_idle, lru_clock, 1000);
|
||||
signalModifiedKey(c,c->db,key);
|
||||
keyModified(c,c->db,key,NULL,1);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"restore",key,c->db->id);
|
||||
KSN_INVALIDATE_KVOBJ(kv);
|
||||
|
||||
/* If we deleted a key that means REPLACE parameter was passed and the
|
||||
* destination key existed. */
|
||||
if (deleted) {
|
||||
notifyKeyspaceEvent(NOTIFY_OVERWRITTEN, "overwritten", key, c->db->id);
|
||||
if (oldtype != kv->type) {
|
||||
if (oldtype != kvtype) {
|
||||
notifyKeyspaceEvent(NOTIFY_TYPE_CHANGED, "type_changed", key, c->db->id);
|
||||
}
|
||||
}
|
||||
@@ -660,7 +693,7 @@ void migrateCommand(client *c) {
|
||||
if (!copy) {
|
||||
/* No COPY option: remove the local key, signal the change. */
|
||||
dbDelete(c->db,keyArray[j]);
|
||||
signalModifiedKey(c,c->db,keyArray[j]);
|
||||
keyModified(c,c->db,keyArray[j],NULL,1);
|
||||
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",keyArray[j],c->db->id);
|
||||
server.dirty++;
|
||||
|
||||
@@ -771,7 +804,12 @@ int verifyClusterNodeId(const char *name, int length) {
|
||||
}
|
||||
|
||||
int isValidAuxChar(int c) {
|
||||
return isalnum(c) || (strchr("!#$%&()*+:;<>?@[]^{|}~", c) == NULL);
|
||||
/* Reject control characters (0x00-0x1F and 0x7F). */
|
||||
if (iscntrl(c)) {
|
||||
return 0;
|
||||
}
|
||||
/* Reject forbidden characters including nodes.conf delimiters and special parsing characters */
|
||||
return isalnum(c) || (strchr("!#$%&()*+:;<>?@[]^{|}~,= \"'\\", c) == NULL);
|
||||
}
|
||||
|
||||
int isValidAuxString(char *s, unsigned int length) {
|
||||
@@ -1054,16 +1092,16 @@ void clusterCommand(client *c) {
|
||||
unsigned int keys_in_slot = countKeysInSlot(slot);
|
||||
unsigned int numkeys = maxkeys > keys_in_slot ? keys_in_slot : maxkeys;
|
||||
addReplyArrayLen(c,numkeys);
|
||||
kvstoreDictIterator *kvs_di = NULL;
|
||||
kvstoreDictIterator kvs_di;
|
||||
dictEntry *de = NULL;
|
||||
kvs_di = kvstoreGetDictIterator(server.db->keys, slot);
|
||||
kvstoreInitDictIterator(&kvs_di, server.db->keys, slot);
|
||||
for (unsigned int i = 0; i < numkeys; i++) {
|
||||
de = kvstoreDictIteratorNext(kvs_di);
|
||||
de = kvstoreDictIteratorNext(&kvs_di);
|
||||
serverAssert(de != NULL);
|
||||
sds sdskey = kvobjGetKey(dictGetKV(de));
|
||||
addReplyBulkCBuffer(c, sdskey, sdslen(sdskey));
|
||||
}
|
||||
kvstoreReleaseDictIterator(kvs_di);
|
||||
kvstoreResetDictIterator(&kvs_di);
|
||||
} else if ((!strcasecmp(c->argv[1]->ptr,"slaves") ||
|
||||
!strcasecmp(c->argv[1]->ptr,"replicas")) && c->argc == 3) {
|
||||
/* CLUSTER SLAVES <NODE ID> */
|
||||
@@ -1089,6 +1127,10 @@ void clusterCommand(client *c) {
|
||||
addReplyBulkCString(c,ni);
|
||||
sdsfree(ni);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr, "migration")) {
|
||||
clusterMigrationCommand(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"syncslots") && c->argc >= 3) {
|
||||
clusterSyncSlotsCommand(c);
|
||||
} else if(!clusterCommandSpecial(c)) {
|
||||
addReplySubcommandSyntaxError(c);
|
||||
return;
|
||||
@@ -1096,16 +1138,14 @@ void clusterCommand(client *c) {
|
||||
}
|
||||
|
||||
/* Extract slot number from keys in a keys_result structure and return to caller.
|
||||
* Returns INVALID_CLUSTER_SLOT if keys belong to different slots (cross-slot error),
|
||||
* or if there are no keys.
|
||||
*/
|
||||
* Returns:
|
||||
* - The slot number if all keys belong to the same slot
|
||||
* - INVALID_CLUSTER_SLOT if there are no keys or cluster is disabled
|
||||
* - CLUSTER_CROSSSLOT if keys belong to different slots (cross-slot error) */
|
||||
int extractSlotFromKeysResult(robj **argv, getKeysResult *keys_result) {
|
||||
if (keys_result->numkeys == 0)
|
||||
if (keys_result->numkeys == 0 || !server.cluster_enabled)
|
||||
return INVALID_CLUSTER_SLOT;
|
||||
|
||||
if (!server.cluster_enabled)
|
||||
return 0;
|
||||
|
||||
int first_slot = INVALID_CLUSTER_SLOT;
|
||||
for (int j = 0; j < keys_result->numkeys; j++) {
|
||||
robj *this_key = argv[keys_result->keys[j].pos];
|
||||
@@ -1114,7 +1154,7 @@ int extractSlotFromKeysResult(robj **argv, getKeysResult *keys_result) {
|
||||
if (first_slot == INVALID_CLUSTER_SLOT)
|
||||
first_slot = this_slot;
|
||||
else if (first_slot != this_slot) {
|
||||
return INVALID_CLUSTER_SLOT;
|
||||
return CLUSTER_CROSSSLOT;
|
||||
}
|
||||
}
|
||||
return first_slot;
|
||||
@@ -1150,6 +1190,8 @@ int extractSlotFromKeysResult(robj **argv, getKeysResult *keys_result) {
|
||||
* already "down" but it is fragile to rely on the update of the global state,
|
||||
* so we also handle it here.
|
||||
*
|
||||
* CLUSTER_REDIR_TRIMMING if the request addresses a slot that is being trimmed.
|
||||
*
|
||||
* CLUSTER_REDIR_DOWN_STATE and CLUSTER_REDIR_DOWN_RO_STATE if the cluster is
|
||||
* down but the user attempts to execute a command that addresses one or more keys. */
|
||||
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot,
|
||||
@@ -1240,6 +1282,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
/* The command has keys and was checked for cross-slot between its keys in preprocessCommand() */
|
||||
if (pcmd->read_error == CLIENT_READ_CROSS_SLOT) {
|
||||
/* Error: multiple keys from different slots. */
|
||||
if (!use_cache_keys_result) getKeysFreeResult(&result);
|
||||
if (error_code)
|
||||
*error_code = CLUSTER_REDIR_CROSS_SLOT;
|
||||
return NULL;
|
||||
@@ -1391,6 +1434,15 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
|
||||
return myself;
|
||||
}
|
||||
|
||||
/* If this node is responsible for the slot and is currently trimming it,
|
||||
* SFLUSH may have triggered active trimming and it could still be in progress.
|
||||
* Here we reject any write commands as no writes should be accepted for
|
||||
* trimming slots while active trimming is in progress. */
|
||||
if (n == myself && is_write_command && isSlotInTrimJob(slot)) {
|
||||
if (error_code) *error_code = CLUSTER_REDIR_TRIMMING;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Base case: just return the right node. However, if this node is not
|
||||
* myself, set error_code to MOVED since we need to issue a redirection. */
|
||||
if (n != myself && error_code) *error_code = CLUSTER_REDIR_MOVED;
|
||||
@@ -1427,6 +1479,8 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
|
||||
"-%s %d %s:%d",
|
||||
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
|
||||
hashslot, clusterNodePreferredEndpoint(n), port));
|
||||
} else if (error_code == CLUSTER_REDIR_TRIMMING) {
|
||||
addReplyError(c,"-TRYAGAIN Slot is being trimmed");
|
||||
} else {
|
||||
serverPanic("getNodeByQuery() unknown error.");
|
||||
}
|
||||
@@ -1670,16 +1724,16 @@ unsigned int clusterDelKeysInSlot(unsigned int hashslot, int by_command) {
|
||||
if (!kvstoreDictSize(server.db->keys, (int) hashslot))
|
||||
return 0;
|
||||
|
||||
kvstoreDictIterator *kvs_di = NULL;
|
||||
kvstoreDictIterator kvs_di;
|
||||
dictEntry *de = NULL;
|
||||
kvs_di = kvstoreGetDictSafeIterator(server.db->keys, (int) hashslot);
|
||||
while((de = kvstoreDictIteratorNext(kvs_di)) != NULL) {
|
||||
kvstoreInitDictSafeIterator(&kvs_di, server.db->keys, (int) hashslot);
|
||||
while((de = kvstoreDictIteratorNext(&kvs_di)) != NULL) {
|
||||
enterExecutionUnit(1, 0);
|
||||
sds sdskey = kvobjGetKey(dictGetKV(de));
|
||||
robj *key = createStringObject(sdskey, sdslen(sdskey));
|
||||
dbDelete(&server.db[0], key);
|
||||
|
||||
signalModifiedKey(NULL, &server.db[0], key);
|
||||
keyModified(NULL, &server.db[0], key, NULL, 1);
|
||||
if (by_command) {
|
||||
/* Keys are deleted by a command (trimslots), we need to notify the
|
||||
* keyspace event. Though, we don't need to propagate the DEL
|
||||
@@ -1692,7 +1746,7 @@ unsigned int clusterDelKeysInSlot(unsigned int hashslot, int by_command) {
|
||||
* just moved to another node. The modules needs to know that these
|
||||
* keys are no longer available locally, so just send the keyspace
|
||||
* notification to the modules, but not to clients. */
|
||||
moduleNotifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, server.db[0].id);
|
||||
moduleNotifyKeyspaceEvent(NOTIFY_GENERIC, "del", key, server.db[0].id, NULL, 0);
|
||||
}
|
||||
exitExecutionUnit();
|
||||
postExecutionUnitOperations();
|
||||
@@ -1700,7 +1754,7 @@ unsigned int clusterDelKeysInSlot(unsigned int hashslot, int by_command) {
|
||||
j++;
|
||||
server.dirty++;
|
||||
}
|
||||
kvstoreReleaseDictIterator(kvs_di);
|
||||
kvstoreResetDictIterator(&kvs_di);
|
||||
return j;
|
||||
}
|
||||
|
||||
@@ -1719,18 +1773,18 @@ int clusterIsMySlot(int slot) {
|
||||
return getMyClusterNode() == getNodeBySlot(slot);
|
||||
}
|
||||
|
||||
void replySlotsFlushAndFree(client *c, slotRangeArray *slots) {
|
||||
void replySlotsFlush(client *c, slotRangeArray *slots) {
|
||||
addReplyArrayLen(c, slots->num_ranges);
|
||||
for (int i = 0 ; i < slots->num_ranges ; i++) {
|
||||
addReplyArrayLen(c, 2);
|
||||
addReplyLongLong(c, slots->ranges[i].start);
|
||||
addReplyLongLong(c, slots->ranges[i].end);
|
||||
}
|
||||
slotRangeArrayFree(slots);
|
||||
}
|
||||
|
||||
/* Checks that slot ranges are well-formed and non-overlapping. */
|
||||
int validateSlotRanges(slotRangeArray *slots, sds *err) {
|
||||
/* Normalizes (sorts and merges adjacent ranges), checks that slot ranges are
|
||||
* well-formed and non-overlapping. */
|
||||
int slotRangeArrayNormalizeAndValidate(slotRangeArray *slots, sds *err) {
|
||||
unsigned char used_slots[CLUSTER_SLOTS] = {0};
|
||||
|
||||
if (slots->num_ranges <= 0 || slots->num_ranges >= CLUSTER_SLOTS) {
|
||||
@@ -1738,6 +1792,9 @@ int validateSlotRanges(slotRangeArray *slots, sds *err) {
|
||||
return C_ERR;
|
||||
}
|
||||
|
||||
/* Sort and merge adjacent slot ranges. */
|
||||
slotRangeArraySortAndMerge(slots);
|
||||
|
||||
for (int i = 0; i < slots->num_ranges; i++) {
|
||||
if (slots->ranges[i].start >= CLUSTER_SLOTS ||
|
||||
slots->ranges[i].end >= CLUSTER_SLOTS)
|
||||
@@ -1787,6 +1844,7 @@ void slotRangeArraySet(slotRangeArray *slots, int idx, int start, int end) {
|
||||
/* Create a slot range string in the format of: "1000-2000 3000-4000 ..." */
|
||||
sds slotRangeArrayToString(slotRangeArray *slots) {
|
||||
sds s = sdsempty();
|
||||
if (slots == NULL || slots->num_ranges == 0) return s;
|
||||
|
||||
for (int i = 0; i < slots->num_ranges; i++) {
|
||||
slotRange *sr = &slots->ranges[i];
|
||||
@@ -1824,7 +1882,7 @@ slotRangeArray *slotRangeArrayFromString(sds data) {
|
||||
|
||||
/* Validate all ranges */
|
||||
sds err_msg = NULL;
|
||||
if (validateSlotRanges(slots, &err_msg) != C_OK) {
|
||||
if (slotRangeArrayNormalizeAndValidate(slots, &err_msg) != C_OK) {
|
||||
if (err_msg) sdsfree(err_msg);
|
||||
goto err;
|
||||
}
|
||||
@@ -1845,13 +1903,32 @@ static int compareSlotRange(const void *a, const void *b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Sort slot ranges by start slot and merge adjacent ranges.
|
||||
* Adjacent means: prev.end + 1 == next.start.
|
||||
* e.g. 1000-2000 2001-3000 0-100 => 0-100 1000-3000
|
||||
*
|
||||
* Note: Overlapping ranges are not merged.*/
|
||||
void slotRangeArraySortAndMerge(slotRangeArray *slots) {
|
||||
if (!slots || slots->num_ranges <= 1) return;
|
||||
|
||||
qsort(slots->ranges, slots->num_ranges, sizeof(slotRange), compareSlotRange);
|
||||
|
||||
int idx = 0;
|
||||
for (int i = 1; i < slots->num_ranges; i++) {
|
||||
if (slots->ranges[idx].end + 1 == slots->ranges[i].start)
|
||||
slots->ranges[idx].end = slots->ranges[i].end;
|
||||
else
|
||||
slots->ranges[++idx] = slots->ranges[i];
|
||||
}
|
||||
slots->num_ranges = idx + 1;
|
||||
}
|
||||
|
||||
/* Compare two slot range arrays, return 1 if equal, 0 otherwise */
|
||||
int slotRangeArrayIsEqual(slotRangeArray *slots1, slotRangeArray *slots2) {
|
||||
if (slots1->num_ranges != slots2->num_ranges) return 0;
|
||||
slotRangeArraySortAndMerge(slots1);
|
||||
slotRangeArraySortAndMerge(slots2);
|
||||
|
||||
/* Sort slot ranges first */
|
||||
qsort(slots1->ranges, slots1->num_ranges, sizeof(slotRange), compareSlotRange);
|
||||
qsort(slots2->ranges, slots2->num_ranges, sizeof(slotRange), compareSlotRange);
|
||||
if (slots1->num_ranges != slots2->num_ranges) return 0;
|
||||
|
||||
for (int i = 0; i < slots1->num_ranges; i++) {
|
||||
if (slots1->ranges[i].start != slots2->ranges[i].start ||
|
||||
@@ -1924,6 +2001,19 @@ void slotRangeArrayFreeGeneric(void *slots) {
|
||||
slotRangeArrayFree(slots);
|
||||
}
|
||||
|
||||
/* Returns the number of keys in the given slot ranges. */
|
||||
unsigned long long getKeyCountInSlotRangeArray(slotRangeArray *slots) {
|
||||
if (!slots) return 0;
|
||||
|
||||
unsigned long long key_count = 0;
|
||||
for (int i = 0; i < slots->num_ranges; i++) {
|
||||
for (int j = slots->ranges[i].start; j <= slots->ranges[i].end; j++) {
|
||||
key_count += countKeysInSlot(j);
|
||||
}
|
||||
}
|
||||
return key_count;
|
||||
}
|
||||
|
||||
/* Slot range array iterator */
|
||||
slotRangeArrayIter *slotRangeArrayGetIterator(slotRangeArray *slots) {
|
||||
slotRangeArrayIter *it = zmalloc(sizeof(*it));
|
||||
@@ -1957,13 +2047,18 @@ void slotRangeArrayIteratorFree(slotRangeArrayIter *it) {
|
||||
zfree(it);
|
||||
}
|
||||
|
||||
/* Parse slot ranges from the command arguments. Returns NULL on error. */
|
||||
/* Parse slot range pairs from argv starting at `pos`.
|
||||
* `argc` is the argument count, `pos` is the first slot argument index.
|
||||
* Returns a slotRangeArray or NULL on error. */
|
||||
slotRangeArray *parseSlotRangesOrReply(client *c, int argc, int pos) {
|
||||
int start, end, count;
|
||||
slotRangeArray *slots;
|
||||
|
||||
serverAssert(pos <= argc);
|
||||
serverAssert((argc - pos) % 2 == 0);
|
||||
/* Ensure there is at least one (start,end) slot range pairs. */
|
||||
if (argc < 0 || pos < 0 || pos >= argc || (argc - pos) < 2 || ((argc - pos) % 2) != 0) {
|
||||
addReplyErrorArity(c);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
count = (argc - pos) / 2;
|
||||
slots = slotRangeArrayCreate(count);
|
||||
@@ -1981,7 +2076,7 @@ slotRangeArray *parseSlotRangesOrReply(client *c, int argc, int pos) {
|
||||
}
|
||||
|
||||
sds err = NULL;
|
||||
if (validateSlotRanges(slots, &err) != C_OK) {
|
||||
if (slotRangeArrayNormalizeAndValidate(slots, &err) != C_OK) {
|
||||
addReplyErrorSds(c, err);
|
||||
slotRangeArrayFree(slots);
|
||||
return NULL;
|
||||
@@ -2012,17 +2107,26 @@ int clusterCanAccessKeysInSlot(int slot) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return the slot ranges that belong to the current node or its master. */
|
||||
/* Return the slot ranges that belong to the current node or its master.
|
||||
* In non-cluster mode, returns the full slot range (0-16383). */
|
||||
slotRangeArray *clusterGetLocalSlotRanges(void) {
|
||||
slotRangeArray *slots = NULL;
|
||||
|
||||
if (!server.cluster_enabled) {
|
||||
slots = slotRangeArrayCreate(1);
|
||||
slotRangeArray *slots = slotRangeArrayCreate(1);
|
||||
slotRangeArraySet(slots, 0, 0, CLUSTER_SLOTS - 1);
|
||||
return slots;
|
||||
}
|
||||
|
||||
clusterNode *master = clusterNodeGetMaster(getMyClusterNode());
|
||||
return clusterGetNodeSlotRanges(getMyClusterNode());
|
||||
}
|
||||
|
||||
/* Returns the slot ranges owned by the given node.
|
||||
* If the node is a replica, the master's slot ranges are returned.
|
||||
* Returns an empty array if the node has no slots. */
|
||||
slotRangeArray *clusterGetNodeSlotRanges(clusterNode *node) {
|
||||
slotRangeArray *slots = NULL;
|
||||
|
||||
serverAssert(server.cluster_enabled && node != NULL);
|
||||
clusterNode *master = clusterNodeGetMaster(node);
|
||||
if (master) {
|
||||
for (int i = 0; i < CLUSTER_SLOTS; i++) {
|
||||
if (clusterNodeCoversSlot(master, i))
|
||||
@@ -2036,16 +2140,18 @@ slotRangeArray *clusterGetLocalSlotRanges(void) {
|
||||
*
|
||||
* Usage: SFLUSH <start-slot> <end slot> [<start-slot> <end slot>]* [SYNC|ASYNC]
|
||||
*
|
||||
* This is an initial implementation of SFLUSH (slots flush) which is limited to
|
||||
* flushing a single shard as a whole, but in the future the same command may be
|
||||
* used to partially flush a shard based on hash slots. Currently only if provided
|
||||
* slots cover entirely the slots of a node, the node will be flushed and the
|
||||
* return value will be pairs of slot ranges. Otherwise, a single empty set will
|
||||
* be returned. If possible, SFLUSH SYNC will be run as blocking ASYNC as an
|
||||
* Redis will flush the slots that belong to this node and reply with the flushed
|
||||
* slot ranges. If no slot is flushed, an empty array will be returned.
|
||||
*
|
||||
* e.g. Node owns slot 100-200, user issues SFLUSH 50 150
|
||||
* Redis will flush slot 100-150 and reply with [100,150]
|
||||
*
|
||||
* If possible, SFLUSH SYNC will be run as blocking ASYNC as an
|
||||
* optimization.
|
||||
*/
|
||||
void sflushCommand(client *c) {
|
||||
int flags = EMPTYDB_NO_FLAGS, argc = c->argc;
|
||||
int trim_method = ASM_TRIM_METHOD_NONE;
|
||||
|
||||
if (server.cluster_enabled == 0) {
|
||||
addReplyError(c,"This instance has cluster support disabled");
|
||||
@@ -2073,40 +2179,87 @@ void sflushCommand(client *c) {
|
||||
slotRangeArray *slots = parseSlotRangesOrReply(c, argc, 1);
|
||||
if (!slots) return;
|
||||
|
||||
/* If client is AOF or master, we must obey the slot ranges. */
|
||||
int must_obey = mustObeyClient(c);
|
||||
|
||||
/* Iterate and find the slot ranges that belong to this node. Save them in
|
||||
* a new slotRangeArray. It is allocated on heap since there is a chance
|
||||
* that FLUSH SYNC will be running as blocking ASYNC and only later reply
|
||||
* with slot ranges */
|
||||
unsigned char slots_to_flush[CLUSTER_SLOTS] = {0}; /* Requested slots to flush */
|
||||
slotRangeArray *myslots = NULL;
|
||||
for (int i = 0; i < slots->num_ranges; i++) {
|
||||
for (int j = slots->ranges[i].start; j <= slots->ranges[i].end; j++) {
|
||||
if (clusterIsMySlot(j)) {
|
||||
if (must_obey || clusterIsMySlot(j)) {
|
||||
myslots = slotRangeArrayAppend(myslots, j);
|
||||
slots_to_flush[j] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Verify that all slots of mynode got covered. See sflushCommand() comment. */
|
||||
int all_slots_covered = 1;
|
||||
for (int i = 0; i < CLUSTER_SLOTS; i++) {
|
||||
if (clusterIsMySlot(i) && !slots_to_flush[i]) {
|
||||
all_slots_covered = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (myslots == NULL || !all_slots_covered) {
|
||||
/* If no slots belong to this node, return empty array. */
|
||||
if (myslots == NULL) {
|
||||
addReplyArrayLen(c, 0);
|
||||
slotRangeArrayFree(slots);
|
||||
slotRangeArrayFree(myslots);
|
||||
return;
|
||||
}
|
||||
slotRangeArrayFree(slots);
|
||||
|
||||
/* takes ownership of myslots */
|
||||
asmTrimCtx *trim_ctx = asmTrimCtxCreate(myslots, server.db[0].keys);
|
||||
|
||||
/* Flush selected slots. If not flush as blocking async, then reply immediately */
|
||||
if (flushCommandCommon(c, FLUSH_TYPE_SLOTS, flags, myslots) == 0)
|
||||
replySlotsFlushAndFree(c, myslots);
|
||||
/* If the selected slots are exactly the same as the local slots, we can
|
||||
* simply flush the entire DB by flushCommandCommon. */
|
||||
slotRangeArray *local_slots = clusterGetLocalSlotRanges();
|
||||
int all_slots_covered = slotRangeArrayIsEqual(myslots, local_slots);
|
||||
slotRangeArrayFree(local_slots);
|
||||
if (all_slots_covered) {
|
||||
/* If not flush as blocking async, then reply immediately */
|
||||
if (flushCommandCommon(c, FLUSH_TYPE_SLOTS, flags, trim_ctx) == 0) {
|
||||
replySlotsFlush(c, trim_ctx->slots);
|
||||
}
|
||||
asmTrimCtxRelease(trim_ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Cancel all ASM tasks that overlap with the given slot ranges. */
|
||||
clusterAsmCancelBySlotRangeArray(myslots, c->argv[0]->ptr);
|
||||
|
||||
/* In case of SYNC, check if we can optimize and run it in bg as blocking ASYNC */
|
||||
int blocking_async = 0;
|
||||
if ((!(flags & EMPTYDB_ASYNC)) && (!(c->flags & CLIENT_AVOID_BLOCKING_ASYNC_FLUSH))) {
|
||||
flags |= EMPTYDB_ASYNC; /* Run as ASYNC */
|
||||
blocking_async = 1;
|
||||
}
|
||||
|
||||
/* Trim the slots if running in async mode and not loading from AOF,
|
||||
* otherwise delete the keys synchronously. */
|
||||
if (flags & EMPTYDB_ASYNC && server.loading == 0) {
|
||||
/* Update dirty stats before trimming. */
|
||||
server.dirty += getKeyCountInSlotRangeArray(myslots);
|
||||
/* Pass client id for active trim to unblock client when trim completes. */
|
||||
trim_method = asmTrimSlots(trim_ctx, blocking_async ? c->id : CLIENT_ID_NONE, 0);
|
||||
} else {
|
||||
clusterDelKeysInSlotRangeArray(myslots, 1);
|
||||
}
|
||||
|
||||
/* Without the forceCommandPropagation, when DB was already empty,
|
||||
* SFLUSH will not be replicated nor put into the AOF. */
|
||||
forceCommandPropagation(c, PROPAGATE_REPL | PROPAGATE_AOF);
|
||||
|
||||
/* Handle waiting for trim job to complete in case of blocking async flush.
|
||||
* Block the client and schedule completion callback based on trim method:
|
||||
* - BG trim uses BIO lazyfree worker to trim the slots, so schedule a new
|
||||
* BIO lazyfree worker to wait for completion, then unblock client and reply.
|
||||
* - Active trim works in cron job of the main thread, it will automatically
|
||||
* unblock client and reply in active trim completion. */
|
||||
if (blocking_async && trim_method != ASM_TRIM_METHOD_NONE) {
|
||||
blockClientForAsyncFlush(c);
|
||||
} else {
|
||||
/* Reply with slot ranges that were flushed. SYNC and ASYNC mode will be
|
||||
* replied here immediately. */
|
||||
replySlotsFlush(c, trim_ctx->slots);
|
||||
}
|
||||
|
||||
asmTrimCtxRelease(trim_ctx); /* if bg trim, released later by kvsAsyncFreeDoneCB() */
|
||||
}
|
||||
|
||||
/* The READWRITE command just clears the READONLY command state. */
|
||||
@@ -2127,3 +2280,83 @@ void resetClusterStats(void) {
|
||||
|
||||
clusterSlotStatResetAll();
|
||||
}
|
||||
|
||||
/* This function is called at server startup in order to initialize cluster data
|
||||
* structures that are shared between the different cluster implementations. */
|
||||
void clusterCommonInit(void) {
|
||||
resetClusterStats();
|
||||
asmInit();
|
||||
}
|
||||
|
||||
/* This function is called after the node startup in order to check if there
|
||||
* are any slots that we have keys for, but are not assigned to us. If so,
|
||||
* we delete the keys. */
|
||||
void clusterDeleteKeysInUnownedSlots(void) {
|
||||
if (clusterNodeIsSlave(getMyClusterNode())) return;
|
||||
|
||||
/* Check that all the slots we have keys for are assigned to us. Otherwise,
|
||||
* delete the keys. */
|
||||
for (int i = 0; i < CLUSTER_SLOTS; i++) {
|
||||
/* Skip if: no keys in the slot, it's our slot, or we are importing it. */
|
||||
if (!countKeysInSlot(i) ||
|
||||
clusterIsMySlot(i) ||
|
||||
getImportingSlotSource(i))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
serverLog(LL_NOTICE, "I have keys for slot %d, but the slot is "
|
||||
"assigned to another node. "
|
||||
"Deleting keys in the slot.", i);
|
||||
/* With atomic slot migration, it is safe to drop keys from slots
|
||||
* that are not owned. This will not result in data loss under the
|
||||
* legacy slot migration approach either, since the importing state
|
||||
* has already been persisted in node.conf. */
|
||||
clusterDelKeysInSlot(i, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* This function is called after the node startup in order to verify that data
|
||||
* loaded from disk is in agreement with the cluster configuration:
|
||||
*
|
||||
* 1) If we find keys about hash slots we have no responsibility for, the
|
||||
* following happens:
|
||||
* A) If no other node is in charge according to the current cluster
|
||||
* configuration, we add these slots to our node.
|
||||
* B) If according to our config other nodes are already in charge for
|
||||
* this slots, we set the slots as IMPORTING from our point of view
|
||||
* in order to justify we have those slots, and in order to make
|
||||
* redis-cli aware of the issue, so that it can try to fix it.
|
||||
* 2) If we find data in a DB different than DB0 we return C_ERR to
|
||||
* signal the caller it should quit the server with an error message
|
||||
* or take other actions.
|
||||
*
|
||||
* The function always returns C_OK even if it will try to correct
|
||||
* the error described in "1". However if data is found in DB different
|
||||
* from DB0, C_ERR is returned.
|
||||
*
|
||||
* The function also uses the logging facility in order to warn the user
|
||||
* about desynchronizations between the data we have in memory and the
|
||||
* cluster configuration. */
|
||||
int verifyClusterConfigWithData(void) {
|
||||
/* Return ASAP if a module disabled cluster redirections. In that case
|
||||
* every master can store keys about every possible hash slot. */
|
||||
if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
|
||||
return C_OK;
|
||||
|
||||
/* If this node is a slave, don't perform the check at all as we
|
||||
* completely depend on the replication stream. */
|
||||
if (clusterNodeIsSlave(getMyClusterNode())) return C_OK;
|
||||
|
||||
/* Make sure we only have keys in DB0. */
|
||||
for (int i = 1; i < server.dbnum; i++) {
|
||||
if (kvstoreSize(server.db[i].keys)) return C_ERR;
|
||||
}
|
||||
|
||||
/* Take over slots that we have keys for, but are assigned to no one. */
|
||||
clusterClaimUnassignedSlots();
|
||||
/* Delete keys in unowned slots */
|
||||
clusterDeleteKeysInUnownedSlots();
|
||||
return C_OK;
|
||||
}
|
||||
|
||||
+8
-9
@@ -23,6 +23,7 @@
|
||||
#define CLUSTER_SLOTS (1<<CLUSTER_SLOT_MASK_BITS) /* Total number of slots in cluster mode, which is 16384. */
|
||||
#define CLUSTER_SLOT_MASK ((unsigned long long)(CLUSTER_SLOTS - 1)) /* Bit mask for slot id stored in LSB. */
|
||||
#define INVALID_CLUSTER_SLOT (-1) /* Invalid slot number. */
|
||||
#define CLUSTER_CROSSSLOT (-2)
|
||||
#define CLUSTER_OK 0 /* Everything looks ok */
|
||||
#define CLUSTER_FAIL 1 /* The cluster can't work */
|
||||
#define CLUSTER_NAMELEN 40 /* sha1 hex length */
|
||||
@@ -36,17 +37,11 @@
|
||||
#define CLUSTER_REDIR_DOWN_STATE 5 /* -CLUSTERDOWN, global state. */
|
||||
#define CLUSTER_REDIR_DOWN_UNBOUND 6 /* -CLUSTERDOWN, unbound slot. */
|
||||
#define CLUSTER_REDIR_DOWN_RO_STATE 7 /* -CLUSTERDOWN, allow reads. */
|
||||
#define CLUSTER_REDIR_TRIMMING 8 /* -TRYAGAIN, slot is being trimmed. */
|
||||
|
||||
typedef struct _clusterNode clusterNode;
|
||||
struct clusterState;
|
||||
|
||||
/* Struct used for storing slot statistics. */
|
||||
typedef struct clusterSlotStat {
|
||||
uint64_t cpu_usec; /* CPU time (in microseconds) spent on given slot */
|
||||
uint64_t network_bytes_in; /* Network ingress (in bytes) received for given slot */
|
||||
uint64_t network_bytes_out; /* Network egress (in bytes) sent for given slot */
|
||||
} clusterSlotStat;
|
||||
|
||||
/* Flags that a module can set in order to prevent certain Redis Cluster
|
||||
* features to be enabled. Useful when implementing a different distributed
|
||||
* system on top of Redis Cluster message bus, using modules. */
|
||||
@@ -86,8 +81,10 @@ static inline unsigned int keyHashSlot(const char *key, int keylen) {
|
||||
/* functions requiring mechanism specific implementations */
|
||||
void clusterInit(void);
|
||||
void clusterInitLast(void);
|
||||
void clusterCommonInit(void);
|
||||
void clusterCron(void);
|
||||
void clusterBeforeSleep(void);
|
||||
void clusterClaimUnassignedSlots(void);
|
||||
int verifyClusterConfigWithData(void);
|
||||
|
||||
int clusterSendModuleMessageToTarget(const char *target, uint64_t module_id, uint8_t type, const char *payload, uint32_t len);
|
||||
@@ -157,6 +154,7 @@ int getSlotOrReply(client *c, robj *o);
|
||||
int clusterIsMySlot(int slot);
|
||||
int clusterCanAccessKeysInSlot(int slot);
|
||||
struct slotRangeArray *clusterGetLocalSlotRanges(void);
|
||||
struct slotRangeArray *clusterGetNodeSlotRanges(clusterNode *node);
|
||||
|
||||
/* functions with shared implementations */
|
||||
clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot,
|
||||
@@ -166,7 +164,6 @@ int clusterRedirectBlockedClientIfNeeded(client *c);
|
||||
void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code);
|
||||
void migrateCloseTimedoutSockets(void);
|
||||
int patternHashSlot(char *pattern, int length);
|
||||
int getSlotOrReply(client *c, robj *o);
|
||||
int isValidAuxString(char *s, unsigned int length);
|
||||
void migrateCommand(client *c);
|
||||
void clusterCommand(client *c);
|
||||
@@ -189,6 +186,7 @@ slotRangeArray *slotRangeArrayDup(slotRangeArray *slots);
|
||||
void slotRangeArraySet(slotRangeArray *slots, int idx, int start, int end);
|
||||
sds slotRangeArrayToString(slotRangeArray *slots);
|
||||
slotRangeArray *slotRangeArrayFromString(sds data);
|
||||
void slotRangeArraySortAndMerge(slotRangeArray *slots);
|
||||
int slotRangeArrayIsEqual(slotRangeArray *slots1, slotRangeArray *slots2);
|
||||
slotRangeArray *slotRangeArrayAppend(slotRangeArray *slots, int slot);
|
||||
int slotRangeArrayContains(slotRangeArray *slots, unsigned int slot);
|
||||
@@ -198,8 +196,9 @@ slotRangeArrayIter *slotRangeArrayGetIterator(slotRangeArray *slots);
|
||||
int slotRangeArrayNext(slotRangeArrayIter *it);
|
||||
int slotRangeArrayGetCurrentSlot(slotRangeArrayIter *it);
|
||||
void slotRangeArrayIteratorFree(slotRangeArrayIter *it);
|
||||
int validateSlotRanges(slotRangeArray *slots, sds *err);
|
||||
int slotRangeArrayNormalizeAndValidate(slotRangeArray *slots, sds *err);
|
||||
slotRangeArray *parseSlotRangesOrReply(client *c, int argc, int pos);
|
||||
unsigned long long getKeyCountInSlotRangeArray(slotRangeArray *slots);
|
||||
|
||||
unsigned int clusterDelKeysInSlot(unsigned int hashslot, int by_command);
|
||||
unsigned int clusterDelKeysInSlotRangeArray(slotRangeArray *slots, int by_command);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user